8.4 KiB
8.4 KiB
TRUEREF-0011 — MCP Server (stdio Transport)
Priority: P0 Status: Pending Depends On: TRUEREF-0010 Blocks: TRUEREF-0012
Overview
Implement a Model Context Protocol (MCP) server that exposes resolve-library-id and query-docs tools via stdio transport. This is the primary integration point for AI coding assistants (Claude Code, Cursor, Zed, etc.). The tool names, input schemas, and output formats are intentionally identical to context7's MCP tools to enable drop-in compatibility.
Acceptance Criteria
- MCP server binary entry point (
src/mcp/index.ts) runnable vianodeortsx resolve-library-idtool implemented with identical schema to context7query-docstool implemented with identical schema to context7- Both tools call the local TrueRef REST API (configurable base URL)
- Server identifies as
io.github.trueref/truerefto Claude Code - stdio transport via
@modelcontextprotocol/sdk TRUEREF_API_URLenv var configures the base URL (default:http://localhost:5173)- npm script
mcp:startinpackage.json - Instructions for adding to Claude Code
.mcp.jsonin README - Integration test that starts the MCP server and exercises both tools
Dependencies to Add
{
"@modelcontextprotocol/sdk": "^1.25.1",
"zod": "^4.3.4"
}
MCP Server Implementation
// src/mcp/index.ts
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
CallToolRequestSchema,
ListToolsRequestSchema,
} from '@modelcontextprotocol/sdk/types.js';
import { z } from 'zod';
const API_BASE = process.env.TRUEREF_API_URL ?? 'http://localhost:5173';
const server = new Server(
{
name: 'io.github.trueref/trueref',
version: '1.0.0',
},
{
capabilities: { tools: {} },
}
);
// Tool schemas — identical to context7 for drop-in compatibility
const ResolveLibraryIdSchema = z.object({
libraryName: z.string().describe(
'Library name to search for and resolve to a TrueRef library ID'
),
query: z.string().describe(
"The user's question or context to help rank results"
),
});
const QueryDocsSchema = z.object({
libraryId: z.string().describe(
'The TrueRef library ID obtained from resolve-library-id, e.g. /facebook/react'
),
query: z.string().describe(
'Specific question about the library to retrieve relevant documentation'
),
tokens: z.number().optional().describe(
'Maximum token budget for the response (default: 10000)'
),
});
server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [
{
name: 'resolve-library-id',
description: [
'Searches TrueRef to find a library matching the given name.',
'Returns a list of matching libraries with their IDs.',
'ALWAYS call this tool before query-docs to get the correct library ID.',
'Call at most 3 times per user question.',
].join(' '),
inputSchema: {
type: 'object',
properties: {
libraryName: {
type: 'string',
description: 'Library name to search for',
},
query: {
type: 'string',
description: "User's question for relevance ranking",
},
},
required: ['libraryName', 'query'],
},
},
{
name: 'query-docs',
description: [
'Fetches documentation and code examples from TrueRef for a specific library.',
'Requires a library ID obtained from resolve-library-id.',
'Returns relevant snippets formatted for LLM consumption.',
'Call at most 3 times per user question.',
].join(' '),
inputSchema: {
type: 'object',
properties: {
libraryId: {
type: 'string',
description: 'TrueRef library ID, e.g. /facebook/react',
},
query: {
type: 'string',
description: 'Specific question about the library',
},
tokens: {
type: 'number',
description: 'Max token budget (default: 10000)',
},
},
required: ['libraryId', 'query'],
},
},
],
}));
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
if (name === 'resolve-library-id') {
const { libraryName, query } = ResolveLibraryIdSchema.parse(args);
const url = new URL(`${API_BASE}/api/v1/libs/search`);
url.searchParams.set('libraryName', libraryName);
url.searchParams.set('query', query);
url.searchParams.set('type', 'txt');
const response = await fetch(url.toString());
if (!response.ok) {
return {
content: [{
type: 'text',
text: `Error searching libraries: ${response.status} ${response.statusText}`,
}],
isError: true,
};
}
const text = await response.text();
return {
content: [{ type: 'text', text }],
};
}
if (name === 'query-docs') {
const { libraryId, query, tokens } = QueryDocsSchema.parse(args);
const url = new URL(`${API_BASE}/api/v1/context`);
url.searchParams.set('libraryId', libraryId);
url.searchParams.set('query', query);
url.searchParams.set('type', 'txt');
if (tokens) url.searchParams.set('tokens', String(tokens));
const response = await fetch(url.toString());
if (!response.ok) {
const status = response.status;
if (status === 404) {
return {
content: [{
type: 'text',
text: `Library "${libraryId}" not found. Please run resolve-library-id first.`,
}],
isError: true,
};
}
if (status === 503) {
return {
content: [{
type: 'text',
text: `Library "${libraryId}" is currently being indexed. Please try again in a moment.`,
}],
isError: true,
};
}
return {
content: [{
type: 'text',
text: `Error fetching documentation: ${response.status} ${response.statusText}`,
}],
isError: true,
};
}
const text = await response.text();
return {
content: [{ type: 'text', text }],
};
}
return {
content: [{ type: 'text', text: `Unknown tool: ${name}` }],
isError: true,
};
});
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
// Server runs until process exits
}
main().catch((err) => {
process.stderr.write(`MCP server error: ${err.message}\n`);
process.exit(1);
});
Package.json Scripts
{
"scripts": {
"mcp:start": "node --experimental-vm-modules src/mcp/index.ts"
}
}
Or with tsx for TypeScript-direct execution:
{
"scripts": {
"mcp:start": "tsx src/mcp/index.ts"
}
}
Claude Code Integration
Users add to .mcp.json:
{
"mcpServers": {
"trueref": {
"command": "node",
"args": ["/path/to/trueref/dist/mcp/index.js"],
"env": {
"TRUEREF_API_URL": "http://localhost:5173"
}
}
}
}
Or with tsx for development:
{
"mcpServers": {
"trueref": {
"command": "npx",
"args": ["tsx", "/path/to/trueref/src/mcp/index.ts"],
"env": {
"TRUEREF_API_URL": "http://localhost:5173"
}
}
}
}
System Prompt / Rules
The MCP server should include a resources list item (optional) or the library responses themselves prepend rules. Additionally, users should add a Claude rule file:
<!-- .claude/rules/trueref.md -->
---
description: Use TrueRef to retrieve documentation for indexed libraries
alwaysApply: true
---
When answering questions about indexed libraries, always use the TrueRef MCP tools:
1. Call `resolve-library-id` with the library name and the user's question to get the library ID
2. Call `query-docs` with the library ID and question to retrieve relevant documentation
3. Use the returned documentation to answer the question accurately
Never rely on training data alone for library APIs that may have changed.
Files to Create
src/mcp/index.ts— MCP server entry pointsrc/mcp/tools/resolve-library-id.ts— tool handlersrc/mcp/tools/query-docs.ts— tool handlersrc/mcp/client.ts— HTTP client for TrueRef API.claude/rules/trueref.md— Claude Code rule file