import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js'; import { ListToolsRequestSchema, CallToolRequestSchema } from '@modelcontextprotocol/sdk/types.js'; import { externalRegistry } from './external-registry.js'; import { buildCtxFromAgent } from './context.js'; import { recordAudit } from '../db/repos/audit_stub.js'; // --- transport-free helpers (exported for tests) --- export function listExternalTools() { return externalRegistry.listTools().map(({ name, description, input_schema }) => ({ name, description, input_schema })); } export async function callExternalTool(name, args, ctx) { const tool = externalRegistry.getTool(name); if (!tool) throw new Error(`Unknown tool: ${name}`); return tool.handler(args, ctx); } // --- MCP server factory (one per request in stateless mode) --- export function createExternalMcpServer(ctx) { const server = new Server( { name: 'void-external', version: '1.0.0' }, { capabilities: { tools: {} } } ); server.setRequestHandler(ListToolsRequestSchema, () => ({ tools: listExternalTools().map(({ name, description, input_schema }) => ({ name, description, inputSchema: input_schema })) })); server.setRequestHandler(CallToolRequestSchema, async (request) => { const { name, arguments: args = {} } = request.params; try { const result = await callExternalTool(name, args, ctx); recordAudit(ctx.actor, 'mcp_tool_call', 'agent', ctx.agent.id, null, { tool: name, space_id: ctx.space_id }).catch(() => {}); return { content: [{ type: 'text', text: JSON.stringify(result) }], structuredContent: result }; } catch (err) { return { content: [{ type: 'text', text: err.message ?? String(err) }], isError: true }; } }); return server; } // --- Express handler: stateless Streamable HTTP. Requires req.mcpAgent (mcpAuth). --- export async function handleMcp(req, res) { const ctx = buildCtxFromAgent(req.mcpAgent); const server = createExternalMcpServer(ctx); const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined, // stateless enableJsonResponse: true }); res.on('close', () => { try { transport.close(); } catch { /* */ } try { server.close(); } catch { /* */ } }); await server.connect(transport); await transport.handleRequest(req, res, req.body); }