Skip to main content
Glama

edit

Perform smart editing operations on files and directories, including window buffering, appending, patching, line-specific changes, and buffer transfers. Supports fuzzy matching, targeted text replacement, and precise content manipulation.

Instructions

Smart editing operations - window (auto-buffers content), append, patch, at_line, from_buffer

Input Schema

NameRequiredDescriptionDefault
actionYesThe specific action to perform
contentNoContent to write or append
fuzzyThresholdNoSimilarity threshold for fuzzy matching (0-1)
lineNumberNoLine number for at_line action
modeNoInsert mode for at_line action
newTextNoText to replace with
oldTextNoText to search for (supports fuzzy matching)
operationNoPatch operation: append (add after), prepend (add before), or replace
pathNoPath to the file or directory
targetNoTarget identifier - e.g., "Daily Notes::Today" for heading, block ID, or frontmatter field name
targetTypeNoWhat to target: heading (by path like "H1::H2"), block (by ID), or frontmatter (field)

Input Schema (JSON Schema)

{ "properties": { "action": { "description": "The specific action to perform", "enum": [ "window", "append", "patch", "at_line", "from_buffer" ], "type": "string" }, "content": { "description": "Content to write or append", "type": "string" }, "fuzzyThreshold": { "default": 0.7, "description": "Similarity threshold for fuzzy matching (0-1)", "type": "number" }, "lineNumber": { "description": "Line number for at_line action", "type": "number" }, "mode": { "description": "Insert mode for at_line action", "enum": [ "before", "after", "replace" ], "type": "string" }, "newText": { "description": "Text to replace with", "type": "string" }, "oldText": { "description": "Text to search for (supports fuzzy matching)", "type": "string" }, "operation": { "description": "Patch operation: append (add after), prepend (add before), or replace", "enum": [ "append", "prepend", "replace" ], "type": "string" }, "path": { "description": "Path to the file or directory", "type": "string" }, "target": { "description": "Target identifier - e.g., \"Daily Notes::Today\" for heading, block ID, or frontmatter field name", "type": "string" }, "targetType": { "description": "What to target: heading (by path like \"H1::H2\"), block (by ID), or frontmatter (field)", "enum": [ "heading", "block", "frontmatter" ], "type": "string" } }, "required": [ "action" ], "type": "object" }

