UNPKG

cntx-ui

Version:

Autonomous Repository Intelligence engine with web UI and MCP server. Unified semantic code understanding, local RAG, and agent working memory.

204 lines (203 loc) 8.78 kB
/** * Model Context Protocol (MCP) Server Implementation * Exposes repository intelligence tools to AI agents */ import { readFileSync } from 'fs'; import { join } from 'path'; export class MCPServer { cntxServer; version; constructor(cntxServer, version = '3.0.0') { this.cntxServer = cntxServer; this.version = version; // Listen for MCP requests on stdin process.stdin.on('data', (data) => { this.handleInput(data.toString()); }); } handleInput(input) { try { const lines = input.split('\n').filter(l => l.trim()); for (const line of lines) { const request = JSON.parse(line); this.routeRequest(request); } } catch (e) { // Ignore invalid JSON } } async routeRequest(request) { const { method, params, id } = request; switch (method) { case 'initialize': return this.sendResponse(this.createSuccessResponse(id, { protocolVersion: '2024-11-05', capabilities: { tools: {}, resources: {}, prompts: {} }, serverInfo: { name: 'cntx-ui', version: this.version } })); case 'tools/list': return this.sendResponse(this.handleListTools(id)); case 'tools/call': return this.sendResponse(await this.handleCallTool(params, id)); case 'resources/list': return this.sendResponse(this.handleListResources(id)); case 'prompts/list': return this.sendResponse(this.handleListPrompts(id)); case 'prompts/get': return this.sendResponse(this.handleGetPrompt(params, id)); default: return this.sendResponse(this.createErrorResponse(id, -32601, `Method not found: ${method}`)); } } sendResponse(response) { process.stdout.write(JSON.stringify(response) + '\n'); } createSuccessResponse(id, result) { return { jsonrpc: '2.0', id, result }; } createErrorResponse(id, code, message, data = null) { const error = { code, message }; if (data) error.data = data; return { jsonrpc: '2.0', id, error }; } // --- Handlers --- handleListTools(id) { const tools = this.getToolDefinitions(); return this.createSuccessResponse(id, { tools }); } async handleCallTool(params, id) { const { name, arguments: args } = params; try { if (name.startsWith('agent/')) { const mode = name.split('/')[1]; let result; switch (mode) { case 'discover': result = await this.cntxServer.agentRuntime.discoverCodebase(args); break; case 'query': result = await this.cntxServer.agentRuntime.answerQuery(args.question, args); break; case 'investigate': result = await this.cntxServer.agentRuntime.investigateFeature(args.feature, args); break; default: return this.createErrorResponse(id, -32602, `Unknown agent tool: ${name}`); } return this.createSuccessResponse(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] }); } // Legacy tools mapping switch (name) { case 'artifacts/list': { const artifacts = this.cntxServer.artifactManager.refresh(); return this.createSuccessResponse(id, { content: [{ type: 'text', text: JSON.stringify({ artifacts }, null, 2) }] }); } case 'artifacts/get_openapi': { this.cntxServer.artifactManager.refresh(); const payload = this.cntxServer.artifactManager.getPayload('openapi'); return this.createSuccessResponse(id, { content: [{ type: 'text', text: JSON.stringify(payload, null, 2) }] }); } case 'artifacts/get_navigation': { this.cntxServer.artifactManager.refresh(); const payload = this.cntxServer.artifactManager.getPayload('navigation'); return this.createSuccessResponse(id, { content: [{ type: 'text', text: JSON.stringify(payload, null, 2) }] }); } case 'artifacts/summarize': { const artifacts = this.cntxServer.artifactManager.refresh(); const summary = { openapi: artifacts.find((a) => a.type === 'openapi')?.summary ?? {}, navigation: artifacts.find((a) => a.type === 'navigation')?.summary ?? {} }; return this.createSuccessResponse(id, { content: [{ type: 'text', text: JSON.stringify(summary, null, 2) }] }); } case 'list_bundles': const bundles = this.cntxServer.bundleManager.getAllBundleInfo(); return this.createSuccessResponse(id, { content: [{ type: 'text', text: JSON.stringify(bundles, null, 2) }] }); case 'read_file': return await this.toolReadFile(args, id); default: return this.createErrorResponse(id, -32602, `Tool not found: ${name}`); } } catch (e) { return this.createErrorResponse(id, -32603, e.message); } } async toolReadFile(args, id) { const { path: filePath } = args; try { const fullPath = join(this.cntxServer.CWD, filePath); const content = readFileSync(fullPath, 'utf8'); return this.createSuccessResponse(id, { content: [{ type: 'text', text: content }] }); } catch (e) { return this.createErrorResponse(id, -32603, e.message); } } handleListResources(id) { return this.createSuccessResponse(id, { resources: [] }); } handleListPrompts(id) { return this.createSuccessResponse(id, { prompts: [] }); } handleGetPrompt(params, id) { return this.createErrorResponse(id, -32602, 'Prompt not found'); } getToolDefinitions() { return [ { name: 'agent/discover', description: 'Discovery Mode: Comprehensive architectural overview.', inputSchema: { type: 'object', properties: { scope: { type: 'string' } } } }, { name: 'agent/query', description: 'Query Mode: Answer technical questions.', inputSchema: { type: 'object', properties: { question: { type: 'string' } }, required: ['question'] } }, { name: 'agent/investigate', description: 'Investigation Mode: Suggest integration points.', inputSchema: { type: 'object', properties: { feature: { type: 'string' } }, required: ['feature'] } }, { name: 'artifacts/list', description: 'List normalized project artifacts (OpenAPI and Navigation manifests).', inputSchema: { type: 'object', properties: {} } }, { name: 'artifacts/get_openapi', description: 'Get OpenAPI artifact payload (summary + parsed content when JSON).', inputSchema: { type: 'object', properties: {} } }, { name: 'artifacts/get_navigation', description: 'Get Navigation artifact payload (summary + parsed manifest).', inputSchema: { type: 'object', properties: {} } }, { name: 'artifacts/summarize', description: 'Get compact summaries for OpenAPI and Navigation artifacts.', inputSchema: { type: 'object', properties: {} } }, { name: 'list_bundles', description: 'List all project bundles.', inputSchema: { type: 'object', properties: {} } }, { name: 'read_file', description: 'Read a file.', inputSchema: { type: 'object', properties: { path: { type: 'string' } }, required: ['path'] } } ]; } }