Skip to main content
Glama
ffpy

GitLab MCP Code Review

by ffpy

fetch_merge_request

Retrieve a GitLab merge request and its contents to analyze code changes for review. Use after checking team standards to ensure compliance with guidelines.

Instructions

Fetch a GitLab merge request and its contents. IMPORTANT: You MUST call fetch_code_review_rules BEFORE using this tool to understand the team's code review standards and guidelines. Args: project_id: The GitLab project ID or URL-encoded path merge_request_iid: The merge request IID (project-specific ID) Returns: XML string containing the merge request information

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
project_idYes
merge_request_iidYes

Implementation Reference

  • The handler function decorated with @mcp.tool() that implements the core logic for fetching and processing GitLab merge request data, filtering changes, collecting commits and discussions, and formatting the output as XML.
    @mcp.tool() def fetch_merge_request(ctx: Context, project_id: str, merge_request_iid: str): """ Fetch a GitLab merge request and its contents. IMPORTANT: You MUST call fetch_code_review_rules BEFORE using this tool to understand the team's code review standards and guidelines. Args: project_id: The GitLab project ID or URL-encoded path merge_request_iid: The merge request IID (project-specific ID) Returns: XML string containing the merge request information """ gl = ctx.request_context.lifespan_context project = gl.projects.get(project_id) mr = project.mergerequests.get(merge_request_iid) # 精简 merge_request 信息 mr_data = mr.asdict() slim_mr = { "id": mr_data.get("id"), "iid": mr_data.get("iid"), "project_id": mr_data.get("project_id"), "title": mr_data.get("title"), "description": mr_data.get("description"), "state": mr_data.get("state"), "author": mr_data.get("author", {}).get("name"), "source_branch": mr_data.get("source_branch"), "target_branch": mr_data.get("target_branch"), } # 获取并过滤 changes original_changes_data = mr.changes() all_changes = original_changes_data.get("changes", []) exclude_patterns = config.get("exclude_patterns", []) filtered_changes_list = [] for change in all_changes: file_path = change.get("new_path") if not is_path_excluded(file_path, exclude_patterns): slim_change = { "new_path": change.get("new_path"), "old_path": change.get("old_path"), "new_file": change.get("new_file"), "renamed_file": change.get("renamed_file"), "deleted_file": change.get("deleted_file"), "diff": change.get("diff") } filtered_changes_list.append(slim_change) # 创建一个只包含必要字段的精简版 changes 对象 slim_changes_obj = { "diff_refs": original_changes_data.get("diff_refs"), "changes": filtered_changes_list } # 精简 commits commits = [ { "id": c.id, "short_id": c.short_id, "title": c.title, "author_name": c.author_name, } for c in mr.commits(all=True) ] def slim_note(note): if not isinstance(note, dict): note = note.asdict() author = note.get("author", {}) return { "id": note.get("id"), "type": note.get("type"), "body": note.get("body"), "system": note.get("system"), "author": author.get("name"), "position": note.get("position", {}), } # 精简 discussions 和其下的 notes all_discussions = mr.discussions.list(all=True) discussions = [] for d in all_discussions: # d.attributes['notes'] 包含了该 discussion 下的所有 note 信息 slim_notes_list = [slim_note(n) for n in d.attributes.get('notes', [])] discussions.append({ "id": d.id, "individual_note": d.individual_note, "notes": slim_notes_list }) # 构建最终的数据结构 result_data = { "merge_request": slim_mr, "changes": slim_changes_obj, "commits": commits, "discussions": discussions, } # 转换为XML并返回 xml_parts = ['<?xml version="1.0" encoding="utf-8"?>\n<merge_request_data>\n'] for key, value in result_data.items(): xml_parts.append(dict_to_xml_string(value, key, 1)) xml_parts.append('</merge_request_data>\n') return "".join(xml_parts)
  • Helper function used by fetch_merge_request to recursively convert the merge request data dictionary into an XML-formatted string.
    def dict_to_xml_string(data: Any, tag: str = "item", indent: int = 0) -> str: """ Convert a dictionary or list to XML string format without escaping any characters. This output is intended for AI consumption, not for XML parser. Args: data: The data to convert (dict, list, or primitive type) tag: The tag name for the current element indent: Current indentation level Returns: The XML string """ indent_str = " " * indent result = [] if isinstance(data, dict): result.append(f"{indent_str}<{tag}>\n") for key, value in data.items(): if value is not None: result.append(dict_to_xml_string(value, str(key), indent + 1)) result.append(f"{indent_str}</{tag}>\n") elif isinstance(data, list): result.append(f"{indent_str}<{tag}>\n") for item in data: result.append(dict_to_xml_string(item, "item", indent + 1)) result.append(f"{indent_str}</{tag}>\n") else: # Leaf node with text content - no escaping if data is None: text = "" elif isinstance(data, bool): text = "true" if data else "false" else: text = str(data) result.append(f"{indent_str}<{tag}>{text}</{tag}>\n") return "".join(result)
  • Helper function used in fetch_merge_request to filter out excluded file paths from the merge request changes based on config patterns.
    def is_path_excluded(file_path: str, patterns: List[str]) -> bool: """Check if a file path matches any of the exclusion patterns.""" for pattern in patterns: if pattern.endswith('/'): if file_path.startswith(pattern) or f"/{pattern}" in file_path: return True elif fnmatch.fnmatch(file_path, pattern): return True return False

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/ffpy/gitlab-mcp-code-review'

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