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

TableJSON 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)

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}`
        }]
      };
    }
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/aaronsb/obsidian-semantic-mcp'

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