edit
Apply sequential text replacements to files by specifying exact strings to modify. Use dry run to preview changes before applying them to ensure accuracy.
Instructions
Apply sequential literal string replacements to a file (first occurrence per edit). oldText must match exactly — include 3–5 lines of context for unique targeting. Use dryRun:true to preview.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| path | Yes | Absolute path to file or directory. | |
| edits | Yes | List of replacements to apply sequentially. Each edit replaces the first occurrence of oldText. | |
| dryRun | No | Preview edits without writing. Check `unmatchedEdits` in response. | |
| ignoreWhitespace | No | Treat all whitespace sequences as equivalent when matching oldText. |
Implementation Reference
- src/tools/edit-file.ts:353-392 (handler)The handler function `handleEditFile` executes the "edit" tool by reading the target file, applying requested edits, and writing the changes back.
async function handleEditFile( args: EditInput, signal?: AbortSignal ): Promise<ToolResponse<EditOutput>> { const { validPath, content } = await loadEditableFile(args.path, signal); const editResult = await applyEdits( content, args.edits, args.ignoreWhitespace ); const structured = buildStructuredEditOutput(validPath, editResult); if (args.dryRun) { if (editResult.appliedEdits > 0) { structured.diff = await buildDiff(validPath, content, editResult.content); } return buildToolResponse( `Dry run complete. ${editResult.appliedEdits} edits would be applied.`, structured ); } if (editResult.appliedEdits === 0 && editResult.unmatchedEdits.length > 0) { throw new McpError( ErrorCode.E_INVALID_INPUT, `All ${editResult.unmatchedEdits.length} edits failed to match. Verify oldText includes exact content with surrounding context.`, args.path ); } if (editResult.appliedEdits > 0) { await atomicWriteFile(validPath, editResult.content, { encoding: 'utf-8', signal, }); } return buildToolResponse(buildEditMessage(args.path, editResult), structured); } - src/tools/edit-file.ts:394-441 (registration)The `registerEditFileTool` function registers the "edit" tool with the MCP server.
export function registerEditFileTool( server: McpServer, options: ToolRegistrationOptions = {} ): void { const handler = ( args: EditInput, extra: ToolExtra ): Promise<ToolResult<EditOutput>> => executeToolWithDiagnostics({ toolName: 'edit', extra, outputSchema: EditFileOutputSchema, timedSignal: {}, context: { path: args.path }, run: (signal) => handleEditFile(args, signal), onError: (error) => buildToolErrorResponse(error, ErrorCode.E_UNKNOWN, args.path), }); const wrappedHandler = wrapToolHandler(handler, { guard: options.isInitialized, progressMessage: buildEditProgressMessage, completionMessage: buildEditCompletionMessage, }); const validatedHandler = withValidatedArgs( EditFileInputSchema, wrappedHandler ); if ( registerToolTaskIfAvailable( server, 'edit', EDIT_FILE_TOOL, validatedHandler, options.iconInfo, options.isInitialized ) ) return; server.registerTool( 'edit', withDefaultIcons({ ...EDIT_FILE_TOOL }, options.iconInfo), validatedHandler ); } - src/tools/edit-file.ts:31-43 (schema)The tool definition `EDIT_FILE_TOOL` provides the schema and metadata for the "edit" tool.
export const EDIT_FILE_TOOL: ToolContract = { name: 'edit', title: 'Edit File', description: 'Apply sequential literal string replacements to a file (first occurrence per edit). ' + '`oldText` must match exactly — include 3–5 lines of context for unique targeting. ' + 'Use `dryRun:true` to preview.', inputSchema: EditFileInputSchema, outputSchema: EditFileOutputSchema, annotations: DESTRUCTIVE_WRITE_TOOL_ANNOTATIONS, nuances: ['Each edit applies to the output of the previous edit.'], taskSupport: 'forbidden', } as const;