replace_content
Search and replace text or regex patterns in files across specified paths. Perform multiple operations at once, with options to ignore case or use regex.
Instructions
Replace content within files across multiple specified paths.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| operations | Yes | An array of search/replace operations to apply to each file. | |
| paths | Yes | An array of relative file paths to perform replacements on. |
Input Schema (JSON Schema)
{
"properties": {
"operations": {
"description": "An array of search/replace operations to apply to each file.",
"items": {
"properties": {
"ignore_case": {
"default": false,
"description": "Ignore case during search.",
"type": "boolean"
},
"replace": {
"description": "Text to replace matches with.",
"type": "string"
},
"search": {
"description": "Text or regex pattern to search for.",
"type": "string"
},
"use_regex": {
"default": false,
"description": "Treat search as regex.",
"type": "boolean"
}
},
"required": [
"search",
"replace"
],
"type": "object"
},
"type": "array"
},
"paths": {
"description": "An array of relative file paths to perform replacements on.",
"items": {
"type": "string"
},
"type": "array"
}
},
"required": [
"paths",
"operations"
],
"type": "object"
}
Implementation Reference
- src/handlers/replace-content.ts:291-313 (handler)Core internal handler function that orchestrates argument parsing, file processing, and response formatting for the replace_content tool.export const handleReplaceContentInternal = async ( args: unknown, deps: ReplaceContentDeps, ): Promise<McpToolResponse> => { // Specify output type const { paths: relativePaths, operations } = parseAndValidateArgs(args); const finalResults = await processAllFilesReplacement(relativePaths, operations, deps); // Return results in McpToolResponse format return { success: true, data: { results: finalResults, }, content: [ { type: 'text', text: JSON.stringify({ results: finalResults }, undefined, 2), }, ], }; };
- Input schemas defining the structure for replace_content tool arguments: ReplaceOperationSchema and ReplaceContentArgsSchema.export const ReplaceOperationSchema = z .object({ search: z.string().describe('Text or regex pattern to search for.'), replace: z.string().describe('Text to replace matches with.'), use_regex: z.boolean().optional().default(false).describe('Treat search as regex.'), ignore_case: z.boolean().optional().default(false).describe('Ignore case during search.'), }) .strict(); export const ReplaceContentArgsSchema = z .object({ paths: z .array(z.string()) .min(1, { message: 'Paths array cannot be empty' }) .describe('An array of relative file paths to perform replacements on.'), operations: z .array(ReplaceOperationSchema) .min(1, { message: 'Operations array cannot be empty' }) .describe('An array of search/replace operations to apply to each file.'), }) .strict();
- src/handlers/replace-content.ts:316-344 (registration)Tool definition for 'replace_content' including name, description, input/output schemas, and handler wrapper that provides dependencies.export const replaceContentToolDefinition = { name: 'replace_content', description: 'Replace content within files across multiple specified paths.', inputSchema: ReplaceContentArgsSchema, // Define output schema for better type safety and clarity outputSchema: z.object({ results: z.array( z.object({ file: z.string(), replacements: z.number().int(), modified: z.boolean(), error: z.string().optional(), }), ), }), // Use locally defined McpResponse type with proper request type handler: async (args: unknown): Promise<McpToolResponse> => { // Validate input using schema first const validatedArgs = ReplaceContentArgsSchema.parse(args); // Production handler provides real dependencies const productionDeps: ReplaceContentDeps = { readFile: fs.readFile, writeFile: fs.writeFile, stat: fs.stat, resolvePath: resolvePath, }; return handleReplaceContentInternal(validatedArgs, productionDeps); }, };
- src/handlers/index.ts:12-60 (registration)Import and registration of replaceContentToolDefinition into the central allToolDefinitions array used for MCP tool exposure.import { replaceContentToolDefinition } from './replace-content.js'; import { handleApplyDiff } from './apply-diff.js'; import { applyDiffInputSchema, ApplyDiffOutput } from '../schemas/apply-diff-schema.js'; import fs from 'node:fs'; import path from 'node:path'; // Define the structure for a tool definition (used internally and for index.ts) import type { ZodType } from 'zod'; import type { McpToolResponse } from '../types/mcp-types.js'; // Define local interfaces based on usage observed in handlers // Define the structure for a tool definition // Matches the structure in individual tool files like applyDiff.ts export interface ToolDefinition<TInput = unknown, TOutput = unknown> { name: string; description: string; inputSchema: ZodType<TInput>; outputSchema?: ZodType<TOutput>; handler: (args: TInput) => Promise<McpToolResponse>; // Changed _args to args } // Helper type to extract input type from a tool definition export type ToolInput<T extends ToolDefinition> = T extends ToolDefinition<infer I, unknown> ? I : never; // Define a more specific type for our tool definitions to avoid naming conflicts type HandlerToolDefinition = { name: string; description: string; inputSchema: ZodType<unknown>; outputSchema?: ZodType<unknown>; handler: (args: unknown) => Promise<{ content: Array<{ type: 'text'; text: string }> }>; }; // Aggregate all tool definitions into a single array // Use our more specific type to avoid naming conflicts export const allToolDefinitions: HandlerToolDefinition[] = [ listFilesToolDefinition, statItemsToolDefinition, readContentToolDefinition, writeContentToolDefinition, deleteItemsToolDefinition, createDirectoriesToolDefinition, chmodItemsToolDefinition, chownItemsToolDefinition, moveItemsToolDefinition, copyItemsToolDefinition, searchFilesToolDefinition, replaceContentToolDefinition,
- Key helper function that processes replacements for a single file, including reading content, applying operations, writing changes, and error handling.async function processSingleFileReplacement( relativePath: string, operations: ReplaceOperation[], deps: ReplaceContentDeps, ): Promise<ReplaceResult> { const pathOutput = relativePath.replaceAll('\\', '/'); let targetPath = ''; let originalContent = ''; let fileContent = ''; let totalReplacements = 0; let modified = false; try { targetPath = await deps.resolvePath(relativePath); const stats = await deps.stat(targetPath); if (!stats.isFile()) { // Return specific error if path is not a file return { file: pathOutput, replacements: 0, modified: false, error: 'Path is not a file', }; } originalContent = await deps.readFile(targetPath, 'utf8'); fileContent = originalContent; for (const op of operations) { const { newContent, replacementsMade } = applyReplaceOperation(fileContent, op); // Only update content and count if replacements were actually made if (replacementsMade > 0 && newContent !== fileContent) { fileContent = newContent; totalReplacements += replacementsMade; // Accumulate replacements across operations } } // Check if content actually changed after all operations if (fileContent !== originalContent) { modified = true; await deps.writeFile(targetPath, fileContent, 'utf8'); } return { file: pathOutput, replacements: totalReplacements, modified }; } catch (error: unknown) { // Catch any error during the process (resolve, stat, read, write) const fileError = handleReplaceError(error, relativePath); return { file: pathOutput, replacements: totalReplacements, // Return replacements count even on write error modified: false, error: fileError, // Use the formatted error message }; } }