326 lines
7.9 KiB
Markdown
326 lines
7.9 KiB
Markdown
# 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 via `node` or `tsx`
|
|
- [ ] `resolve-library-id` tool implemented with identical schema to context7
|
|
- [ ] `query-docs` tool implemented with identical schema to context7
|
|
- [ ] Both tools call the local TrueRef REST API (configurable base URL)
|
|
- [ ] Server identifies as `io.github.trueref/trueref` to Claude Code
|
|
- [ ] stdio transport via `@modelcontextprotocol/sdk`
|
|
- [ ] `TRUEREF_API_URL` env var configures the base URL (default: `http://localhost:5173`)
|
|
- [ ] npm script `mcp:start` in `package.json`
|
|
- [ ] Instructions for adding to Claude Code `.mcp.json` in README
|
|
- [ ] Integration test that starts the MCP server and exercises both tools
|
|
|
|
---
|
|
|
|
## Dependencies to Add
|
|
|
|
```json
|
|
{
|
|
"@modelcontextprotocol/sdk": "^1.25.1",
|
|
"zod": "^4.3.4"
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## MCP Server Implementation
|
|
|
|
```typescript
|
|
// 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
|
|
|
|
```json
|
|
{
|
|
"scripts": {
|
|
"mcp:start": "node --experimental-vm-modules src/mcp/index.ts"
|
|
}
|
|
}
|
|
```
|
|
|
|
Or with `tsx` for TypeScript-direct execution:
|
|
|
|
```json
|
|
{
|
|
"scripts": {
|
|
"mcp:start": "tsx src/mcp/index.ts"
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Claude Code Integration
|
|
|
|
Users add to `.mcp.json`:
|
|
|
|
```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:
|
|
|
|
```json
|
|
{
|
|
"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:
|
|
|
|
```markdown
|
|
## <!-- .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 point
|
|
- `src/mcp/tools/resolve-library-id.ts` — tool handler
|
|
- `src/mcp/tools/query-docs.ts` — tool handler
|
|
- `src/mcp/client.ts` — HTTP client for TrueRef API
|
|
- `.claude/rules/trueref.md` — Claude Code rule file
|