Skip to main content
Glama
mattyatea

Git Conflict MCP

by mattyatea

list_conflicts

Identify files with Git merge conflicts, display conflict types, and provide resolution suggestions to streamline conflict resolution workflows.

Instructions

List files with git conflicts including conflict types. Returns a map of ID to file info with conflict type and suggested resolution. (Rate limit: 2 calls per minute). IMPORTANT: You must run init_project before using this tool.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
pageNoPage number (1-based). Default is 1.
extensionNoFilter by file extension (e.g. 'ts', '.ts').
pathNoFilter by file path (substring match).
showTypesNoShow detailed conflict types and resolution suggestions. Default is true.

Implementation Reference

  • The core handler function for the 'list_conflicts' tool. It handles rate limiting, fetches conflicted files, applies pagination and filters, generates IDs and suggestions, excludes pending resolves, and returns a JSON map of ID to file info.
    async ({ page, extension, path, showTypes }) => {
        if (!rateLimiter.check("list_conflicts", 2, 60 * 1000)) {
            return { content: [{ type: "text", text: "Please fix the conflicted files that have already been provided." }], isError: true };
        }
    
        const pageNum = page || 1;
        const pageSize = 20;
        const includeTypes = showTypes !== false; // Default to true
    
        try {
            const allConflicts = await getConflictedFilesWithStatus();
    
            // Assign consistent IDs based on file path
            const conflictsWithIds = allConflicts.map((conflict) => ({
                ...conflict,
                id: generateId(conflict.file)
            }));
    
            // Fetch pending resolves (local or external)
            const pendingResolves = await getPendingResolves();
            const pendingPaths = new Set(pendingResolves.map(p => p.filePath));
    
            // Filter out conflicts that are already in the pending list
            let filteredConflicts = conflictsWithIds.filter(c => !pendingPaths.has(c.file));
    
            // Apply filters
            if (extension) {
                const ext = extension.startsWith('.') ? extension : `.${extension}`;
                filteredConflicts = filteredConflicts.filter(c => c.file.endsWith(ext));
            }
    
            if (path) {
                filteredConflicts = filteredConflicts.filter(c => c.file.includes(path));
            }
    
            const start = (pageNum - 1) * pageSize;
            const end = start + pageSize;
            const slice = filteredConflicts.slice(start, end);
    
            const result: Record<string, any> = {};
    
            if (includeTypes) {
                // Include detailed information with conflict types
                slice.forEach((item) => {
                    const suggestion = getResolutionSuggestion(item.status);
                    const rejectionReason = state.getRejection(item.file);
    
                    result[item.id] = {
                        file: item.file,
                        conflictType: item.conflictType,
                        fileSize: item.fileSize !== undefined ? `${item.fileSize} bytes` : "N/A (file deleted)",
                        suggestion: suggestion,
                        ...(rejectionReason ? { previousRejectionReason: rejectionReason } : {})
                    };
                });
            } else {
                // Simple format: just ID to file path
                slice.forEach((item) => {
                    result[item.id] = item.file;
                });
            }
    
            if (filteredConflicts.length > end) {
                result["isMoreConflict"] = "true";
            }
    
            return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
        } catch (e: any) {
            return { content: [{ type: "text", text: `Error: ${e.message}` }], isError: true };
        }
    }
  • Key helper function that retrieves detailed conflict information (status, type, size) by running 'git status --porcelain' and parsing the output. Used by the handler.
    export async function getConflictedFilesWithStatus(): Promise<ConflictInfo[]> {
      try {
        const output = await runGit(["status", "--porcelain"]);
        const lines = output.split("\n").map(s => s.trim()).filter(s => s.length > 0);
    
        const conflicts: ConflictInfo[] = [];
        const projectPath = state.getProjectPath();
        if (!projectPath) {
          throw new Error("Project not initialized. Call init_project first.");
        }
    
        for (const line of lines) {
          // Format: XY filename
          // X = index status, Y = working tree status
          const status = line.substring(0, 2);
          const file = line.substring(3);
    
          // Only include unmerged files (conflicts)
          if (["DD", "AU", "UD", "UA", "DU", "AA", "UU"].includes(status)) {
            let conflictType: string;
            switch (status) {
              case "DD":
                conflictType = "both deleted";
                break;
              case "AU":
                conflictType = "added by us";
                break;
              case "UD":
                conflictType = "deleted by them";
                break;
              case "UA":
                conflictType = "added by them";
                break;
              case "DU":
                conflictType = "deleted by us";
                break;
              case "AA":
                conflictType = "both added";
                break;
              case "UU":
                conflictType = "both modified";
                break;
              default:
                conflictType = "unknown";
            }
    
            // Get file size if file exists
            let fileSize: number | undefined;
            try {
              const filePath = join(projectPath, file);
              const stats = await stat(filePath);
              fileSize = stats.size;
            } catch (e) {
              // File doesn't exist (deleted), fileSize remains undefined
              fileSize = undefined;
            }
    
            conflicts.push({ file, status, conflictType, fileSize });
          }
        }
    
        return conflicts.sort((a, b) => a.file.localeCompare(b.file));
      } catch (e) {
        throw e;
      }
    }
  • Zod input schema defining optional parameters for pagination, filtering by extension/path, and showing types/suggestions.
    inputSchema: z.object({
        page: z.number().optional().describe("Page number (1-based). Default is 1."),
        extension: z.string().optional().describe("Filter by file extension (e.g. 'ts', '.ts')."),
        path: z.string().optional().describe("Filter by file path (substring match)."),
        showTypes: z.boolean().optional().describe("Show detailed conflict types and resolution suggestions. Default is true."),
    }),
  • Function that registers the 'list_conflicts' tool with the MCP server, including name, description, schema, and handler.
    export function registerListConflicts(server: McpServer) {
        server.registerTool(
            "list_conflicts",
            {
                description: "List files with git conflicts including conflict types. Returns a map of ID to file info with conflict type and suggested resolution. (Rate limit: 2 calls per minute). IMPORTANT: You must run init_project before using this tool.",
                inputSchema: z.object({
                    page: z.number().optional().describe("Page number (1-based). Default is 1."),
                    extension: z.string().optional().describe("Filter by file extension (e.g. 'ts', '.ts')."),
                    path: z.string().optional().describe("Filter by file path (substring match)."),
                    showTypes: z.boolean().optional().describe("Show detailed conflict types and resolution suggestions. Default is true."),
                }),
            },
            async ({ page, extension, path, showTypes }) => {
                if (!rateLimiter.check("list_conflicts", 2, 60 * 1000)) {
                    return { content: [{ type: "text", text: "Please fix the conflicted files that have already been provided." }], isError: true };
                }
    
                const pageNum = page || 1;
                const pageSize = 20;
                const includeTypes = showTypes !== false; // Default to true
    
                try {
                    const allConflicts = await getConflictedFilesWithStatus();
    
                    // Assign consistent IDs based on file path
                    const conflictsWithIds = allConflicts.map((conflict) => ({
                        ...conflict,
                        id: generateId(conflict.file)
                    }));
    
                    // Fetch pending resolves (local or external)
                    const pendingResolves = await getPendingResolves();
                    const pendingPaths = new Set(pendingResolves.map(p => p.filePath));
    
                    // Filter out conflicts that are already in the pending list
                    let filteredConflicts = conflictsWithIds.filter(c => !pendingPaths.has(c.file));
    
                    // Apply filters
                    if (extension) {
                        const ext = extension.startsWith('.') ? extension : `.${extension}`;
                        filteredConflicts = filteredConflicts.filter(c => c.file.endsWith(ext));
                    }
    
                    if (path) {
                        filteredConflicts = filteredConflicts.filter(c => c.file.includes(path));
                    }
    
                    const start = (pageNum - 1) * pageSize;
                    const end = start + pageSize;
                    const slice = filteredConflicts.slice(start, end);
    
                    const result: Record<string, any> = {};
    
                    if (includeTypes) {
                        // Include detailed information with conflict types
                        slice.forEach((item) => {
                            const suggestion = getResolutionSuggestion(item.status);
                            const rejectionReason = state.getRejection(item.file);
    
                            result[item.id] = {
                                file: item.file,
                                conflictType: item.conflictType,
                                fileSize: item.fileSize !== undefined ? `${item.fileSize} bytes` : "N/A (file deleted)",
                                suggestion: suggestion,
                                ...(rejectionReason ? { previousRejectionReason: rejectionReason } : {})
                            };
                        });
                    } else {
                        // Simple format: just ID to file path
                        slice.forEach((item) => {
                            result[item.id] = item.file;
                        });
                    }
    
                    if (filteredConflicts.length > end) {
                        result["isMoreConflict"] = "true";
                    }
    
                    return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
                } catch (e: any) {
                    return { content: [{ type: "text", text: `Error: ${e.message}` }], isError: true };
                }
            }
        );
    }
  • Invocation of registerListConflicts within the main registerTools function to include the tool in the server.
    registerListConflicts(server);
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries full burden. It discloses key behavioral traits: the return format ('map of ID to file info with conflict type and suggested resolution'), a rate limit ('2 calls per minute'), and a prerequisite. However, it doesn't mention potential side effects (e.g., if this is read-only or has any impact) or error handling, leaving some gaps.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

It's front-loaded with the core purpose, followed by return details, rate limit, and prerequisite. Each sentence adds value, but the structure could be slightly improved by separating the prerequisite into its own sentence for clarity. Overall, it's efficient with minimal waste.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given no annotations and no output schema, the description does well by explaining the return format and adding a rate limit and prerequisite. However, for a tool with 4 parameters and no output schema, it could benefit from more detail on error cases or example usage to be fully complete.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the schema already documents all 4 parameters. The description adds no parameter-specific information beyond what's in the schema, such as how 'extension' or 'path' filters interact with conflict listing. Baseline is 3 when schema does the heavy lifting.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the verb 'List' and the resource 'files with git conflicts including conflict types', specifying what the tool does. It distinguishes from siblings like 'read_conflict' (likely reads a specific conflict) and 'resolve_conflict' (resolves conflicts) by focusing on listing all conflicts with types.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It explicitly states 'IMPORTANT: You must run init_project before using this tool', providing a clear prerequisite. While it doesn't mention when to use alternatives like 'post_resolve' or 'read_conflict', the prerequisite guidance is strong and specific for this tool's context.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

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/mattyatea/git-conflict-mcp'

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