Skip to main content
Glama

diff_files

Compare two files to generate a unified diff showing differences. Use the output to apply patches or verify file changes.

Instructions

Generate a unified diff between two files. Output feeds directly into apply_patch. isIdentical=true means files match — no patch needed.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
originalYesPath to original file
modifiedYesPath to modified file
contextNoLines of context to include in the diff
ignoreWhitespaceNoIgnore leading/trailing whitespace when comparing lines
stripTrailingCrNoStrip trailing carriage returns before diffing

Implementation Reference

  • The 'handleDiffFiles' function executes the core logic for the 'diff_files' tool, including reading files and computing the diff using the 'diff' library.
    async function handleDiffFiles(
      args: z.infer<typeof DiffFilesInputSchema>,
      signal?: AbortSignal,
      resourceStore?: ToolRegistrationOptions['resourceStore']
    ): Promise<ToolResponse<z.infer<typeof DiffFilesOutputSchema>>> {
      const maxFileSize = MAX_TEXT_FILE_SIZE;
      const [originalPath, modifiedPath] = await Promise.all([
        validateExistingPath(args.original, signal),
        validateExistingPath(args.modified, signal),
      ]);
    
      const [originalStats, modifiedStats] = await Promise.all([
        withAbort(fs.stat(originalPath), signal),
        withAbort(fs.stat(modifiedPath), signal),
      ]);
    
      assertDiffFileSizeWithinLimit(originalPath, originalStats.size, maxFileSize);
      assertDiffFileSizeWithinLimit(modifiedPath, modifiedStats.size, maxFileSize);
    
      const [originalContent, modifiedContent] = await Promise.all([
        fs.readFile(originalPath, { encoding: 'utf-8', signal }),
        fs.readFile(modifiedPath, { encoding: 'utf-8', signal }),
      ]);
    
      const patchObj = await new Promise<StructuredPatch | undefined>((resolve) => {
        structuredPatch(
          path.basename(originalPath),
          path.basename(modifiedPath),
          originalContent,
          modifiedContent,
          undefined,
          undefined,
          {
            ...(args.context !== undefined ? { context: args.context } : {}),
            ignoreWhitespace: args.ignoreWhitespace,
            stripTrailingCr: args.stripTrailingCr,
            timeout: 10000,
            callback: (res) => {
              resolve(res);
            },
          }
        );
      });
    
      if (!patchObj) {
        throw new McpError(
          ErrorCode.E_TIMEOUT,
          `Diff computation timed out or failed due to complexity.`,
          originalPath
        );
      }
    
      const isIdentical = patchObj.hunks.length === 0;
      const diffText = isIdentical ? '' : formatPatch(patchObj);
      const stats = isIdentical ? undefined : computeDiffStats(patchObj.hunks);
    
      const externalized = maybeExternalizeTextContent(resourceStore, diffText, {
        name: 'diff:patch',
        mimeType: 'text/x-diff',
      });
    
      if (!externalized) {
        return buildToolResponse(isIdentical ? 'No differences' : diffText, {
          ok: true,
          diff: diffText,
          isIdentical,
          ...(stats ?? {}),
        });
      }
    
      const { preview, entry } = externalized;
      return buildToolResponse(
        preview,
        {
          ok: true,
          diff: preview,
          isIdentical,
          ...(stats ?? {}),
          truncated: true,
          resourceUri: entry.uri,
        },
        [
          buildResourceLink({
            uri: entry.uri,
            name: entry.name,
            mimeType: entry.mimeType,
            description: 'Full diff content',
            expiresAt: entry.expiresAt,
          }),
        ]
      );
    }
  • The 'registerDiffFilesTool' function registers the 'diff_files' tool with the McpServer, wrapping the handler with validation and telemetry/progress reporting.
    export function registerDiffFilesTool(
      server: McpServer,
      options: ToolRegistrationOptions = {}
    ): void {
      const handler = (
        args: z.infer<typeof DiffFilesInputSchema>,
        extra: ToolExtra
      ): Promise<ToolResult<z.infer<typeof DiffFilesOutputSchema>>> =>
        executeToolWithDiagnostics({
          toolName: 'diff_files',
          extra,
          outputSchema: DiffFilesOutputSchema,
          timedSignal: {},
          context: { path: args.original },
          run: (signal) => handleDiffFiles(args, signal, options.resourceStore),
          onError: (error) =>
            buildToolErrorResponse(error, ErrorCode.E_UNKNOWN, args.original),
        });
    
      const wrappedHandler = wrapToolHandler(handler, {
        guard: options.isInitialized,
        progressMessage: (args) => {
          const n1 = path.basename(args.original);
          const n2 = path.basename(args.modified);
          return `🕮 diff: ${n1} ⟷ ${n2}`;
        },
        completionMessage: (args, result) => {
          const n1 = path.basename(args.original);
          const n2 = path.basename(args.modified);
          if (result.isError) return `🕮 diff: ${n1} ⟷ ${n2} • failed`;
          const sc = result.structuredContent;
          if (sc.isIdentical) return `🕮 diff: ${n1} ⟷ ${n2} • identical`;
          const added = sc.linesAdded ?? 0;
          const removed = sc.linesRemoved ?? 0;
          if (added > 0 || removed > 0)
            return `🕮 diff: ${n1} ⟷ ${n2} • +${added} -${removed}`;
          return `🕮 diff: ${n1} ⟷ ${n2}`;
        },
      });
    
      const validatedHandler = withValidatedArgs(
        DiffFilesInputSchema,
        wrappedHandler
      );
    
      if (
        registerToolTaskIfAvailable(
          server,
          'diff_files',
          DIFF_FILES_TOOL,
          validatedHandler,
          options.iconInfo,
          options.isInitialized
        )
      )
        return;
      server.registerTool(
        'diff_files',
        withDefaultIcons({ ...DIFF_FILES_TOOL }, options.iconInfo),
        validatedHandler
      );
    }
  • The 'DiffFilesInputSchema' defines the expected input parameters for the 'diff_files' tool, requiring 'original' and 'modified' file paths.
    export const DiffFilesInputSchema = z.strictObject({
      original: RequiredPathSchema.describe('Path to original file'),
      modified: RequiredPathSchema.describe('Path to modified file'),
      context: z
        .int({ error: 'Must be integer' })
        .min(0, 'Min: 0')
        .max(10000, 'Max: 10,000')
        .optional()
        .describe('Lines of context to include in the diff'),
      ignoreWhitespace: z
        .boolean()
        .optional()
        .default(false)
        .describe('Ignore leading/trailing whitespace when comparing lines'),
      stripTrailingCr: z
        .boolean()
        .optional()
        .default(false)
        .describe('Strip trailing carriage returns before diffing'),
    });

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/j0hanz/filesystem-mcp'

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