@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
JavaScript
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.' }],
};