/**
* 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);
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, {
summary: args.summary,
focus: args.focus,
lastUpdated: new Date().toISOString(),
});
return {
content: [{ type: 'text', text: 'Active context updated successfully.' }],
};
}
case 'file_to_cabinet': {
await storage.indexFile(args.project_id, {
originalPath: args.file_path,
summary: args.summary,
keyExports: args.key_exports,
dependencies: args.dependencies,
});
return {
content: [{ type: 'text', text: `Filed ${args.file_path} to cabinet.` }],
};
}
case 'get_from_cabinet': {
const entry = await storage.getFileFromCabinet(args.project_id, args.file_path);
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, args.project_id || 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);
return {
content: [{ type: 'text', text: JSON.stringify(map, null, 2) }],
};
}
case 'add_room': {
await storage.addRoom(args.project_id, {
path: args.path,
description: args.description,
depth: args.depth,
connectedRooms: [],
keyItems: args.key_items || [],
});
return {
content: [{ type: 'text', text: `Room ${args.path} added to spatial map.` }],
};
}
case 'link_rooms': {
await storage.linkRooms(args.project_id, args.room_a, args.room_b);
return {
content: [{ type: 'text', text: `Linked ${args.room_a} <-> ${args.room_b}.` }],
};
}
case 'log_bug': {
const bugId = await storage.logBug({
projectId: args.project_id,
title: args.title,
description: args.description,
stackTrace: args.stack_trace,
filePath: args.file_path,
lineNumber: args.line_number,
severity: args.severity,
});
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, args.status);
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, args.project_id, args.resolution);
return {
content: [{ type: 'text', text: `Bug ${args.bug_id} marked as resolved.` }],
};
}
case 'configure_server': {
await storage.setServerConfig({
serverPath: args.server_path,
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, args.agent_id, args.operation, args.timeout_ms);
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, args.agent_id);
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);
const queueLength = lockManager.getQueueLength(args.resource_id);
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,
agentId: args.agent_id,
eventType: args.event_type,
summary: args.summary,
focus: args.focus,
metadata: args.metadata,
});
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, args.limit);
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.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);