147 lines
4.3 KiB
TypeScript
147 lines
4.3 KiB
TypeScript
/**
|
|
* 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<void> {
|
|
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);
|
|
});
|