Skip to main content
Glama

update_file

Modify file content by performing targeted search-and-replace operations using specified search-replace blocks. Supports regex and multiple replacements for precise, localized updates.

Instructions

Performs targeted search-and-replace operations within an existing file using an array of {search, replace} blocks. Preferred for smaller, localized changes. For large-scale updates or overwrites, consider using write_file. Accepts relative or absolute paths. File must exist. Supports optional useRegex (boolean, default false) and replaceAll (boolean, default false).

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
blocksYesAn array of objects, each with a `search` (string) and `replace` (string) property.
pathYesThe path to the file to update. Can be relative or absolute (resolved like readFile). The file must exist.
replaceAllNoIf true, replace all occurrences matching the SEARCH criteria within the file. If false, only replace the first occurrence. Defaults to false.
useRegexNoIf true, treat the `search` field of each block as a JavaScript regular expression pattern. Defaults to false (exact string matching).

Implementation Reference

  • Core handler function that executes the update_file tool logic: resolves path, reads file content, applies sequential search/replace blocks (supporting regex and all/first occurrence options), writes updated content if changes made, tracks applied/failed blocks, handles errors.
    export const updateFileLogic = async (input: UpdateFileInput, context: RequestContext): Promise<UpdateFileOutput> => { // Destructure validated input const { path: requestedPath, blocks: inputBlocks, useRegex, replaceAll } = input; const logicContext = { ...context, useRegex, replaceAll }; logger.debug(`updateFileLogic: Received request for path "${requestedPath}" with ${inputBlocks.length} blocks`, logicContext); // Resolve the path const absolutePath = serverState.resolvePath(requestedPath, context); logger.debug(`updateFileLogic: Resolved path to "${absolutePath}"`, { ...context, requestedPath }); try { // 1. Read the existing file content let currentContent: string; try { currentContent = await fs.readFile(absolutePath, 'utf8'); logger.debug(`updateFileLogic: Successfully read existing file "${absolutePath}"`, { ...context, requestedPath }); } catch (readError: any) { if (readError.code === 'ENOENT') { logger.warning(`updateFileLogic: File not found at "${absolutePath}"`, { ...context, requestedPath }); throw new McpError(BaseErrorCode.NOT_FOUND, `File not found at path: ${absolutePath}. Cannot update a non-existent file.`, { ...context, requestedPath, resolvedPath: absolutePath, originalError: readError }); } throw readError; // Re-throw other read errors } // 2. Input blocks are already parsed and validated by Zod const diffBlocks: DiffBlock[] = inputBlocks.map(block => ({ ...block, applied: false })); // Add internal 'applied' flag // 3. Apply blocks sequentially let updatedContent = currentContent; let blocksApplied = 0; let blocksFailed = 0; let totalReplacementsMade = 0; // Track individual replacements if replaceAll is true for (let i = 0; i < diffBlocks.length; i++) { const block = diffBlocks[i]; // Create context specific to this block's processing const blockContext = { ...logicContext, blockIndex: i, searchPreview: block.search.substring(0, 50) }; let blockMadeChange = false; let replacementsInBlock = 0; // Count replacements made by *this specific block* try { if (useRegex) { // Treat search as regex pattern // Create the regex. Add 'g' flag if replaceAll is true. const regex = new RegExp(block.search, replaceAll ? 'g' : ''); const matches = updatedContent.match(regex); // Find matches before replacing if (matches && matches.length > 0) { updatedContent = updatedContent.replace(regex, block.replace); replacementsInBlock = matches.length; // Count actual matches found blockMadeChange = true; logger.debug(`Applied regex block`, blockContext); } } else { // Treat search as exact string if (replaceAll) { let startIndex = 0; let index; let replaced = false; // Use split/join for robust replacement of all occurrences const parts = updatedContent.split(block.search); if (parts.length > 1) { // Check if the search string was found at all updatedContent = parts.join(block.replace); replacementsInBlock = parts.length - 1; // Number of replacements is one less than the number of parts replaced = true; } if (replaced) { blockMadeChange = true; logger.debug(`Applied string block (replaceAll=true)`, blockContext); } } else { // Replace only the first occurrence const index = updatedContent.indexOf(block.search); if (index !== -1) { updatedContent = updatedContent.substring(0, index) + block.replace + updatedContent.substring(index + block.search.length); replacementsInBlock = 1; blockMadeChange = true; logger.debug(`Applied string block (replaceAll=false)`, blockContext); } } } } catch (regexError: any) { if (regexError instanceof SyntaxError && useRegex) { logger.error('Invalid regex pattern provided in SEARCH block', { ...blockContext, error: regexError.message }); throw new McpError(BaseErrorCode.VALIDATION_ERROR, `Invalid regular expression pattern in block ${i + 1}: "${block.search}". Error: ${regexError.message}`, blockContext); } // Re-throw other unexpected errors during replacement logger.error('Unexpected error during replacement operation', { ...blockContext, error: regexError.message }); throw new McpError(BaseErrorCode.INTERNAL_ERROR, `Error processing block ${i + 1}: ${regexError.message}`, blockContext); } if (blockMadeChange) { block.applied = true; // Mark the block as having made a change blocksApplied++; totalReplacementsMade += replacementsInBlock; // Add replacements from this block to total } else { blocksFailed++; logger.warning(`Diff block search criteria not found`, blockContext); } } // 4. Write the updated content back to the file only if changes were actually made if (totalReplacementsMade > 0) { // Check if any replacement occurred across all blocks logger.debug(`updateFileLogic: Writing updated content back to "${absolutePath}"`, logicContext); await fs.writeFile(absolutePath, updatedContent, 'utf8'); logger.info(`updateFileLogic: Successfully updated file "${absolutePath}"`, { ...logicContext, requestedPath, blocksApplied, blocksFailed, totalReplacementsMade }); const replaceMsg = `Made ${totalReplacementsMade} replacement(s) across ${blocksApplied} block(s).`; return { message: `Successfully updated file ${absolutePath}. ${replaceMsg} ${blocksFailed} block(s) failed (search criteria not found).`, updatedPath: absolutePath, blocksApplied, blocksFailed, }; } else { // No replacements were made, even if blocks were provided logger.info(`updateFileLogic: No replacements made in file "${absolutePath}"`, { ...logicContext, requestedPath, blocksFailed }); return { message: `No changes applied to file ${absolutePath}. ${blocksFailed} block(s) failed (search criteria not found).`, updatedPath: absolutePath, blocksApplied: 0, // No blocks resulted in a change blocksFailed, }; } } catch (error: any) { logger.error(`updateFileLogic: Error updating file "${absolutePath}"`, { ...logicContext, requestedPath, error: error.message, code: error.code }); if (error instanceof McpError) { throw error; // Re-throw known McpErrors } // Handle potential I/O errors during read or write throw new McpError(BaseErrorCode.INTERNAL_ERROR, `Failed to update file: ${error.message || 'Unknown I/O error'}`, { ...context, requestedPath, resolvedPath: absolutePath, originalError: error }); } };
  • Zod input schema (UpdateFileInputSchema including DiffBlockSchema), TypeScript input type, and output interface for the update_file tool.
    const DiffBlockSchema = z.object({ search: z.string().min(1, 'Search pattern cannot be empty'), replace: z.string(), // Allow empty replace string for deletions }); // Define the input schema using Zod for validation export const UpdateFileInputSchema = z.object({ path: z.string().min(1, 'Path cannot be empty') .describe('The path to the file to update. Can be relative or absolute (resolved like readFile). The file must exist.'), blocks: z.array(DiffBlockSchema).min(1, 'At least one search/replace block is required.') .describe('An array of objects, each with a `search` (string) and `replace` (string) property.'), useRegex: z.boolean().default(false) .describe('If true, treat the `search` field of each block as a JavaScript regular expression pattern. Defaults to false (exact string matching).'), replaceAll: z.boolean().default(false) .describe('If true, replace all occurrences matching the SEARCH criteria within the file. If false, only replace the first occurrence. Defaults to false.'), }); // Define the TypeScript type for the input export type UpdateFileInput = z.infer<typeof UpdateFileInputSchema>; // Define the TypeScript type for a single block based on the schema, adding internal tracking export type DiffBlock = z.infer<typeof DiffBlockSchema> & { applied?: boolean }; // Define the TypeScript type for the output export interface UpdateFileOutput { message: string; updatedPath: string; blocksApplied: number; blocksFailed: number; // Track blocks that didn't find a match }
  • Registration function that adds the 'update_file' tool to the MCP server using server.tool(), including input validation and handler invocation.
    export const registerUpdateFileTool = async (server: McpServer): Promise<void> => { const registrationContext = requestContextService.createRequestContext({ operation: 'RegisterUpdateFileTool' }); logger.info("Attempting to register 'update_file' tool with JSON input format", registrationContext); await ErrorHandler.tryCatch( async () => { server.tool( 'update_file', // Tool name 'Performs targeted search-and-replace operations within an existing file using an array of {search, replace} blocks. Preferred for smaller, localized changes. For large-scale updates or overwrites, consider using `write_file`. Accepts relative or absolute paths. File must exist. Supports optional `useRegex` (boolean, default false) and `replaceAll` (boolean, default false).', // Emphasized usage guidance UpdateFileInputSchema.shape, // Pass the updated schema shape async (params, extra) => { // Validate input using the Zod schema before proceeding const validationResult = UpdateFileInputSchema.safeParse(params); if (!validationResult.success) { // Create a new context for validation error const errorContext = requestContextService.createRequestContext({ operation: 'UpdateFileToolValidation' }); logger.error('Invalid input parameters for update_file tool', { ...errorContext, errors: validationResult.error.errors }); // Throw McpError for invalid parameters throw new McpError(BaseErrorCode.VALIDATION_ERROR, `Invalid parameters: ${validationResult.error.errors.map(e => `${e.path.join('.')} - ${e.message}`).join(', ')}`, errorContext); } const typedParams = validationResult.data; // Use validated data // Create a new context for this specific tool execution const callContext = requestContextService.createRequestContext({ operation: 'UpdateFileToolExecution' }); logger.info(`Executing 'update_file' tool for path: ${typedParams.path} with ${typedParams.blocks.length} blocks`, callContext); // ErrorHandler will catch McpErrors thrown by the logic const result = await ErrorHandler.tryCatch( () => updateFileLogic(typedParams, callContext), { operation: 'updateFileLogic', context: callContext, // Sanitize input for logging: keep path, redact block content input: sanitization.sanitizeForLogging({ path: typedParams.path, blocks: typedParams.blocks.map((_, index) => `[Block ${index + 1} REDACTED]`), // Redact block details useRegex: typedParams.useRegex, replaceAll: typedParams.replaceAll, }), errorCode: BaseErrorCode.INTERNAL_ERROR } ); logger.info(`Successfully executed 'update_file' for path: ${result.updatedPath}. Blocks Applied: ${result.blocksApplied}, Failed: ${result.blocksFailed}`, callContext); // Format the successful response return { content: [{ type: 'text', text: result.message }], }; } ); logger.info("'update_file' tool registered successfully with JSON input format", registrationContext); }, { operation: 'registerUpdateFileTool', context: registrationContext, errorCode: BaseErrorCode.CONFIGURATION_ERROR, critical: true } ); };
  • Invocation of the tool registration during server initialization in the main server file.
    const registrationPromises = [ registerReadFileTool(server), registerSetFilesystemDefaultTool(server), registerWriteFileTool(server), registerUpdateFileTool(server), registerListFilesTool(server), registerDeleteFileTool(server), registerDeleteDirectoryTool(server), registerCreateDirectoryTool(server), registerMovePathTool(server), registerCopyPathTool(server) ]; await Promise.all(registrationPromises); logger.info("Filesystem tools registered successfully", context);

Other Tools

Related Tools

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

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