Implementation Reference

  • Core handler for 'edit' tool operations - implements window (fuzzy replace), append, patch, at_line (insert/replace), from_buffer actions
    private async executeEditOperation(action: string, params: any): Promise<any> { // Import window edit tools dynamically to avoid circular dependencies const { performWindowEdit } = await import('../tools/window-edit.js'); const buffer = ContentBufferManager.getInstance(); switch (action) { case 'window': const result = await performWindowEdit( this.api, params.path, params.oldText, params.newText, params.fuzzyThreshold ); if (result.isError) { throw new Error(result.content[0].text); } return result; case 'append': return await this.api.appendToFile(params.path, params.content); case 'patch': return await this.api.patchVaultFile(params.path, { operation: params.operation, targetType: params.targetType, target: params.target, content: params.content }); case 'at_line': // Get content to insert let insertContent = params.content; if (!insertContent) { const buffered = buffer.retrieve(); if (!buffered) { throw new Error('No content provided and no buffered content found'); } insertContent = buffered.content; } // Get file and perform line-based edit const file = await this.api.getFile(params.path); if (isImageFile(file)) { throw new Error('Cannot perform line-based edits on image files'); } const content = typeof file === 'string' ? file : file.content; const lines = content.split('\n'); if (params.lineNumber < 1 || params.lineNumber > lines.length + 1) { throw new Error(`Invalid line number ${params.lineNumber}. File has ${lines.length} lines.`); } const lineIndex = params.lineNumber - 1; const mode = params.mode || 'replace'; switch (mode) { case 'before': lines.splice(lineIndex, 0, insertContent); break; case 'after': lines.splice(lineIndex + 1, 0, insertContent); break; case 'replace': lines[lineIndex] = insertContent; break; } await this.api.updateFile(params.path, lines.join('\n')); return { success: true, line: params.lineNumber, mode }; case 'from_buffer': const buffered = buffer.retrieve(); if (!buffered) { throw new Error('No buffered content available'); } return await performWindowEdit( this.api, params.path, params.oldText || buffered.searchText || '', buffered.content, params.fuzzyThreshold ); default: throw new Error(`Unknown edit action: ${action}`); } }
  • MCP tool handler for 'edit' - instantiates SemanticRouter and calls route with operation='edit', action=args.action
    handler: async (api: ObsidianAPI, args: any) => { const router = new SemanticRouter(api); const request: SemanticRequest = { operation, action: args.action, params: args }; const response = await router.route(request); // Format for MCP if (response.error) { return { content: [{ type: 'text', text: JSON.stringify({ error: response.error, workflow: response.workflow, context: response.context }, null, 2) }], isError: true }; } // Check if the result is an image file for vault read operations if (operation === 'vault' && args.action === 'read' && response.result && isImageFile(response.result)) { // Return image content for MCP return { content: [{ type: 'image', data: response.result.base64Data, mimeType: response.result.mimeType }] }; } return { content: [{ type: 'text', text: JSON.stringify({ result: response.result, workflow: response.workflow, context: response.context, efficiency_hints: response.efficiency_hints }, null, 2) }] }; } });
  • Input schema properties for 'edit' tool parameters (path, content, oldText, newText, fuzzyThreshold, lineNumber, mode, operation, targetType, target)
    edit: { ...pathParam, ...contentParam, oldText: { type: 'string', description: 'Text to search for (supports fuzzy matching)' }, newText: { type: 'string', description: 'Text to replace with' }, fuzzyThreshold: { type: 'number', description: 'Similarity threshold for fuzzy matching (0-1)', default: 0.7 }, lineNumber: { type: 'number', description: 'Line number for at_line action' }, mode: { type: 'string', enum: ['before', 'after', 'replace'], description: 'Insert mode for at_line action' }, operation: { type: 'string', enum: ['append', 'prepend', 'replace'], description: 'Patch operation: append (add after), prepend (add before), or replace' }, targetType: { type: 'string', enum: ['heading', 'block', 'frontmatter'], description: 'What to target: heading (by path like "H1::H2"), block (by ID), or frontmatter (field)' }, target: { type: 'string', description: 'Target identifier - e.g., "Daily Notes::Today" for heading, block ID, or frontmatter field name' } },
  • Action enum for 'edit' tool: ['window', 'append', 'patch', 'at_line', 'from_buffer'] used in inputSchema
    const actions: Record<string, string[]> = { vault: ['list', 'read', 'create', 'update', 'delete', 'search', 'fragments'], edit: ['window', 'append', 'patch', 'at_line', 'from_buffer'], view: ['file', 'window', 'active', 'open_in_obsidian'], workflow: ['suggest'], system: ['info', 'commands', 'fetch_web'] }; return actions[operation] || []; }
  • Registration of 'edit' tool via createSemanticTool('edit') in semanticTools export
    // Export the 5 semantic tools export const semanticTools = [ createSemanticTool('vault'), createSemanticTool('edit'), createSemanticTool('view'), createSemanticTool('workflow'), createSemanticTool('system') ];
  • performWindowEdit helper function used by edit:window and edit:from_buffer for fuzzy text replacement in files
    export async function performWindowEdit( api: ObsidianAPI, path: string, oldText: string, newText: string, fuzzyThreshold: number = 0.7 ) { const buffer = ContentBufferManager.getInstance(); // Get current file content const file = await api.getFile(path); if (isImageFile(file)) { throw new Error('Cannot perform window edits on image files'); } const content = typeof file === 'string' ? file : file.content; // Try exact match first if (content.includes(oldText)) { const newContent = content.replace(oldText, newText); await api.updateFile(path, newContent); return { content: [{ type: 'text', text: `Successfully replaced exact match in ${path}` }] }; } // Buffer the new content for potential recovery buffer.store(newText, undefined, { filePath: path, searchText: oldText }); // Try fuzzy matching const matches = findFuzzyMatches(content, oldText, fuzzyThreshold); if (matches.length === 0) { // No matches found, provide helpful feedback return { content: [{ type: 'text', text: `No matches found for "${oldText}" in ${path}. ` + `Content has been buffered. You can use edit_vault_from_buffer to retry ` + `with different search text or insert_vault_at_line to insert at a specific line.` }], isError: true }; } // If multiple matches, ask for clarification if (matches.length > 1) { const matchList = matches.map(m => `Line ${m.lineNumber} (${Math.round(m.similarity * 100)}% match): "${m.line.trim()}"` ).join('\n'); return { content: [{ type: 'text', text: `Found ${matches.length} potential matches:\n\n${matchList}\n\n` + `Content has been buffered. Use insert_vault_at_line with the specific line number.` }], isError: true }; } // Single match found - replace the entire line const match = matches[0]; const lines = content.split('\n'); lines[match.lineNumber - 1] = newText; const newContent = lines.join('\n'); await api.updateFile(path, newContent); return { content: [{ type: 'text', text: `Successfully replaced line ${match.lineNumber} (${Math.round(match.similarity * 100)}% match) in ${path}` }] }; }

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/aaronsb/obsidian-semantic-mcp'

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