feat(TRUEREF-0011-0012): implement MCP server with stdio and HTTP transports
- resolve-library-id and query-docs tools with context7-identical schemas - stdio transport for Claude Code, Cursor, and other MCP clients - HTTP transport via StreamableHTTPServerTransport on configurable port - /mcp endpoint with CORS and /ping health check - mcp:start and mcp:http npm scripts - Claude Code rule file at .claude/rules/trueref.md Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -15,6 +15,7 @@
|
|||||||
"test:unit": "vitest",
|
"test:unit": "vitest",
|
||||||
"test": "npm run test:unit -- --run",
|
"test": "npm run test:unit -- --run",
|
||||||
"mcp:start": "tsx src/mcp/index.ts",
|
"mcp:start": "tsx src/mcp/index.ts",
|
||||||
|
"mcp:http": "tsx src/mcp/index.ts --transport http --port 3001",
|
||||||
"db:push": "drizzle-kit push",
|
"db:push": "drizzle-kit push",
|
||||||
"db:generate": "drizzle-kit generate",
|
"db:generate": "drizzle-kit generate",
|
||||||
"db:migrate": "drizzle-kit migrate",
|
"db:migrate": "drizzle-kit migrate",
|
||||||
|
|||||||
104
src/mcp/index.ts
104
src/mcp/index.ts
@@ -1,5 +1,5 @@
|
|||||||
/**
|
/**
|
||||||
* TrueRef MCP Server — stdio transport
|
* TrueRef MCP Server — stdio and HTTP transports
|
||||||
*
|
*
|
||||||
* Exposes resolve-library-id and query-docs tools, identical to context7's
|
* Exposes resolve-library-id and query-docs tools, identical to context7's
|
||||||
* MCP interface, for drop-in compatibility with Claude Code, Cursor, and
|
* MCP interface, for drop-in compatibility with Claude Code, Cursor, and
|
||||||
@@ -8,12 +8,18 @@
|
|||||||
* Configuration:
|
* Configuration:
|
||||||
* TRUEREF_API_URL Base URL for the TrueRef REST API (default: http://localhost:5173)
|
* TRUEREF_API_URL Base URL for the TrueRef REST API (default: http://localhost:5173)
|
||||||
*
|
*
|
||||||
* Usage:
|
* Usage (stdio, default):
|
||||||
* npx tsx src/mcp/index.ts
|
* 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 { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
||||||
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
||||||
|
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
|
||||||
import {
|
import {
|
||||||
CallToolRequestSchema,
|
CallToolRequestSchema,
|
||||||
ListToolsRequestSchema
|
ListToolsRequestSchema
|
||||||
@@ -26,10 +32,23 @@ import {
|
|||||||
import { QUERY_DOCS_TOOL, handleQueryDocs } from './tools/query-docs.js';
|
import { QUERY_DOCS_TOOL, handleQueryDocs } from './tools/query-docs.js';
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Server setup
|
// CLI args
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
const server = new Server(
|
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',
|
name: 'io.github.trueref/trueref',
|
||||||
version: '1.0.0'
|
version: '1.0.0'
|
||||||
@@ -37,21 +56,13 @@ const server = new Server(
|
|||||||
{
|
{
|
||||||
capabilities: { tools: {} }
|
capabilities: { tools: {} }
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
||||||
// Tool registry
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
||||||
tools: [RESOLVE_LIBRARY_ID_TOOL, QUERY_DOCS_TOOL]
|
tools: [RESOLVE_LIBRARY_ID_TOOL, QUERY_DOCS_TOOL]
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
||||||
// Tool dispatch
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
||||||
const { name, arguments: args } = request.params;
|
const { name, arguments: args } = request.params;
|
||||||
|
|
||||||
if (name === 'resolve-library-id') {
|
if (name === 'resolve-library-id') {
|
||||||
@@ -66,16 +77,73 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|||||||
content: [{ type: 'text' as const, text: `Unknown tool: ${name}` }],
|
content: [{ type: 'text' as const, text: `Unknown tool: ${name}` }],
|
||||||
isError: true
|
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
|
// Startup
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
async function main() {
|
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();
|
const transport = new StdioServerTransport();
|
||||||
await server.connect(transport);
|
await mcpServer.connect(transport);
|
||||||
// Server runs until process exits
|
// Server runs until process exits
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
main().catch((err) => {
|
main().catch((err) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user