UNPKG

@context-sync/server

Version:

MCP server for AI context sync with persistent memory, workspace file access, and intelligent code operations

1,219 lines (1,218 loc) • 83.7 kB
import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { CallToolRequestSchema, ListToolsRequestSchema, ListPromptsRequestSchema, GetPromptRequestSchema, } from '@modelcontextprotocol/sdk/types.js'; import { Storage } from './storage.js'; import { ProjectDetector } from './project-detector.js'; import { WorkspaceDetector } from './workspace-detector.js'; import { FileWriter } from './file-writer.js'; import { FileSearcher } from './file-searcher.js'; import { GitIntegration } from './git-integration.js'; import { DependencyAnalyzer } from './dependency-analyzer.js'; import { CallGraphAnalyzer } from './call-graph-analyzer.js'; import { TypeAnalyzer } from './type-analyzer.js'; import { PlatformSync } from './platform-sync.js'; import { TodoManager } from './todo-manager.js'; import { createTodoHandlers } from './todo-handlers.js'; import { todoToolDefinitions } from './todo-tools.js'; export class ContextSyncServer { server; storage; projectDetector; workspaceDetector; fileWriter; fileSearcher; gitIntegration = null; dependencyAnalyzer = null; callGraphAnalyzer = null; typeAnalyzer = null; platformSync; todoManager; todoHandlers; constructor(storagePath) { this.storage = new Storage(storagePath); this.projectDetector = new ProjectDetector(this.storage); this.workspaceDetector = new WorkspaceDetector(this.storage, this.projectDetector); this.fileWriter = new FileWriter(this.workspaceDetector, this.storage); this.fileSearcher = new FileSearcher(this.workspaceDetector); this.server = new Server({ name: 'context-sync', version: '0.6.0', }, { capabilities: { tools: {}, prompts: {}, }, }); this.platformSync = new PlatformSync(this.storage); this.todoManager = new TodoManager(this.storage.getDb()); this.todoHandlers = createTodoHandlers(this.todoManager); // Auto-detect platform const detectedPlatform = PlatformSync.detectPlatform(); this.platformSync.setPlatform(detectedPlatform); this.setupToolHandlers(); this.setupPromptHandlers(); } setupPromptHandlers() { this.server.setRequestHandler(ListPromptsRequestSchema, async () => ({ prompts: [ { name: 'project_context', description: 'Automatically inject active project context into conversation', arguments: [], }, ], })); this.server.setRequestHandler(GetPromptRequestSchema, async (request) => { if (request.params.name !== 'project_context') { throw new Error(`Unknown prompt: ${request.params.name}`); } const project = this.storage.getCurrentProject(); if (!project) { return { description: 'No active project', messages: [], }; } const summary = this.storage.getContextSummary(project.id); const contextMessage = this.buildContextPrompt(summary); return { description: `Context for ${project.name}`, messages: [ { role: 'user', content: { type: 'text', text: contextMessage, }, }, ], }; }); } buildContextPrompt(summary) { const { project, recentDecisions } = summary; let prompt = `[ACTIVE PROJECT CONTEXT - Auto-loaded]\n\n`; prompt += `šŸ“ Project: ${project.name}\n`; if (project.architecture) { prompt += `šŸ—ļø Architecture: ${project.architecture}\n`; } if (project.techStack && project.techStack.length > 0) { prompt += `āš™ļø Tech Stack: ${project.techStack.join(', ')}\n`; } if (recentDecisions.length > 0) { prompt += `\nšŸ“‹ Recent Decisions:\n`; recentDecisions.slice(0, 5).forEach((d, i) => { prompt += `${i + 1}. [${d.type}] ${d.description}`; if (d.reasoning) { prompt += ` - ${d.reasoning}`; } prompt += `\n`; }); } const lastUpdated = new Date(project.updatedAt).toLocaleString(); prompt += `\nšŸ• Last Updated: ${lastUpdated}\n`; prompt += `\n---\n`; prompt += `FILE WRITING WORKFLOW (v0.3.0):\n\n`; prompt += `When user requests file creation/modification:\n`; prompt += `1. Call create_file/modify_file/delete_file → Shows preview\n`; prompt += `2. Ask user: "Should I proceed?" or "Approve this change?"\n`; prompt += `3. If user says yes/approve/go ahead:\n`; prompt += ` → Call apply_create_file/apply_modify_file/apply_delete_file\n`; prompt += `4. If user says no → Don't call apply tools\n\n`; prompt += `IMPORTANT: Always wait for explicit user approval before calling apply_* tools!\n`; return prompt; } setupToolHandlers() { this.server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: [ // V0.2.0 tools ...this.getV02Tools(), // V0.3.0 tools (including apply_* tools) ...this.getV03Tools(), ], })); this.server.setRequestHandler(CallToolRequestSchema, async (request) => { const { name, arguments: args } = request.params; // V0.2.0 handlers if (name === 'get_project_context') return this.handleGetContext(); if (name === 'save_decision') return this.handleSaveDecision(args); if (name === 'save_conversation') return this.handleSaveConversation(args); if (name === 'init_project') return this.handleInitProject(args); if (name === 'detect_project') return this.handleDetectProject(args); if (name === 'set_workspace') return this.handleSetWorkspace(args); if (name === 'read_file') return this.handleReadFile(args); if (name === 'get_project_structure') return this.handleGetProjectStructure(args); if (name === 'scan_workspace') return this.handleScanWorkspace(); // V0.3.0 - Preview tools if (name === 'create_file') return this.handleCreateFile(args); if (name === 'modify_file') return this.handleModifyFile(args); if (name === 'delete_file') return this.handleDeleteFile(args); // V0.3.0 - Apply tools (NEW!) if (name === 'apply_create_file') return this.handleApplyCreateFile(args); if (name === 'apply_modify_file') return this.handleApplyModifyFile(args); if (name === 'apply_delete_file') return this.handleApplyDeleteFile(args); // V0.3.0 - Other tools if (name === 'undo_file_change') return this.handleUndoFileChange(args); if (name === 'search_files') return this.handleSearchFiles(args); if (name === 'search_content') return this.handleSearchContent(args); if (name === 'find_symbol') return this.handleFindSymbol(args); if (name === 'git_status') return this.handleGitStatus(); if (name === 'git_diff') return this.handleGitDiff(args); if (name === 'git_branch_info') return this.handleGitBranchInfo(args); if (name === 'suggest_commit_message') return this.handleSuggestCommitMessage(args); // V0.4.0 - Dependency Analysis if (name === 'analyze_dependencies') return this.handleAnalyzeDependencies(args); if (name === 'get_dependency_tree') return this.handleGetDependencyTree(args); if (name === 'find_importers') return this.handleFindImporters(args); if (name === 'detect_circular_deps') return this.handleDetectCircularDeps(args); // V0.4.0 - Call Graph Analysis (ADD THESE) if (name === 'analyze_call_graph') return this.handleAnalyzeCallGraph(args); if (name === 'find_callers') return this.handleFindCallers(args); if (name === 'trace_execution_path') return this.handleTraceExecutionPath(args); if (name === 'get_call_tree') return this.handleGetCallTree(args); // V0.4.0 - Type Analysis if (name === 'find_type_definition') return await this.handleFindTypeDefinition(args); if (name === 'get_type_info') return await this.handleGetTypeInfo(args); if (name === 'find_type_usages') return await this.handleFindTypeUsages(args); if (name === 'switch_platform') return this.handleSwitchPlatform(args); if (name === 'get_platform_status') return this.handleGetPlatformStatus(); if (name === 'get_platform_context') return this.handleGetPlatformContext(args); if (name === 'setup_cursor') return this.handleSetupCursor(); if (name === 'get_started') return this.handleGetStarted(); // V0.4.0 - Todo Management Tools (ADD THESE) if (name.startsWith('todo_')) { const handler = this.todoHandlers[name]; if (handler) { return await handler(args); } } throw new Error(`Unknown tool: ${name}`); }); } getV02Tools() { return [ { name: 'get_project_context', description: 'Get the current project context including recent decisions and conversations', inputSchema: { type: 'object', properties: {} }, }, { name: 'save_decision', description: 'Save an important technical decision or architectural choice', inputSchema: { type: 'object', properties: { type: { type: 'string', enum: ['architecture', 'library', 'pattern', 'configuration', 'other'] }, description: { type: 'string' }, reasoning: { type: 'string' }, }, required: ['type', 'description'], }, }, { name: 'save_conversation', description: 'Save a conversation snippet for future reference', inputSchema: { type: 'object', properties: { content: { type: 'string' }, role: { type: 'string', enum: ['user', 'assistant'] }, }, required: ['content', 'role'], }, }, { name: 'init_project', description: 'Initialize or switch to a project', inputSchema: { type: 'object', properties: { name: { type: 'string' }, path: { type: 'string' }, architecture: { type: 'string' }, }, required: ['name'], }, }, { name: 'detect_project', description: 'Auto-detect project from a directory path', inputSchema: { type: 'object', properties: { path: { type: 'string' } }, required: ['path'], }, }, { name: 'set_workspace', description: 'Set the current workspace/project folder', inputSchema: { type: 'object', properties: { path: { type: 'string' } }, required: ['path'], }, }, { name: 'read_file', description: 'Read a file from the current workspace', inputSchema: { type: 'object', properties: { path: { type: 'string' } }, required: ['path'], }, }, { name: 'get_project_structure', description: 'Get the file/folder structure of current workspace', inputSchema: { type: 'object', properties: { depth: { type: 'number' } }, }, }, { name: 'scan_workspace', description: 'Scan workspace and get overview of important files', inputSchema: { type: 'object', properties: {} }, }, ]; } getV03Tools() { return [ // Preview tools (show preview, don't apply) { name: 'create_file', description: 'Preview file creation (does NOT create the file yet)', inputSchema: { type: 'object', properties: { path: { type: 'string', description: 'Relative path for new file' }, content: { type: 'string', description: 'File content' }, overwrite: { type: 'boolean', description: 'Overwrite if exists (default: false)' }, }, required: ['path', 'content'], }, }, { name: 'modify_file', description: 'Preview file modification (does NOT modify the file yet)', inputSchema: { type: 'object', properties: { path: { type: 'string' }, changes: { type: 'array', items: { type: 'object', properties: { type: { type: 'string', enum: ['replace', 'insert', 'delete'] }, line: { type: 'number', description: 'Line number for the change' }, oldText: { type: 'string', description: 'Text to be replaced or deleted' }, newText: { type: 'string', description: 'New text to insert or replace with' }, }, required: ['type', 'newText'] }, description: 'Array of file changes' }, }, required: ['path', 'changes'], }, }, { name: 'delete_file', description: 'Preview file deletion (does NOT delete the file yet)', inputSchema: { type: 'object', properties: { path: { type: 'string' } }, required: ['path'], }, }, // Apply tools (actually perform the action) { name: 'apply_create_file', description: 'Actually create the file after user approval', inputSchema: { type: 'object', properties: { path: { type: 'string' }, content: { type: 'string' }, }, required: ['path', 'content'], }, }, { name: 'apply_modify_file', description: 'Actually modify the file after user approval', inputSchema: { type: 'object', properties: { path: { type: 'string' }, changes: { type: 'array', items: { type: 'object', properties: { type: { type: 'string', enum: ['replace', 'insert', 'delete'] }, line: { type: 'number', description: 'Line number for the change' }, oldText: { type: 'string', description: 'Text to be replaced or deleted' }, newText: { type: 'string', description: 'New text to insert or replace with' } }, required: ['type', 'newText'] }, description: 'Array of file changes to apply' }, }, required: ['path', 'changes'], }, }, { name: 'apply_delete_file', description: 'Actually delete the file after user approval', inputSchema: { type: 'object', properties: { path: { type: 'string' } }, required: ['path'], }, }, // Other tools { name: 'undo_file_change', description: 'Undo the last modification to a file', inputSchema: { type: 'object', properties: { path: { type: 'string' }, steps: { type: 'number', description: 'Number of changes to undo (default: 1)' }, }, required: ['path'], }, }, // Search tools { name: 'search_files', description: 'Search for files by name or pattern', inputSchema: { type: 'object', properties: { pattern: { type: 'string' }, maxResults: { type: 'number' }, ignoreCase: { type: 'boolean' }, }, required: ['pattern'], }, }, { name: 'search_content', description: 'Search file contents for text or regex', inputSchema: { type: 'object', properties: { query: { type: 'string' }, regex: { type: 'boolean' }, caseSensitive: { type: 'boolean' }, filePattern: { type: 'string' }, maxResults: { type: 'number' }, }, required: ['query'], }, }, { name: 'find_symbol', description: 'Find function, class, or variable definitions', inputSchema: { type: 'object', properties: { symbol: { type: 'string' }, type: { type: 'string', enum: ['function', 'class', 'variable', 'all'] }, }, required: ['symbol'], }, }, // Git tools { name: 'git_status', description: 'Check git repository status', inputSchema: { type: 'object', properties: {} }, }, { name: 'git_diff', description: 'View git diff for file(s)', inputSchema: { type: 'object', properties: { path: { type: 'string' }, staged: { type: 'boolean' }, }, }, }, { name: 'git_branch_info', description: 'Get git branch information', inputSchema: { type: 'object', properties: { action: { type: 'string', enum: ['current', 'list', 'recent'] }, }, }, }, { name: 'suggest_commit_message', description: 'Suggest a commit message based on changes', inputSchema: { type: 'object', properties: { files: { type: 'array', items: { type: 'string' } }, convention: { type: 'string', enum: ['conventional', 'simple', 'descriptive'] }, }, }, }, // V0.4.0 - Dependency Analysis Tools (ADD THESE) { name: 'analyze_dependencies', description: 'Analyze import/export dependencies for a file. Returns all imports, exports, files that import this file, and circular dependencies.', inputSchema: { type: 'object', properties: { filePath: { type: 'string', description: 'Path to the file to analyze (relative to workspace)' }, }, required: ['filePath'], }, }, { name: 'get_dependency_tree', description: 'Get a tree view of all dependencies for a file, showing nested imports up to a specified depth.', inputSchema: { type: 'object', properties: { filePath: { type: 'string', description: 'Path to the file (relative to workspace)' }, depth: { type: 'number', description: 'Maximum depth to traverse (default: 3, max: 10)' }, }, required: ['filePath'], }, }, { name: 'find_importers', description: 'Find all files that import a given file. Useful for understanding what depends on this file.', inputSchema: { type: 'object', properties: { filePath: { type: 'string', description: 'Path to the file (relative to workspace)' }, }, required: ['filePath'], }, }, { name: 'detect_circular_deps', description: 'Detect circular dependencies starting from a file. Shows all circular dependency chains.', inputSchema: { type: 'object', properties: { filePath: { type: 'string', description: 'Path to the file (relative to workspace)' }, }, required: ['filePath'], }, }, // V0.4.0 - Call Graph Analysis Tools (ADD THESE) { name: 'analyze_call_graph', description: 'Analyze the call graph for a function. Shows what functions it calls (callees) and what functions call it (callers).', inputSchema: { type: 'object', properties: { functionName: { type: 'string', description: 'Name of the function to analyze' }, }, required: ['functionName'], }, }, { name: 'find_callers', description: 'Find all functions that call a given function. Useful for impact analysis.', inputSchema: { type: 'object', properties: { functionName: { type: 'string', description: 'Name of the function' }, }, required: ['functionName'], }, }, { name: 'trace_execution_path', description: 'Trace all possible execution paths from one function to another. Shows the call chain.', inputSchema: { type: 'object', properties: { startFunction: { type: 'string', description: 'Starting function name' }, endFunction: { type: 'string', description: 'Target function name' }, maxDepth: { type: 'number', description: 'Maximum depth to search (default: 10)' }, }, required: ['startFunction', 'endFunction'], }, }, { name: 'get_call_tree', description: 'Get a tree view of function calls starting from a given function.', inputSchema: { type: 'object', properties: { functionName: { type: 'string', description: 'Name of the function' }, depth: { type: 'number', description: 'Maximum depth (default: 3)' }, }, required: ['functionName'], }, }, // V0.4.0 - Type Analysis Tools (ADD THESE) { name: 'find_type_definition', description: 'Find where a type, interface, class, or enum is defined.', inputSchema: { type: 'object', properties: { typeName: { type: 'string', description: 'Name of the type to find' }, }, required: ['typeName'], }, }, { name: 'get_type_info', description: 'Get complete information about a type including properties, methods, and usage.', inputSchema: { type: 'object', properties: { typeName: { type: 'string', description: 'Name of the type' }, }, required: ['typeName'], }, }, { name: 'find_type_usages', description: 'Find all places where a type is used in the codebase.', inputSchema: { type: 'object', properties: { typeName: { type: 'string', description: 'Name of the type' }, }, required: ['typeName'], }, }, { name: 'switch_platform', description: 'Switch between AI platforms (Claude ↔ Cursor) with full context handoff', inputSchema: { type: 'object', properties: { fromPlatform: { type: 'string', enum: ['claude', 'cursor', 'copilot', 'other'], description: 'Platform you are switching from', }, toPlatform: { type: 'string', enum: ['claude', 'cursor', 'copilot', 'other'], description: 'Platform you are switching to', }, }, required: ['fromPlatform', 'toPlatform'], }, }, { name: 'get_platform_status', description: 'Check which AI platforms have Context Sync configured', inputSchema: { type: 'object', properties: {}, }, }, { name: 'get_platform_context', description: 'Get context specific to a platform (conversations, decisions)', inputSchema: { type: 'object', properties: { platform: { type: 'string', enum: ['claude', 'cursor', 'copilot', 'other'], description: 'Platform to get context for (defaults to current platform)', }, }, }, }, { name: 'setup_cursor', description: 'Get instructions for setting up Context Sync in Cursor IDE', inputSchema: { type: 'object', properties: {}, }, }, { name: 'get_started', description: 'Get started with Context Sync - shows installation status, current state, and guided next steps', inputSchema: { type: 'object', properties: {}, }, }, // V0.4.0 - Todo Management Tools (ADD THESE) ...todoToolDefinitions, ]; } // ========== V0.2.0 HANDLERS ========== handleGetContext() { const project = this.storage.getCurrentProject(); if (!project) { return { content: [{ type: 'text', text: 'No active project. Use init_project to create one.', }], }; } const summary = this.storage.getContextSummary(project.id); return { content: [{ type: 'text', text: this.formatContextSummary(summary), }], }; } handleSaveDecision(args) { const project = this.storage.getCurrentProject(); if (!project) { return { content: [{ type: 'text', text: 'No active project. Use init_project first.' }], }; } const decision = this.storage.addDecision({ projectId: project.id, type: args.type, description: args.description, reasoning: args.reasoning, }); return { content: [{ type: 'text', text: `Decision saved: ${decision.description}` }], }; } handleSaveConversation(args) { const project = this.storage.getCurrentProject(); if (!project) { return { content: [{ type: 'text', text: 'No active project. Use init_project first.' }], }; } this.storage.addConversation({ projectId: project.id, tool: 'claude', role: args.role, content: args.content, }); return { content: [{ type: 'text', text: 'Conversation saved to project context.' }], }; } handleInitProject(args) { const project = this.storage.createProject(args.name, args.path); if (args.architecture) { this.storage.updateProject(project.id, { architecture: args.architecture }); } return { content: [{ type: 'text', text: `Project "${args.name}" initialized and set as active.` }], }; } handleDetectProject(args) { try { this.projectDetector.createOrUpdateProject(args.path); const project = this.storage.getCurrentProject(); if (!project) { return { content: [{ type: 'text', text: `No project detected at: ${args.path}` }], }; } const techStack = project.techStack.join(', ') || 'None detected'; const arch = project.architecture || 'Not specified'; return { content: [{ type: 'text', text: `šŸ“ Detected project: ${project.name}\nšŸ—ļø Architecture: ${arch}\nāš™ļø Tech Stack: ${techStack}`, }], }; } catch (error) { return { content: [{ type: 'text', text: `Error detecting project: ${error instanceof Error ? error.message : 'Unknown error'}`, }], }; } } async handleSetWorkspace(args) { try { this.workspaceDetector.setWorkspace(args.path); this.gitIntegration = new GitIntegration(args.path); this.dependencyAnalyzer = new DependencyAnalyzer(args.path); this.callGraphAnalyzer = new CallGraphAnalyzer(args.path); this.typeAnalyzer = new TypeAnalyzer(args.path); const project = this.storage.getCurrentProject(); if (!project) { return { content: [{ type: 'text', text: `Workspace set to: ${args.path}\nBut no project configuration detected.`, }], }; } const structure = await this.workspaceDetector.getProjectStructure(2); const isGit = this.gitIntegration.isGitRepo() ? ' (Git repo āœ“)' : ''; return { content: [{ type: 'text', text: `āœ… Workspace set: ${args.path}${isGit}\n\nšŸ“ Project: ${project.name}\nāš™ļø Tech Stack: ${project.techStack.join(', ')}\n\nšŸ“‚ Structure Preview:\n${structure}\n\nYou can now:\n- read_file to view files\n- create_file to preview new files (then apply_create_file to create)\n- modify_file to preview changes (then apply_modify_file to apply)\n- search_files to find files\n- git_status to check git status`, }], }; } catch (error) { return { content: [{ type: 'text', text: `Error setting workspace: ${error instanceof Error ? error.message : 'Unknown error'}`, }], }; } } async handleReadFile(args) { try { const file = await this.workspaceDetector.readFile(args.path); if (!file) { return { content: [{ type: 'text', text: `File not found: ${args.path}\n\nMake sure:\n1. Workspace is set (use set_workspace)\n2. Path is relative to workspace root\n3. File exists`, }], }; } const sizeKB = file.size / 1024; let sizeWarning = ''; if (sizeKB > 100) { sizeWarning = `\nāš ļø Large file (${sizeKB.toFixed(1)}KB) - showing full content\n`; } return { content: [{ type: 'text', text: `šŸ“„ ${file.path} (${file.language})${sizeWarning}\n\`\`\`${file.language.toLowerCase()}\n${file.content}\n\`\`\``, }], }; } catch (error) { return { content: [{ type: 'text', text: `Error reading file: ${error instanceof Error ? error.message : 'Unknown error'}`, }], }; } } async handleGetProjectStructure(args) { try { const depth = args.depth || 3; const structure = await this.workspaceDetector.getProjectStructure(depth); if (!structure || structure === 'No workspace open') { return { content: [{ type: 'text', text: 'No workspace set. Use set_workspace first.' }], }; } return { content: [{ type: 'text', text: `šŸ“‚ Project Structure (depth: ${depth}):\n\n${structure}` }], }; } catch (error) { return { content: [{ type: 'text', text: `Error getting structure: ${error instanceof Error ? error.message : 'Unknown error'}`, }], }; } } async handleScanWorkspace() { try { const snapshot = await this.workspaceDetector.createSnapshot(); if (!snapshot.rootPath) { return { content: [{ type: 'text', text: 'No workspace set. Use set_workspace first.' }], }; } let response = `šŸ“Š Workspace Scan Results\n\n`; response += `šŸ“ Root: ${snapshot.rootPath}\n\n`; response += `${snapshot.summary}\n\n`; response += `šŸ“‚ Structure:\n${snapshot.structure}\n\n`; response += `šŸ“‹ Scanned ${snapshot.files.length} important files:\n`; snapshot.files.forEach(f => { const icon = f.language.includes('TypeScript') ? 'šŸ“˜' : f.language.includes('JavaScript') ? 'šŸ“œ' : f.language === 'JSON' ? 'šŸ“‹' : 'šŸ“„'; response += `${icon} ${f.path} (${f.language}, ${(f.size / 1024).toFixed(1)}KB)\n`; }); response += `\nUse read_file to view any specific file!`; return { content: [{ type: 'text', text: response }], }; } catch (error) { return { content: [{ type: 'text', text: `Error scanning workspace: ${error instanceof Error ? error.message : 'Unknown error'}`, }], }; } } // ========== V0.3.0 HANDLERS - PREVIEW TOOLS ========== async handleCreateFile(args) { const result = await this.fileWriter.createFile(args.path, args.content, args.overwrite || false); if (!result.success) { return { content: [{ type: 'text', text: result.message }], }; } return { content: [{ type: 'text', text: result.preview + '\n\nāš ļø This is a PREVIEW only. To actually create this file, user must approve and you must call apply_create_file with the same parameters.', }], }; } async handleModifyFile(args) { const result = await this.fileWriter.modifyFile(args.path, args.changes); if (!result.success) { return { content: [{ type: 'text', text: result.message }], }; } return { content: [{ type: 'text', text: result.preview + '\n\nāš ļø This is a PREVIEW only. To actually modify this file, user must approve and you must call apply_modify_file with the same parameters.', }], }; } async handleDeleteFile(args) { const result = await this.fileWriter.deleteFile(args.path); if (!result.success) { return { content: [{ type: 'text', text: result.message }], }; } return { content: [{ type: 'text', text: result.preview + '\n\nāš ļø This is a PREVIEW only. To actually delete this file, user must approve and you must call apply_delete_file with the same path.', }], }; } // ========== V0.3.0 HANDLERS - APPLY TOOLS (NEW!) ========== async handleApplyCreateFile(args) { const result = await this.fileWriter.applyCreateFile(args.path, args.content); return { content: [{ type: 'text', text: result.message }], }; } async handleApplyModifyFile(args) { const result = await this.fileWriter.applyModifyFile(args.path, args.changes); return { content: [{ type: 'text', text: result.message }], }; } async handleApplyDeleteFile(args) { const result = await this.fileWriter.applyDeleteFile(args.path); return { content: [{ type: 'text', text: result.message }], }; } // ========== V0.3.0 HANDLERS - OTHER TOOLS ========== async handleUndoFileChange(args) { const result = await this.fileWriter.undoChange(args.path, args.steps || 1); return { content: [{ type: 'text', text: result.message }], }; } handleSearchFiles(args) { const results = this.fileSearcher.searchFiles(args.pattern, { maxResults: args.maxResults, ignoreCase: args.ignoreCase, }); if (results.length === 0) { return { content: [{ type: 'text', text: `No files found matching pattern: "${args.pattern}"`, }], }; } let response = `šŸ” Found ${results.length} files matching "${args.pattern}":\n\n`; results.forEach((file, i) => { const size = (file.size / 1024).toFixed(1); response += `${i + 1}. ${file.path} (${file.language}, ${size}KB)\n`; }); response += `\nUse read_file to view any of these files.`; return { content: [{ type: 'text', text: response }], }; } handleSearchContent(args) { const results = this.fileSearcher.searchContent(args.query, { regex: args.regex, caseSensitive: args.caseSensitive, filePattern: args.filePattern, maxResults: args.maxResults, }); if (results.length === 0) { return { content: [{ type: 'text', text: `No matches found for: "${args.query}"`, }], }; } let response = `šŸ” Found ${results.length} matches for "${args.query}":\n\n`; results.slice(0, 20).forEach((match, i) => { response += `${i + 1}. ${match.path}:${match.line}\n`; response += ` ${match.content}\n\n`; }); if (results.length > 20) { response += `... and ${results.length - 20} more matches`; } return { content: [{ type: 'text', text: response }], }; } handleFindSymbol(args) { const results = this.fileSearcher.findSymbol(args.symbol, args.type); if (results.length === 0) { return { content: [{ type: 'text', text: `Symbol "${args.symbol}" not found`, }], }; } let response = `šŸ” Found ${results.length} definition(s) of "${args.symbol}":\n\n`; results.forEach((match, i) => { response += `${i + 1}. ${match.path}:${match.line}\n`; response += ` ${match.content}\n\n`; }); return { content: [{ type: 'text', text: response }], }; } handleGitStatus() { if (!this.gitIntegration) { return { content: [{ type: 'text', text: 'No workspace set. Use set_workspace first.' }], }; } const status = this.gitIntegration.getStatus(); if (!status) { return { content: [{ type: 'text', text: 'Not a git repository' }], }; } let response = `šŸ”„ Git Status\n\n`; response += `šŸ“ Branch: ${status.branch}`; if (status.ahead > 0) response += ` (ahead ${status.ahead})`; if (status.behind > 0) response += ` (behind ${status.behind})`; response += `\n\n`; if (status.clean) { response += `āœ… Working tree clean`; } else { if (status.staged.length > 0) { response += `šŸ“ Staged (${status.staged.length}):\n`; status.staged.slice(0, 10).forEach(f => response += ` • ${f}\n`); if (status.staged.length > 10) { response += ` ... and ${status.staged.length - 10} more\n`; } response += `\n`; } if (status.modified.length > 0) { response += `āœļø Modified (${status.modified.length}):\n`; status.modified.slice(0, 10).forEach(f => response += ` • ${f}\n`); if (status.modified.length > 10) { response += ` ... and ${status.modified.length - 10} more\n`; } response += `\n`; } if (status.untracked.length > 0) { response += `ā“ Untracked (${status.untracked.length}):\n`; status.untracked.slice(0, 10).forEach(f => response += ` • ${f}\n`); if (status.untracked.length > 10) { response += ` ... and ${status.untracked.length - 10} more\n`; } } } return { content: [{ type: 'text', text: response }], }; } handleGitDiff(args) { if (!this.gitIntegration) { return { content: [{ type: 'text', text: 'No workspace set. Use set_workspace first.' }], }; } const diff = this.gitIntegration.getDiff(args.path, args.staged); if (!diff) { return { content: [{ type: 'text', text: 'Not a git repository or no changes' }], }; } if (diff.trim().length === 0) { return { content: [{ type: 'text', text: 'No changes to show' }], }; } return { content: [{ type: 'text', text: `šŸ“Š Git Diff${args.staged ? ' (staged)' : ''}:\n\n\`\`\`diff\n${diff}\n\`\`\``, }], }; } handleGitBranchInfo(args) { if (!this.gitIntegration) { return { content: [{ type: 'text', text: 'No workspace set. Use set_workspace first.' }], }; } const info = this.gitIntegration.getBranchInfo(args.action || 'current'); if (!info) { return { content: [{ type: 'text', text: 'Not a git repository' }], }; } if (typeof info === 'string') { return { content: [{ type: 'text', text: `šŸ“ Current branch: ${info}` }], }; } let response = `šŸ“ Git Branches\n\n`; response += `Current: ${info.current}\n\n`; if (info.all.length > 0) { response += `All branches (${info.all.length}):\n`; info.all.slice(0, 20).forEach(b => { const marker = b === info.current ? '→ ' : ' '; response += `${marker}${b}\n`; }); } if (info.recent.length > 0) { response += `\nRecent branches:\n`; info.recent.forEach(b => { const marker = b.name === info.current ? '→ ' : ' '; response += `${marker}${b.name} (${b.lastCommit})\n`; }); } return { content: [{ type: 'text', text: response }], }; } handleSuggestCommitMessage(args) { if (!this.gitIntegration) { return { content: [{ type: 'text', text: 'No workspace set. Use set_workspace first.' }], };