Skip to main content
Glama

edit_document

Make line-based edits to markdown documents by replacing specific text sequences, generating git-style diffs to track changes.

Instructions

Make line-based edits to a markdown document. Each edit replaces exact line sequences with new content. Returns a git-style diff showing the changes made.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
pathYes
editsYes
dryRunNo

Implementation Reference

  • The core handler function in DocumentHandler class that reads a document, applies sequential text replacements (with fuzzy line matching), generates a unified diff, and optionally writes changes back. Supports dry-run mode.
    async editDocument(
      docPath: string,
      edits: Array<{ oldText: string; newText: string }>,
      dryRun = false
    ): Promise<ToolResponse> {
      try {
        const validPath = await this.validatePath(docPath);
    
        // Read file content and normalize line endings
        const content = normalizeLineEndings(
          await fs.readFile(validPath, "utf-8")
        );
    
        // Apply edits sequentially
        let modifiedContent = content;
        for (const edit of edits) {
          const normalizedOld = normalizeLineEndings(edit.oldText);
          const normalizedNew = normalizeLineEndings(edit.newText);
    
          // If exact match exists, use it
          if (modifiedContent.includes(normalizedOld)) {
            modifiedContent = modifiedContent.replace(
              normalizedOld,
              normalizedNew
            );
            continue;
          }
    
          // Otherwise, try line-by-line matching with flexibility for whitespace
          const oldLines = normalizedOld.split("\n");
          const contentLines = modifiedContent.split("\n");
          let matchFound = false;
    
          for (let i = 0; i <= contentLines.length - oldLines.length; i++) {
            const potentialMatch = contentLines.slice(i, i + oldLines.length);
    
            // Compare lines with normalized whitespace
            const isMatch = oldLines.every((oldLine, j) => {
              const contentLine = potentialMatch[j];
              return oldLine.trim() === contentLine.trim();
            });
    
            if (isMatch) {
              // Preserve original indentation of first line
              const originalIndent = contentLines[i].match(/^\s*/)?.[0] || "";
              const newLines = normalizedNew.split("\n").map((line, j) => {
                if (j === 0) return originalIndent + line.trimStart();
                // For subsequent lines, try to preserve relative indentation
                const oldIndent = oldLines[j]?.match(/^\s*/)?.[0] || "";
                const newIndent = line.match(/^\s*/)?.[0] || "";
                if (oldIndent && newIndent) {
                  const relativeIndent = newIndent.length - oldIndent.length;
                  return (
                    originalIndent +
                    " ".repeat(Math.max(0, relativeIndent)) +
                    line.trimStart()
                  );
                }
                return line;
              });
    
              contentLines.splice(i, oldLines.length, ...newLines);
              modifiedContent = contentLines.join("\n");
              matchFound = true;
              break;
            }
          }
    
          if (!matchFound) {
            throw new Error(
              `Could not find exact match for edit:\n${edit.oldText}`
            );
          }
        }
    
        // Create unified diff
        const diff = createUnifiedDiff(content, modifiedContent, docPath);
    
        // Format diff with appropriate number of backticks
        let numBackticks = 3;
        while (diff.includes("`".repeat(numBackticks))) {
          numBackticks++;
        }
        const formattedDiff = `${"`".repeat(
          numBackticks
        )}diff\n${diff}${"`".repeat(numBackticks)}\n\n`;
    
        if (!dryRun) {
          await fs.writeFile(validPath, modifiedContent, "utf-8");
        }
    
        return {
          content: [{ type: "text", text: formattedDiff }],
        };
      } catch (error) {
        const errorMessage =
          error instanceof Error ? error.message : String(error);
        return {
          content: [
            { type: "text", text: `Error editing document: ${errorMessage}` },
          ],
          isError: true,
        };
      }
    }
  • Zod schema defining the input for edit_document tool: path to document, array of edits (oldText/newText pairs), optional dryRun flag.
    export const EditDocumentSchema = ToolInputSchema.extend({
      path: z.string(),
      edits: z.array(
        z.object({
          oldText: z.string(),
          newText: z.string(),
        })
      ),
      dryRun: z.boolean().default(false),
    });
  • src/index.ts:216-222 (registration)
    Tool registration in the MCP server's listTools response, defining name, description, and input schema.
      description:
        "Make line-based edits to a markdown document. Each edit replaces exact line sequences " +
        "with new content. Returns a git-style diff showing the changes made.",
      inputSchema: zodToJsonSchema(EditDocumentSchema) as any,
    },
    {
      name: "list_documents",
  • src/index.ts:335-347 (registration)
    Dispatch logic in the main CallToolRequest handler that parses input with EditDocumentSchema and delegates to documentHandler.editDocument.
    case "edit_document": {
      const parsed = EditDocumentSchema.safeParse(args);
      if (!parsed.success) {
        throw new Error(
          `Invalid arguments for edit_document: ${parsed.error}`
        );
      }
      return await documentHandler.editDocument(
        parsed.data.path,
        parsed.data.edits,
        parsed.data.dryRun
      );
    }
  • Helper function to generate unified diff patch used in editDocument response.
    function createUnifiedDiff(
      originalContent: string,
      newContent: string,
      filepath: string = "file"
    ): string {
      // Ensure consistent line endings for diff
      const normalizedOriginal = normalizeLineEndings(originalContent);
      const normalizedNew = normalizeLineEndings(newContent);
    
      return createTwoFilesPatch(
        filepath,
        filepath,
        normalizedOriginal,
        normalizedNew,
        "original",
        "modified"
      );
    }

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/alekspetrov/mcp-docs-service'

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