/** * TrueRef MCP Server — stdio and HTTP transports * * Exposes resolve-library-id and query-docs tools, identical to context7's * MCP interface, for drop-in compatibility with Claude Code, Cursor, and * other MCP-aware AI coding assistants. * * Configuration: * TRUEREF_API_URL Base URL for the TrueRef REST API (default: http://localhost:5173) * * Usage (stdio, default): * npx tsx src/mcp/index.ts * * Usage (HTTP): * npx tsx src/mcp/index.ts --transport http [--port 3001] */ import { parseArgs } from 'node:util'; import { createServer } from 'node:http'; import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js'; import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js'; import { RESOLVE_LIBRARY_ID_TOOL, handleResolveLibraryId } from './tools/resolve-library-id.js'; import { QUERY_DOCS_TOOL, handleQueryDocs } from './tools/query-docs.js'; // --------------------------------------------------------------------------- // CLI args // --------------------------------------------------------------------------- const { values: cliArgs } = parseArgs({ options: { transport: { type: 'string', default: 'stdio' }, port: { type: 'string', default: process.env.PORT ?? '3001' } }, strict: false }); // --------------------------------------------------------------------------- // Server factory // --------------------------------------------------------------------------- function createMcpServer(): Server { const server = new Server( { name: 'io.github.trueref/trueref', version: '1.0.0' }, { capabilities: { tools: {} } } ); server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: [RESOLVE_LIBRARY_ID_TOOL, QUERY_DOCS_TOOL] })); server.setRequestHandler(CallToolRequestSchema, async (request) => { const { name, arguments: args } = request.params; if (name === 'resolve-library-id') { return handleResolveLibraryId(args); } if (name === 'query-docs') { return handleQueryDocs(args); } return { content: [{ type: 'text' as const, text: `Unknown tool: ${name}` }], isError: true }; }); return server; } // --------------------------------------------------------------------------- // HTTP transport // --------------------------------------------------------------------------- async function startHttp(port: number): Promise { const httpServer = createServer(async (req, res) => { const url = new URL(req.url!, `http://localhost:${port}`); // Health check if (url.pathname === '/ping') { res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ ok: true })); return; } // MCP endpoint if (url.pathname === '/mcp') { // CORS headers res.setHeader('Access-Control-Allow-Origin', '*'); res.setHeader('Access-Control-Allow-Methods', 'POST, GET, OPTIONS'); res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Accept'); if (req.method === 'OPTIONS') { res.writeHead(204); res.end(); return; } // Create a fresh server and transport per request (stateless mode) const mcpServer = createMcpServer(); const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined }); await mcpServer.connect(transport); await transport.handleRequest(req, res); return; } res.writeHead(404); res.end('Not Found'); }); httpServer.listen(port, () => { process.stderr.write(`TrueRef MCP server listening on http://localhost:${port}/mcp\n`); }); } // --------------------------------------------------------------------------- // Startup // --------------------------------------------------------------------------- async function main() { if (cliArgs.transport === 'http') { const port = parseInt(cliArgs.port as string, 10); await startHttp(port); } else { const mcpServer = createMcpServer(); const transport = new StdioServerTransport(); await mcpServer.connect(transport); // Server runs until process exits } } main().catch((err) => { process.stderr.write(`MCP server error: ${err instanceof Error ? err.message : String(err)}\n`); process.exit(1); });