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);
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries full burden and does well by disclosing key behavioral traits: it modifies existing files (implies mutation), requires the file to exist, supports path resolution, and mentions optional parameters with defaults. It doesn't explicitly mention error handling or permissions requirements, but covers most operational aspects.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is efficiently structured with zero waste: first sentence states core purpose, second provides usage guidance, third covers path handling and prerequisites, fourth explains optional parameters. Every sentence earns its place and information is front-loaded appropriately.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a mutation tool with no annotations and no output schema, the description does well by covering purpose, usage context, prerequisites, and parameter overview. It could be more complete by mentioning what happens on success/failure or typical return values, but given the schema's 100% coverage and clear behavioral disclosure, it's mostly adequate.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the schema already fully documents all 4 parameters. The description adds minimal value beyond the schema - it mentions the 'blocks' parameter structure and the optional boolean parameters, but doesn't provide additional semantic context. Baseline 3 is appropriate when schema does the heavy lifting.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool performs 'targeted search-and-replace operations within an existing file' using specific data structures. It distinguishes from sibling tools like 'write_file' by specifying it's for 'smaller, localized changes' rather than large-scale overwrites.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides explicit guidance: 'Preferred for smaller, localized changes' and 'For large-scale updates or overwrites, consider using `write_file`.' It also states prerequisites: 'File must exist' and 'Accepts relative or absolute paths.' This gives clear when-to-use and when-not-to-use criteria.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

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