Skip to main content
Glama

get_commit_discussions

Retrieve discussions and comments on commits within a GitLab merge request to review feedback and track changes.

Instructions

Get discussions and comments on commits within a specific merge request

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
merge_request_iidYesInternal ID of the merge request

Implementation Reference

  • The main handler function that implements the tool logic: fetches merge request commits and discussions, matches discussions to commits based on head_sha, and formats a detailed markdown report with summaries and per-commit details.
    async def get_commit_discussions(gitlab_url, project_id, access_token, args): """Get discussions/comments on commits within a merge request""" logging.info(f"get_commit_discussions called with args: {args}") mr_iid = args["merge_request_iid"] try: commits_result = await get_merge_request_commits(gitlab_url, project_id, access_token, mr_iid) commits_status, commits_data, commits_error = commits_result if commits_status != 200: logging.error(f"Error fetching merge request commits: " f"{commits_status} - {commits_error}") raise Exception(f"Error fetching merge request commits: {commits_error}") if not commits_data: return [TextContent(type="text", text="No commits found in this merge request.")] logging.info(f"Getting ALL MR discussions for MR #{mr_iid}...") discussions_result = await get_merge_request_discussions_paginated(gitlab_url, project_id, access_token, mr_iid) discussions_status, discussions_data, discussions_error = discussions_result if discussions_status != 200: logging.error(f"Error fetching MR discussions: " f"{discussions_status} - {discussions_error}") discussions_data = [] commit_map = {commit["id"]: commit for commit in commits_data} commits_with_discussions = {} total_discussions = 0 for discussion in discussions_data: notes = discussion.get("notes", []) for note in notes: position = note.get("position") if position and position.get("head_sha"): commit_sha = position["head_sha"] if commit_sha in commit_map: if commit_sha not in commits_with_discussions: commits_with_discussions[commit_sha] = {"commit": commit_map[commit_sha], "discussions": []} commits_with_discussions[commit_sha]["discussions"].append( {"discussion_id": discussion.get("id"), "note": note, "position": position} ) total_discussions += 1 if not commits_with_discussions: summary_text = ( f"## Commit Discussions for MR #{mr_iid}\n\n" f"**Summary:**\n" f"- Total commits: {len(commits_data)}\n" f"- Commits with discussions: 0\n" f"- Total discussions: 0\n\n" f"No line-level discussions found on any commits in this " f"merge request. Found {len(discussions_data)} total MR discussions." ) return [TextContent(type="text", text=summary_text)] response_text = ( f"## Commit Discussions for MR #{mr_iid}\n\n" f"**Summary:**\n" f"- Total commits: {len(commits_data)}\n" f"- Commits with discussions: {len(commits_with_discussions)}\n" f"- Total line-level discussions: {total_discussions}\n" f"- Total MR discussions: {len(discussions_data)}\n\n" ) for _commit_sha, item in commits_with_discussions.items(): commit = item["commit"] discussions = item["discussions"] response_text += f"### πŸ“ Commit: {commit['short_id']}\n" response_text += f"**Title:** {commit['title']}\n" response_text += f"**Author:** {commit['author_name']}\n" response_text += f"**Date:** {format_date(commit['committed_date'])}\n" response_text += f"**SHA:** `{commit['id']}`\n\n" for discussion_item in discussions: discussion_id = discussion_item["discussion_id"] note = discussion_item["note"] position = discussion_item["position"] author_name = note["author"]["name"] response_text += f"**πŸ’¬ Comment by {author_name}:**\n" response_text += f"{note['body']}\n" if position.get("new_path"): line_info = position.get("new_line", "N/A") response_text += f"*On file: {position['new_path']} " f"(line {line_info})*\n" created_at = format_date(note["created_at"]) response_text += f"*Posted: {created_at}*\n" response_text += f"*Discussion ID: {discussion_id}*\n\n" response_text += "---\n\n" return [TextContent(type="text", text=response_text)] except Exception as e: logging.error(f"Error in get_commit_discussions: {str(e)}") return [TextContent(type="text", text=f"Error retrieving commit discussions: {str(e)}")]
  • The tool schema definition including input schema requiring 'merge_request_iid' as input.
    Tool( name="get_commit_discussions", description=("Get discussions and comments on commits within a " "specific merge request"), inputSchema={ "type": "object", "properties": { "merge_request_iid": { "type": "integer", "minimum": 1, "description": ("Internal ID of the merge request"), } }, "required": ["merge_request_iid"], "additionalProperties": False, }, ),
  • main.py:344-347 (registration)
    The dispatch/registration in the call_tool handler that routes calls to the get_commit_discussions function.
    elif name == "get_commit_discussions": return await get_commit_discussions( self.config["gitlab_url"], self.config["project_id"], self.config["access_token"], arguments )
  • main.py:41-277 (registration)
    The list_tools decorator registration where the tool is listed and described for the MCP server.
    @self.server.list_tools() async def list_tools() -> List[Tool]: logging.info("list_tools called") tools = [ Tool( name="list_merge_requests", description="List merge requests for the GitLab project", inputSchema={ "type": "object", "properties": { "state": { "type": "string", "enum": ["opened", "closed", "merged", "all"], "default": "opened", "description": "Filter by merge request state", }, "target_branch": {"type": "string", "description": ("Filter by target branch (optional)")}, "limit": { "type": "integer", "default": 10, "minimum": 1, "maximum": 100, "description": "Maximum number of results", }, }, "additionalProperties": False, }, ), Tool( name="get_merge_request_reviews", description=("Get reviews and discussions for a specific " "merge request"), inputSchema={ "type": "object", "properties": { "merge_request_iid": { "type": "integer", "minimum": 1, "description": ("Internal ID of the merge request"), } }, "required": ["merge_request_iid"], "additionalProperties": False, }, ), Tool( name="get_merge_request_details", description=("Get detailed information about a specific " "merge request"), inputSchema={ "type": "object", "properties": { "merge_request_iid": { "type": "integer", "minimum": 1, "description": ("Internal ID of the merge request"), } }, "required": ["merge_request_iid"], "additionalProperties": False, }, ), Tool( name="get_merge_request_pipeline", description=( "Get the last pipeline data for a specific merge " "request, including all jobs and their statuses. " "Returns job IDs that can be used with get_job_log " "to fetch detailed output." ), inputSchema={ "type": "object", "properties": { "merge_request_iid": { "type": "integer", "minimum": 1, "description": ("Internal ID of the merge request"), } }, "required": ["merge_request_iid"], "additionalProperties": False, }, ), Tool( name="get_merge_request_test_report", description=( "Get structured test report for a merge request " "with specific test failures, error messages, and " "stack traces. Shows the same test data visible on " "the GitLab MR page. Best for debugging test failures." ), inputSchema={ "type": "object", "properties": { "merge_request_iid": { "type": "integer", "minimum": 1, "description": ("Internal ID of the merge request"), } }, "required": ["merge_request_iid"], "additionalProperties": False, }, ), Tool( name="get_pipeline_test_summary", description=( "Get test summary for a merge request - a " "lightweight overview showing pass/fail counts " "per test suite. Faster than full test report. " "Great for quick status checks." ), inputSchema={ "type": "object", "properties": { "merge_request_iid": { "type": "integer", "minimum": 1, "description": ("Internal ID of the merge request"), } }, "required": ["merge_request_iid"], "additionalProperties": False, }, ), Tool( name="get_job_log", description=( "Get the trace/log output for a specific pipeline " "job. Perfect for debugging failed tests and " "understanding CI/CD failures." ), inputSchema={ "type": "object", "properties": { "job_id": { "type": "integer", "minimum": 1, "description": ("ID of the pipeline job (obtained from " "get_merge_request_pipeline)"), } }, "required": ["job_id"], "additionalProperties": False, }, ), Tool( name="get_branch_merge_requests", description=("Get all merge requests for a specific branch"), inputSchema={ "type": "object", "properties": {"branch_name": {"type": "string", "description": "Name of the branch"}}, "required": ["branch_name"], "additionalProperties": False, }, ), Tool( name="reply_to_review_comment", description=("Reply to a specific discussion thread in a " "merge request review"), inputSchema={ "type": "object", "properties": { "merge_request_iid": { "type": "integer", "minimum": 1, "description": ("Internal ID of the merge request"), }, "discussion_id": { "type": "string", "description": ("ID of the discussion thread to reply to"), }, "body": {"type": "string", "description": "Content of the reply comment"}, }, "required": ["merge_request_iid", "discussion_id", "body"], "additionalProperties": False, }, ), Tool( name="create_review_comment", description=("Create a new discussion thread in a " "merge request review"), inputSchema={ "type": "object", "properties": { "merge_request_iid": { "type": "integer", "minimum": 1, "description": ("Internal ID of the merge request"), }, "body": {"type": "string", "description": ("Content of the new discussion comment")}, }, "required": ["merge_request_iid", "body"], "additionalProperties": False, }, ), Tool( name="resolve_review_discussion", description=("Resolve or unresolve a discussion thread in a " "merge request review"), inputSchema={ "type": "object", "properties": { "merge_request_iid": { "type": "integer", "minimum": 1, "description": ("Internal ID of the merge request"), }, "discussion_id": { "type": "string", "description": ("ID of the discussion thread to " "resolve/unresolve"), }, "resolved": { "type": "boolean", "default": True, "description": ("Whether to resolve (true) or unresolve " "(false) the discussion"), }, }, "required": ["merge_request_iid", "discussion_id"], "additionalProperties": False, }, ), Tool( name="get_commit_discussions", description=("Get discussions and comments on commits within a " "specific merge request"), inputSchema={ "type": "object", "properties": { "merge_request_iid": { "type": "integer", "minimum": 1, "description": ("Internal ID of the merge request"), } }, "required": ["merge_request_iid"], "additionalProperties": False, }, ), ] tool_names = [t.name for t in tools] logging.info(f"Returning {len(tools)} tools: {tool_names}") return tools
  • Helper function used by the handler to fetch all commits in the merge request with pagination.
    async def get_merge_request_commits(gitlab_url, project_id, access_token, mr_iid): """Get all commits in a merge request (handles pagination)""" base_url = f"{gitlab_url}/api/v4/projects/{project_id}/" f"merge_requests/{mr_iid}/commits" headers = _headers(access_token) all_commits = [] page = 1 per_page = 100 # Maximum allowed per page async with aiohttp.ClientSession() as session: while True: params = {"page": page, "per_page": per_page} async with session.get(base_url, headers=headers, params=params) as response: if response.status != 200: return (response.status, await response.json(), await response.text()) page_data = await response.json() if not page_data: # No more results break all_commits.extend(page_data) # If we got fewer results than per_page, we're done if len(page_data) < per_page: break page += 1 return (200, all_commits, "Success")

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/amirsina-mandegari/gitlab-mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server