55 lines
2.4 KiB
JavaScript
55 lines
2.4 KiB
JavaScript
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);
|
|
}
|