summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--servers/gitlab_python/src/mcp_server_gitlab_python/server.py200
-rw-r--r--servers/gitlab_python/tests/test_server.py142
2 files changed, 342 insertions, 0 deletions
diff --git a/servers/gitlab_python/src/mcp_server_gitlab_python/server.py b/servers/gitlab_python/src/mcp_server_gitlab_python/server.py
index c176832..6688a1a 100644
--- a/servers/gitlab_python/src/mcp_server_gitlab_python/server.py
+++ b/servers/gitlab_python/src/mcp_server_gitlab_python/server.py
@@ -389,6 +389,126 @@ class GitLabPythonServer:
logger.error(f"Failed to update epic {group}#{epic_iid}: {e}")
return {"error": str(e)}
+ def list_issue_comments(
+ self, project: str, issue_iid: int
+ ) -> dict[str, Any]:
+ """List all comments (notes) for a given issue.
+
+ Args:
+ project (str): The project full path or ID.
+ issue_iid (int): The internal ID of the issue.
+
+ Returns:
+ dict[str, Any]: Dictionary with a list of comments or error message.
+ """
+ try:
+ proj = self.gl.projects.get(project)
+ issue = proj.issues.get(issue_iid)
+ notes = issue.notes.list(all=True)
+ return {
+ "comments": [
+ {
+ "id": n.id,
+ "body": n.body,
+ "author": getattr(n, "author", None),
+ "created_at": n.created_at,
+ "updated_at": n.updated_at,
+ "system": getattr(n, "system", False),
+ }
+ for n in notes
+ ]
+ }
+ except Exception as e:
+ return {"error": str(e)}
+
+ def create_issue_comment(
+ self, project: str, issue_iid: int, body: str
+ ) -> dict[str, Any]:
+ """Create a comment (note) on a given issue.
+
+ Args:
+ project (str): The project full path or ID.
+ issue_iid (int): The internal ID of the issue.
+ body (str): The comment text.
+
+ Returns:
+ dict[str, Any]: Dictionary with the comment info or error message.
+ """
+ try:
+ proj = self.gl.projects.get(project)
+ issue = proj.issues.get(issue_iid)
+ note = issue.notes.create({"body": body})
+ return {
+ "id": note.id,
+ "body": note.body,
+ "author": getattr(note, "author", None),
+ "created_at": note.created_at,
+ "updated_at": note.updated_at,
+ "system": getattr(note, "system", False),
+ }
+ except Exception as e:
+ return {"error": str(e)}
+
+ def list_epic_comments(
+ self, group: str, epic_iid: int
+ ) -> dict[str, Any]:
+ """List all comments (notes) for a given epic.
+
+ Args:
+ group (str): The group full path or ID.
+ epic_iid (int): The internal ID of the epic.
+
+ Returns:
+ dict[str, Any]: Dictionary with a list of comments or error message.
+ """
+ try:
+ grp = self.gl.groups.get(group)
+ epic = grp.epics.get(epic_iid)
+ notes = epic.notes.list(all=True)
+ return {
+ "comments": [
+ {
+ "id": n.id,
+ "body": n.body,
+ "author": getattr(n, "author", None),
+ "created_at": n.created_at,
+ "updated_at": n.updated_at,
+ "system": getattr(n, "system", False),
+ }
+ for n in notes
+ ]
+ }
+ except Exception as e:
+ return {"error": str(e)}
+
+ def create_epic_comment(
+ self, group: str, epic_iid: int, body: str
+ ) -> dict[str, Any]:
+ """Create a comment (note) on a given epic.
+
+ Args:
+ group (str): The group full path or ID.
+ epic_iid (int): The internal ID of the epic.
+ body (str): The comment text.
+
+ Returns:
+ dict[str, Any]: Dictionary with the comment info or error message.
+ """
+ try:
+ grp = self.gl.groups.get(group)
+ epic = grp.epics.get(epic_iid)
+ note = epic.notes.create({"body": body})
+ return {
+ "id": note.id,
+ "body": note.body,
+ "author": getattr(note, "author", None),
+ "created_at": note.created_at,
+ "updated_at": note.updated_at,
+ "system": getattr(note, "system", False),
+ }
+ except Exception as e:
+ return {"error": str(e)}
+
def create_server(host: str = "127.0.0.1", port: int = 8080) -> FastMCP:
mcp = FastMCP("GitLab Python", host=host, port=port)
@@ -616,6 +736,86 @@ def create_server(host: str = "127.0.0.1", port: int = 8080) -> FastMCP:
data.update(kwargs)
return server.update_epic(group, epic_iid, **data)
+ @mcp.tool()
+ def list_issue_comments(
+ project: str,
+ issue_iid: int,
+ working_directory: str,
+ ) -> dict[str, Any]:
+ """List all comments (notes) for a given issue.
+
+ Args:
+ project (str): The project full path or ID.
+ issue_iid (int): The internal ID of the issue.
+ working_directory (str): The working directory for context.
+
+ Returns:
+ dict[str, Any]: Dictionary with a list of comments or error message.
+ """
+ server = GitLabPythonServer(working_directory)
+ return server.list_issue_comments(project, issue_iid)
+
+ @mcp.tool()
+ def create_issue_comment(
+ project: str,
+ issue_iid: int,
+ body: str,
+ working_directory: str,
+ ) -> dict[str, Any]:
+ """Create a comment (note) on a given issue.
+
+ Args:
+ project (str): The project full path or ID.
+ issue_iid (int): The internal ID of the issue.
+ body (str): The comment text.
+ working_directory (str): The working directory for context.
+
+ Returns:
+ dict[str, Any]: Dictionary with the comment info or error message.
+ """
+ server = GitLabPythonServer(working_directory)
+ return server.create_issue_comment(project, issue_iid, body)
+
+ @mcp.tool()
+ def list_epic_comments(
+ group: str,
+ epic_iid: int,
+ working_directory: str,
+ ) -> dict[str, Any]:
+ """List all comments (notes) for a given epic.
+
+ Args:
+ group (str): The group full path or ID.
+ epic_iid (int): The internal ID of the epic.
+ working_directory (str): The working directory for context.
+
+ Returns:
+ dict[str, Any]: Dictionary with a list of comments or error message.
+ """
+ server = GitLabPythonServer(working_directory)
+ return server.list_epic_comments(group, epic_iid)
+
+ @mcp.tool()
+ def create_epic_comment(
+ group: str,
+ epic_iid: int,
+ body: str,
+ working_directory: str,
+ ) -> dict[str, Any]:
+ """Create a comment (note) on a given epic.
+
+ Args:
+ group (str): The group full path or ID.
+ epic_iid (int): The internal ID of the epic.
+ body (str): The comment text.
+ working_directory (str): The working directory for context.
+
+ Returns:
+ dict[str, Any]: Dictionary with the comment info or error message.
+ """
+ server = GitLabPythonServer(working_directory)
+ return server.create_epic_comment(group, epic_iid, body)
+
return mcp
async def main(transport_type: str, host: str, port: int) -> None:
diff --git a/servers/gitlab_python/tests/test_server.py b/servers/gitlab_python/tests/test_server.py
index 244cdd5..94820aa 100644
--- a/servers/gitlab_python/tests/test_server.py
+++ b/servers/gitlab_python/tests/test_server.py
@@ -335,4 +335,146 @@ def test_update_epic_error(mock_gitlab, mock_settings):
mock_gitlab.return_value.groups.get.side_effect = Exception("Group not found")
result = server.update_epic("bad-group", 101, title="Updated Epic")
assert "error" in result
+ assert "Group not found" in result["error"]
+
+@patch("mcp_server_gitlab_python.server.get_gitlab_settings", return_value=("https://gitlab.com", "dummy-token"))
+@patch("gitlab.Gitlab")
+def test_list_issue_comments_success(mock_gitlab, mock_settings):
+ server = GitLabPythonServer(working_directory="/tmp")
+ proj = MagicMock()
+ issue = MagicMock()
+ note1 = MagicMock()
+ note1.id = 1
+ note1.body = "First comment"
+ note1.author = {"id": 2, "name": "Alice"}
+ note1.created_at = "2024-01-01T00:00:00Z"
+ note1.updated_at = "2024-01-01T01:00:00Z"
+ note1.system = False
+ note2 = MagicMock()
+ note2.id = 2
+ note2.body = "Second comment"
+ note2.author = {"id": 3, "name": "Bob"}
+ note2.created_at = "2024-01-02T00:00:00Z"
+ note2.updated_at = "2024-01-02T01:00:00Z"
+ note2.system = True
+ issue.notes.list.return_value = [note1, note2]
+ proj.issues.get.return_value = issue
+ mock_gitlab.return_value.projects.get.return_value = proj
+ result = server.list_issue_comments("project/path", 42)
+ assert "comments" in result
+ assert len(result["comments"]) == 2
+ assert result["comments"][0]["body"] == "First comment"
+ assert result["comments"][1]["author"]["name"] == "Bob"
+ proj.issues.get.assert_called_once_with(42)
+ issue.notes.list.assert_called_once_with(all=True)
+
+@patch("mcp_server_gitlab_python.server.get_gitlab_settings", return_value=("https://gitlab.com", "dummy-token"))
+@patch("gitlab.Gitlab")
+def test_list_issue_comments_error(mock_gitlab, mock_settings):
+ server = GitLabPythonServer(working_directory="/tmp")
+ mock_gitlab.return_value.projects.get.side_effect = Exception("Project not found")
+ result = server.list_issue_comments("bad-project", 42)
+ assert "error" in result
+ assert "Project not found" in result["error"]
+
+@patch("mcp_server_gitlab_python.server.get_gitlab_settings", return_value=("https://gitlab.com", "dummy-token"))
+@patch("gitlab.Gitlab")
+def test_create_issue_comment_success(mock_gitlab, mock_settings):
+ server = GitLabPythonServer(working_directory="/tmp")
+ proj = MagicMock()
+ issue = MagicMock()
+ note = MagicMock()
+ note.id = 123
+ note.body = "A new comment"
+ note.author = {"id": 2, "name": "Alice"}
+ note.created_at = "2024-01-01T00:00:00Z"
+ note.updated_at = "2024-01-01T01:00:00Z"
+ note.system = False
+ issue.notes.create.return_value = note
+ proj.issues.get.return_value = issue
+ mock_gitlab.return_value.projects.get.return_value = proj
+ result = server.create_issue_comment("project/path", 42, "A new comment")
+ assert result["id"] == 123
+ assert result["body"] == "A new comment"
+ assert result["author"]["name"] == "Alice"
+ issue.notes.create.assert_called_once_with({"body": "A new comment"})
+
+@patch("mcp_server_gitlab_python.server.get_gitlab_settings", return_value=("https://gitlab.com", "dummy-token"))
+@patch("gitlab.Gitlab")
+def test_create_issue_comment_error(mock_gitlab, mock_settings):
+ server = GitLabPythonServer(working_directory="/tmp")
+ mock_gitlab.return_value.projects.get.side_effect = Exception("Project not found")
+ result = server.create_issue_comment("bad-project", 42, "A new comment")
+ assert "error" in result
+ assert "Project not found" in result["error"]
+
+@patch("mcp_server_gitlab_python.server.get_gitlab_settings", return_value=("https://gitlab.com", "dummy-token"))
+@patch("gitlab.Gitlab")
+def test_list_epic_comments_success(mock_gitlab, mock_settings):
+ server = GitLabPythonServer(working_directory="/tmp")
+ group = MagicMock()
+ epic = MagicMock()
+ note1 = MagicMock()
+ note1.id = 1
+ note1.body = "Epic comment 1"
+ note1.author = {"id": 2, "name": "Alice"}
+ note1.created_at = "2024-01-01T00:00:00Z"
+ note1.updated_at = "2024-01-01T01:00:00Z"
+ note1.system = False
+ note2 = MagicMock()
+ note2.id = 2
+ note2.body = "Epic comment 2"
+ note2.author = {"id": 3, "name": "Bob"}
+ note2.created_at = "2024-01-02T00:00:00Z"
+ note2.updated_at = "2024-01-02T01:00:00Z"
+ note2.system = True
+ epic.notes.list.return_value = [note1, note2]
+ group.epics.get.return_value = epic
+ mock_gitlab.return_value.groups.get.return_value = group
+ result = server.list_epic_comments("test-group", 101)
+ assert "comments" in result
+ assert len(result["comments"]) == 2
+ assert result["comments"][0]["body"] == "Epic comment 1"
+ assert result["comments"][1]["author"]["name"] == "Bob"
+ group.epics.get.assert_called_once_with(101)
+ epic.notes.list.assert_called_once_with(all=True)
+
+@patch("mcp_server_gitlab_python.server.get_gitlab_settings", return_value=("https://gitlab.com", "dummy-token"))
+@patch("gitlab.Gitlab")
+def test_list_epic_comments_error(mock_gitlab, mock_settings):
+ server = GitLabPythonServer(working_directory="/tmp")
+ mock_gitlab.return_value.groups.get.side_effect = Exception("Group not found")
+ result = server.list_epic_comments("bad-group", 101)
+ assert "error" in result
+ assert "Group not found" in result["error"]
+
+@patch("mcp_server_gitlab_python.server.get_gitlab_settings", return_value=("https://gitlab.com", "dummy-token"))
+@patch("gitlab.Gitlab")
+def test_create_epic_comment_success(mock_gitlab, mock_settings):
+ server = GitLabPythonServer(working_directory="/tmp")
+ group = MagicMock()
+ epic = MagicMock()
+ note = MagicMock()
+ note.id = 456
+ note.body = "A new epic comment"
+ note.author = {"id": 2, "name": "Alice"}
+ note.created_at = "2024-01-01T00:00:00Z"
+ note.updated_at = "2024-01-01T01:00:00Z"
+ note.system = False
+ epic.notes.create.return_value = note
+ group.epics.get.return_value = epic
+ mock_gitlab.return_value.groups.get.return_value = group
+ result = server.create_epic_comment("test-group", 101, "A new epic comment")
+ assert result["id"] == 456
+ assert result["body"] == "A new epic comment"
+ assert result["author"]["name"] == "Alice"
+ epic.notes.create.assert_called_once_with({"body": "A new epic comment"})
+
+@patch("mcp_server_gitlab_python.server.get_gitlab_settings", return_value=("https://gitlab.com", "dummy-token"))
+@patch("gitlab.Gitlab")
+def test_create_epic_comment_error(mock_gitlab, mock_settings):
+ server = GitLabPythonServer(working_directory="/tmp")
+ mock_gitlab.return_value.groups.get.side_effect = Exception("Group not found")
+ result = server.create_epic_comment("bad-group", 101, "A new epic comment")
+ assert "error" in result
assert "Group not found" in result["error"] \ No newline at end of file