Skip to main content
Glama
index.ts20.8 kB
/** * Agent Synch MCP Server - Entry Point * Exposes the memory bank as MCP tools for AI agents. */ import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { CallToolRequestSchema, ListToolsRequestSchema, } from '@modelcontextprotocol/sdk/types.js'; import { AgentSynchStorage } from './storage.js'; import { LockManager } from './lock-manager.js'; // Initialize storage and lock manager const storage = new AgentSynchStorage(); const lockManager = new LockManager(process.env.HOME || process.env.USERPROFILE || ''); // Create MCP server const server = new Server( { name: 'agent-synch', version: '1.0.0', }, { capabilities: { tools: {}, }, } ); // --- Tool Definitions --- server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools: [ { name: 'get_active_context', description: 'Get the active context for a project. Returns the current summary, focus, and task graph state.', inputSchema: { type: 'object', properties: { project_id: { type: 'string', description: 'Project identifier (use "global" for cross-project context)', }, }, required: ['project_id'], }, }, { name: 'set_active_context', description: 'Update the active context for a project. Use this to persist your current working state.', inputSchema: { type: 'object', properties: { project_id: { type: 'string', description: 'Project identifier' }, summary: { type: 'string', description: 'Summary of current state' }, focus: { type: 'string', description: 'Current focus area' }, }, required: ['project_id', 'summary'], }, }, { name: 'file_to_cabinet', description: 'Index a file into the Filing Cabinet for fast future retrieval. Provide a summary and key metadata.', inputSchema: { type: 'object', properties: { project_id: { type: 'string', description: 'Project identifier' }, file_path: { type: 'string', description: 'Original file path' }, summary: { type: 'string', description: 'Brief summary of file purpose' }, key_exports: { type: 'array', items: { type: 'string' }, description: 'Key exports/functions in the file', }, dependencies: { type: 'array', items: { type: 'string' }, description: 'Files this imports from', }, }, required: ['project_id', 'file_path', 'summary'], }, }, { name: 'get_from_cabinet', description: 'Retrieve a previously indexed file from the Filing Cabinet.', inputSchema: { type: 'object', properties: { project_id: { type: 'string', description: 'Project identifier' }, file_path: { type: 'string', description: 'Original file path' }, }, required: ['project_id', 'file_path'], }, }, { name: 'search_memory', description: 'Search across all indexed content in the memory bank.', inputSchema: { type: 'object', properties: { query: { type: 'string', description: 'Search query' }, project_id: { type: 'string', description: 'Project to search in (omit for global search)', }, }, required: ['query'], }, }, { name: 'list_projects', description: 'List all projects in the memory bank.', inputSchema: { type: 'object', properties: {}, }, }, { name: 'get_spatial_map', description: 'Get the "PC as Rooms" spatial map for a project, showing folder structure and connections.', inputSchema: { type: 'object', properties: { project_id: { type: 'string', description: 'Project identifier' }, }, required: ['project_id'], }, }, { name: 'add_room', description: 'Add a folder as a "room" to the spatial map with description and depth.', inputSchema: { type: 'object', properties: { project_id: { type: 'string', description: 'Project identifier' }, path: { type: 'string', description: 'Folder path' }, description: { type: 'string', description: 'Description of this folder' }, depth: { type: 'number', description: 'Folder depth from project root' }, key_items: { type: 'array', items: { type: 'string' }, description: 'Important files in this folder', }, }, required: ['project_id', 'path', 'description', 'depth'], }, }, { name: 'link_rooms', description: 'Connect two rooms in the spatial map (bidirectional link).', inputSchema: { type: 'object', properties: { project_id: { type: 'string', description: 'Project identifier' }, room_a: { type: 'string', description: 'First room path' }, room_b: { type: 'string', description: 'Second room path' }, }, required: ['project_id', 'room_a', 'room_b'], }, }, // --- Bug Logging Tools --- { name: 'log_bug', description: 'Log a bug for the agent to fix later. Use this when you encounter an error or unexpected behavior.', inputSchema: { type: 'object', properties: { project_id: { type: 'string', description: 'Project identifier' }, title: { type: 'string', description: 'Brief bug title' }, description: { type: 'string', description: 'Detailed description of the bug' }, stack_trace: { type: 'string', description: 'Stack trace if available' }, file_path: { type: 'string', description: 'File where bug occurred' }, line_number: { type: 'number', description: 'Line number of error' }, severity: { type: 'string', enum: ['low', 'medium', 'high', 'critical'], description: 'Bug severity' }, }, required: ['project_id', 'title', 'description', 'severity'], }, }, { name: 'get_bugs', description: 'Get all bugs for a project. Use this to find bugs that need fixing.', inputSchema: { type: 'object', properties: { project_id: { type: 'string', description: 'Project identifier' }, status: { type: 'string', enum: ['open', 'in_progress', 'resolved'], description: 'Filter by status' }, }, required: ['project_id'], }, }, { name: 'resolve_bug', description: 'Mark a bug as resolved with a resolution description.', inputSchema: { type: 'object', properties: { project_id: { type: 'string', description: 'Project identifier' }, bug_id: { type: 'string', description: 'Bug ID to resolve' }, resolution: { type: 'string', description: 'How the bug was fixed' }, }, required: ['project_id', 'bug_id', 'resolution'], }, }, // --- Server Configuration Tools --- { name: 'configure_server', description: 'FIRST-RUN SETUP: Configure the Agent Synch server location. Call this on first use to tell all future agents where the server lives.', inputSchema: { type: 'object', properties: { server_path: { type: 'string', description: 'Absolute path to the Agent Synch MCP server' }, }, required: ['server_path'], }, }, { name: 'get_server_info', description: 'Get the Agent Synch server configuration. Returns server path and version info.', inputSchema: { type: 'object', properties: {}, }, }, // --- Lock Management Tools --- { name: 'acquire_lock', description: 'Acquire a lock on a resource for concurrent agent access. Used by agent swarms.', inputSchema: { type: 'object', properties: { resource_id: { type: 'string', description: 'Resource to lock (e.g., file path or project ID)' }, agent_id: { type: 'string', description: 'Your unique agent identifier' }, operation: { type: 'string', description: 'Type of operation: read or write' }, timeout_ms: { type: 'number', description: 'Lock timeout in ms (default: 30000)' }, }, required: ['resource_id', 'agent_id', 'operation'], }, }, { name: 'release_lock', description: 'Release a lock on a resource.', inputSchema: { type: 'object', properties: { resource_id: { type: 'string', description: 'Resource to unlock' }, agent_id: { type: 'string', description: 'Your unique agent identifier' }, }, required: ['resource_id', 'agent_id'], }, }, { name: 'get_lock_status', description: 'Check lock status and queue for a resource.', inputSchema: { type: 'object', properties: { resource_id: { type: 'string', description: 'Resource to check' }, }, required: ['resource_id'], }, }, // --- Event-Based Context Tools --- { name: 'emit_context_event', description: 'Emit a context event for agent-to-agent handoff. Use "handoff" when passing work to another agent, "checkpoint" for progress saves, "error" for issues, "complete" when done.', inputSchema: { type: 'object', properties: { project_id: { type: 'string', description: 'Project identifier' }, agent_id: { type: 'string', description: 'Your unique agent identifier' }, event_type: { type: 'string', enum: ['handoff', 'checkpoint', 'error', 'complete'], description: 'Type of event' }, summary: { type: 'string', description: 'Summary for the next agent' }, focus: { type: 'string', description: 'Current focus area' }, metadata: { type: 'object', description: 'Additional context data' }, }, required: ['project_id', 'agent_id', 'event_type', 'summary'], }, }, { name: 'get_context_events', description: 'Get recent context events for a project. Use to understand what previous agents did.', inputSchema: { type: 'object', properties: { project_id: { type: 'string', description: 'Project identifier' }, limit: { type: 'number', description: 'Max events to return (default: 10)' }, }, required: ['project_id'], }, }, ], }; }); // --- Tool Handlers --- server.setRequestHandler(CallToolRequestSchema, async (request) => { const { name, arguments: args } = request.params; if (!args) { return { content: [{ type: 'text', text: 'Error: Missing arguments' }], isError: true, }; } try { switch (name) { case 'get_active_context': { const context = await storage.getActiveContext(args.project_id as string); return { content: [ { type: 'text', text: context ? JSON.stringify(context, null, 2) : 'No active context found.', }, ], }; } case 'set_active_context': { await storage.setActiveContext(args.project_id as string, { summary: args.summary as string, focus: args.focus as string | undefined, lastUpdated: new Date().toISOString(), }); return { content: [{ type: 'text', text: 'Active context updated successfully.' }], }; } case 'file_to_cabinet': { await storage.indexFile(args.project_id as string, { originalPath: args.file_path as string, summary: args.summary as string, keyExports: args.key_exports as string[] | undefined, dependencies: args.dependencies as string[] | undefined, }); return { content: [{ type: 'text', text: `Filed ${args.file_path} to cabinet.` }], }; } case 'get_from_cabinet': { const entry = await storage.getFileFromCabinet( args.project_id as string, args.file_path as string ); return { content: [ { type: 'text', text: entry ? JSON.stringify(entry, null, 2) : 'File not found in cabinet.', }, ], }; } case 'search_memory': { const results = await storage.searchMemory( args.query as string, (args.project_id as string) || undefined ); return { content: [{ type: 'text', text: JSON.stringify(results, null, 2) }], }; } case 'list_projects': { const projects = await storage.listProjects(); return { content: [{ type: 'text', text: JSON.stringify(projects, null, 2) }], }; } case 'get_spatial_map': { const map = await storage.getSpatialMap(args.project_id as string); return { content: [{ type: 'text', text: JSON.stringify(map, null, 2) }], }; } case 'add_room': { await storage.addRoom(args.project_id as string, { path: args.path as string, description: args.description as string, depth: args.depth as number, connectedRooms: [], keyItems: (args.key_items as string[]) || [], }); return { content: [{ type: 'text', text: `Room ${args.path} added to spatial map.` }], }; } case 'link_rooms': { await storage.linkRooms( args.project_id as string, args.room_a as string, args.room_b as string ); return { content: [{ type: 'text', text: `Linked ${args.room_a} <-> ${args.room_b}.` }], }; } case 'log_bug': { const bugId = await storage.logBug({ projectId: args.project_id as string, title: args.title as string, description: args.description as string, stackTrace: args.stack_trace as string | undefined, filePath: args.file_path as string | undefined, lineNumber: args.line_number as number | undefined, severity: args.severity as 'low' | 'medium' | 'high' | 'critical', }); return { content: [{ type: 'text', text: `Bug logged: ${bugId}\n\nThe agent should attempt to fix this bug. Use \`resolve_bug\` when fixed.` }], }; } case 'get_bugs': { const bugs = await storage.getBugs( args.project_id as string, args.status as string | undefined ); if (bugs.length === 0) { return { content: [{ type: 'text', text: 'No bugs found. Great job! 🎉' }], }; } return { content: [{ type: 'text', text: `Found ${bugs.length} bug(s):\n\n${JSON.stringify(bugs, null, 2)}\n\n**Agent Behavior**: You should attempt to fix open bugs automatically.` }], }; } case 'resolve_bug': { await storage.resolveBug( args.bug_id as string, args.project_id as string, args.resolution as string ); return { content: [{ type: 'text', text: `Bug ${args.bug_id} marked as resolved.` }], }; } case 'configure_server': { await storage.setServerConfig({ serverPath: args.server_path as string, discoveredAt: new Date().toISOString(), lastVerified: new Date().toISOString(), version: '1.0.0', }); return { content: [{ type: 'text', text: `Server configured at: ${args.server_path}\n\nAll future agents will know where Agent Synch lives.` }], }; } case 'get_server_info': { const config = await storage.getServerConfig(); const isFirstRun = await storage.isFirstRun(); if (isFirstRun) { return { content: [{ type: 'text', text: '⚠️ FIRST RUN DETECTED!\n\nPlease ask the user: "Where is the Agent Synch MCP server located?"\nThen call `configure_server` with the absolute path.\n\nExample: c:/Users/username/Desktop/Agent Synch/mcp-server' }], }; } return { content: [{ type: 'text', text: JSON.stringify(config, null, 2) }], }; } // --- Lock Management Handlers --- case 'acquire_lock': { const acquired = await lockManager.acquireLock( args.resource_id as string, args.agent_id as string, args.operation as string, args.timeout_ms as number | undefined ); if (acquired) { return { content: [{ type: 'text', text: `🔒 Lock acquired on ${args.resource_id}` }], }; } return { content: [{ type: 'text', text: `⏳ Timeout waiting for lock on ${args.resource_id}` }], isError: true, }; } case 'release_lock': { const released = await lockManager.releaseLock( args.resource_id as string, args.agent_id as string ); if (released) { return { content: [{ type: 'text', text: `🔓 Lock released on ${args.resource_id}` }], }; } return { content: [{ type: 'text', text: `Lock not held by ${args.agent_id}` }], isError: true, }; } case 'get_lock_status': { const lock = lockManager.getLockStatus(args.resource_id as string); const queueLength = lockManager.getQueueLength(args.resource_id as string); if (!lock) { return { content: [{ type: 'text', text: `Resource ${args.resource_id} is unlocked. Queue: ${queueLength}` }], }; } return { content: [{ type: 'text', text: `🔒 Locked by ${lock.agentId} (${lock.operation})\nExpires: ${lock.expiresAt}\nQueue: ${queueLength}` }], }; } // --- Event-Based Context Handlers --- case 'emit_context_event': { const event = await storage.emitContextEvent({ projectId: args.project_id as string, agentId: args.agent_id as string, eventType: args.event_type as 'handoff' | 'checkpoint' | 'error' | 'complete', summary: args.summary as string, focus: args.focus as string | undefined, metadata: args.metadata as Record<string, unknown> | undefined, }); const emoji = { handoff: '🤝', checkpoint: '📍', error: '❌', complete: '✅' }[event.eventType]; return { content: [{ type: 'text', text: `${emoji} Event emitted: ${event.id}\n\n${event.eventType === 'handoff' ? 'Active context updated for next agent.' : ''}` }], }; } case 'get_context_events': { const events = await storage.getContextEvents( args.project_id as string, args.limit as number | undefined ); if (events.length === 0) { return { content: [{ type: 'text', text: 'No context events found. You are the first agent on this project!' }], }; } const summary = events.map((e) => `- [${e.eventType}] ${e.agentId} @ ${e.timestamp}: ${e.summary.substring(0, 100)}...`).join('\n'); return { content: [{ type: 'text', text: `Recent events (${events.length}):\n\n${summary}\n\nUse the latest handoff event to understand context.` }], }; } default: throw new Error(`Unknown tool: ${name}`); } } catch (error) { return { content: [{ type: 'text', text: `Error: ${(error as Error).message}` }], isError: true, }; } }); // --- Start Server --- async function main() { await storage.initialize(); await lockManager.initialize(); const transport = new StdioServerTransport(); await server.connect(transport); console.error('Agent Synch MCP Server running on stdio'); } main().catch(console.error);

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/Mnehmos/mnehmos.synch.mcp'

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