From 9473f0e612c7f7b775df397c02b78f5b88042fab Mon Sep 17 00:00:00 2001 From: factspark23-hash Date: Thu, 23 Apr 2026 14:51:42 +0800 Subject: [PATCH] feat(filesystem): add compare_files tool for file diff comparison New tool that compares two files and returns a unified diff output. - Shows exactly what changed between two files - Configurable context lines (default: 3) - Reports when files are identical - Uses existing createUnifiedDiff utility - Both files must be within allowed directories Useful for code review, change verification, and understanding differences between file versions. --- src/filesystem/index.ts | 51 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/src/filesystem/index.ts b/src/filesystem/index.ts index 7b67e63e58..901fe5278d 100644 --- a/src/filesystem/index.ts +++ b/src/filesystem/index.ts @@ -159,6 +159,12 @@ const GetFileInfoArgsSchema = z.object({ path: z.string(), }); +const CompareFilesArgsSchema = z.object({ + file1: z.string().describe('Path to the first file'), + file2: z.string().describe('Path to the second file'), + contextLines: z.number().int().min(0).default(3).describe('Number of surrounding lines to show for each change (default: 3)') +}); + // Server setup const server = new McpServer( { @@ -702,6 +708,51 @@ server.registerTool( } ); +server.registerTool( + "compare_files", + { + title: "Compare Files", + description: + "Compare two files and show the differences in a unified diff format. " + + "Useful for reviewing changes, verifying edits, or understanding differences " + + "between versions. Returns a human-readable diff with configurable context lines. " + + "Both files must be within allowed directories.", + inputSchema: { + file1: z.string().describe("Path to the first file"), + file2: z.string().describe("Path to the second file"), + contextLines: z.number().int().min(0).default(3).describe("Number of surrounding lines to show for each change (default: 3)") + }, + outputSchema: { content: z.string() }, + annotations: { readOnlyHint: true } + }, + async (args: z.infer) => { + const validPath1 = await validatePath(args.file1); + const validPath2 = await validatePath(args.file2); + + const [content1, content2] = await Promise.all([ + readFileContent(validPath1), + readFileContent(validPath2) + ]); + + const { createUnifiedDiff } = await import('./lib.js'); + const diff = createUnifiedDiff(content1, content2, `${args.file1} vs ${args.file2}`); + + if (!diff || diff.split('\n').length <= 4) { + const text = `Files are identical:\n ${args.file1}\n ${args.file2}`; + return { + content: [{ type: "text" as const, text }], + structuredContent: { content: text } + }; + } + + const text = diff; + return { + content: [{ type: "text" as const, text }], + structuredContent: { content: text } + }; + } +); + // Updates allowed directories based on MCP client roots async function updateAllowedDirectoriesFromRoots(requestedRoots: Root[]) { const validatedRootDirs = await getValidRootDirectories(requestedRoots);