Skip to main content
Glama

apply_patch

Apply unified diff patches to modify files, supporting validation, fuzzy matching, and line ending conversion for reliable file updates.

Instructions

Apply a unified diff patch to one or more files. Single-file: throws on failure. Multi-file: best-effort per file with results[]. Workflow: diff_filesapply_patch(dryRun:true)apply_patch. On failure, regenerate the patch from current file content.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
pathYesPath to file to patch
patchYesUnified diff with @@ hunk headers. Generate with `diff_files`.
fuzzFactorNoMaximum fuzzy mismatches per hunk
autoConvertLineEndingsNoAuto-convert line endings to match target file
dryRunNoValidate patch without writing. Check `applied` before committing.

Implementation Reference

  • The core logic function 'handleApplyPatch' that parses the patch content and applies it using the 'diff' library, handling both single and multi-file scenarios.
    async function handleApplyPatch(
      args: z.infer<typeof ApplyPatchInputSchema>,
      signal?: AbortSignal
    ): Promise<ToolResponse<z.infer<typeof ApplyPatchOutputSchema>>> {
      if (!args.patch.trim()) {
        throw new McpError(ErrorCode.E_INVALID_INPUT, 'Patch content is empty.');
      }
    
      const fuzzFactor = args.fuzzFactor ?? 0;
      const parsed = parsePatch(args.patch);
    
      const hasHunks = parsed.some((p) => p.hunks.length > 0);
      if (!hasHunks) {
        throw new McpError(
          ErrorCode.E_INVALID_INPUT,
          'Patch must include unified hunk headers (e.g., @@ -1,2 +1,2 @@).'
        );
      }
    
      const options: PatchOptions = {
        dryRun: args.dryRun,
        fuzzFactor,
        autoConvertLineEndings: args.autoConvertLineEndings,
      };
      if (parsed.length > 1) {
        return processMultiFilePatch(args.path, parsed, options, signal);
      }
      const diff = parsed[0];
      if (!diff) {
        throw new McpError(ErrorCode.E_INVALID_INPUT, 'No patch content found.');
      }
    
      const result = await applyDiff(args.path, diff, options, signal);
    
      if (!result.applied) {
        throw new McpError(
          ErrorCode.E_INVALID_INPUT,
          result.error?.message === 'Patch had no effect'
            ? 'Patch had no effect \u2014 the file content is unchanged after applying. The patch may not match the current file content. Generate a fresh patch via diff_files and retry.'
            : 'Patch application failed. The file content may have changed or patch context is insufficient. Generate a fresh patch via diff_files against the current file, then retry. If differences are minor, enable fuzzy matching with the fuzzFactor parameter.'
        );
      }
    
      const text = args.dryRun
        ? 'Dry run successful. Patch can be applied.'
        : `Successfully patched ${args.path}`;
    
      return buildToolResponse(text, {
        ok: true,
        path: result.path,
        applied: true,
        hunksApplied: result.hunksApplied,
        linesAdded: result.linesAdded,
        linesRemoved: result.linesRemoved,
      });
    }
  • Input/output schemas for the 'apply_patch' tool.
    export const ApplyPatchInputSchema = z.strictObject({
      path: RequiredPathSchema.describe('Path to file to patch'),
      patch: z
        .string()
        .min(1, 'Patch content required')
        .refine((val) => /@@ -\d+(?:,\d+)? \+\d+(?:,\d+)? @@/u.test(val), {
          error: 'Patch must include hunk headers (e.g., @@ -1,2 +1,2 @@)',
        })
        .describe('Unified diff with @@ hunk headers. Generate with `diff_files`.'),
      fuzzFactor: z
        .int({ error: 'Must be integer' })
        .min(0, 'Min: 0')
        .max(20, 'Max: 20')
        .optional()
        .describe('Maximum fuzzy mismatches per hunk'),
      autoConvertLineEndings: z
        .boolean()
        .optional()
        .default(true)
        .describe('Auto-convert line endings to match target file'),
      dryRun: z
        .boolean()
        .optional()
        .default(false)
        .describe(
          'Validate patch without writing. Check `applied` before committing.'
        ),
    });
    
    export const ApplyPatchOutputSchema = z.strictObject({
      ok: z.boolean(),
      path: z.string().optional(),
      applied: z.boolean().optional(),
      hunksApplied: NonNegativeIntegerSchema.optional().describe('Hunks applied'),
      linesAdded: NonNegativeIntegerSchema.optional().describe('Lines added'),
      linesRemoved: NonNegativeIntegerSchema.optional().describe('Lines removed'),
      results: z
        .array(
          z.strictObject({
            path: z.string().describe('File path'),
            applied: z.boolean().describe('Patch applied successfully'),
            hunksApplied:
              NonNegativeIntegerSchema.optional().describe('Hunks applied'),
  • Registration function 'registerApplyPatchTool' that binds the tool to the MCP server.
    export function registerApplyPatchTool(
      server: McpServer,
      options: ToolRegistrationOptions = {}
    ): void {
      const handler = (
        args: z.infer<typeof ApplyPatchInputSchema>,
        extra: ToolExtra
      ): Promise<ToolResult<z.infer<typeof ApplyPatchOutputSchema>>> =>
        executeToolWithDiagnostics({
          toolName: 'apply_patch',
          extra,
          outputSchema: ApplyPatchOutputSchema,
          timedSignal: {},
          context: { path: args.path },
          run: (signal) => handleApplyPatch(args, signal),
          onError: (error) =>
            buildToolErrorResponse(error, ErrorCode.E_UNKNOWN, args.path),
        });
    
      const wrappedHandler = wrapToolHandler(handler, {
        guard: options.isInitialized,
        progressMessage: (args) => {
          const name = path.basename(args.path);
          return args.dryRun ? `🛠 patch: ${name} [dry run]` : `🛠 patch: ${name}`;
        },
        completionMessage: (args, result) => {
          const name = path.basename(args.path);
          if (result.isError) return `🛠 patch: ${name} • failed`;
          const sc = result.structuredContent;
          if (!sc.ok) return `🛠 patch: ${name} • failed`;
          const added = sc.linesAdded ?? 0;
          const removed = sc.linesRemoved ?? 0;
          const dry = args.dryRun ? 'dry run ' : '';
          if (added > 0 || removed > 0)
            return `🛠 patch: ${name} • ${dry} +${added} -${removed}`;
          return `🛠 patch: ${name} • ${dry}no changes`;
        },
      });
    
      const validatedHandler = withValidatedArgs(
        ApplyPatchInputSchema,
        wrappedHandler
      );
    
      if (
        registerToolTaskIfAvailable(
          server,
          'apply_patch',
          APPLY_PATCH_TOOL,
          validatedHandler,
          options.iconInfo,
          options.isInitialized
        )
      )
        return;
      server.registerTool(
        'apply_patch',
        withDefaultIcons({ ...APPLY_PATCH_TOOL }, options.iconInfo),
        validatedHandler
      );
    }

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