Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 51 additions & 0 deletions src/filesystem/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(
{
Expand Down Expand Up @@ -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<typeof CompareFilesArgsSchema>) => {
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);
Expand Down
Loading