chore(FEEDBACK-0001): linting
This commit is contained in:
@@ -32,8 +32,8 @@ Implement a Model Context Protocol (MCP) server that exposes `resolve-library-id
|
||||
|
||||
```json
|
||||
{
|
||||
"@modelcontextprotocol/sdk": "^1.25.1",
|
||||
"zod": "^4.3.4"
|
||||
"@modelcontextprotocol/sdk": "^1.25.1",
|
||||
"zod": "^4.3.4"
|
||||
}
|
||||
```
|
||||
|
||||
@@ -46,189 +46,190 @@ Implement a Model Context Protocol (MCP) server that exposes `resolve-library-id
|
||||
|
||||
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 { 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: {} },
|
||||
}
|
||||
{
|
||||
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"
|
||||
),
|
||||
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)'
|
||||
),
|
||||
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'],
|
||||
},
|
||||
},
|
||||
],
|
||||
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;
|
||||
const { name, arguments: args } = request.params;
|
||||
|
||||
if (name === 'resolve-library-id') {
|
||||
const { libraryName, query } = ResolveLibraryIdSchema.parse(args);
|
||||
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 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 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 }],
|
||||
};
|
||||
}
|
||||
const text = await response.text();
|
||||
return {
|
||||
content: [{ type: 'text', text }]
|
||||
};
|
||||
}
|
||||
|
||||
if (name === 'query-docs') {
|
||||
const { libraryId, query, tokens } = QueryDocsSchema.parse(args);
|
||||
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 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 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 }],
|
||||
};
|
||||
}
|
||||
const text = await response.text();
|
||||
return {
|
||||
content: [{ type: 'text', text }]
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
content: [{ type: 'text', text: `Unknown tool: ${name}` }],
|
||||
isError: true,
|
||||
};
|
||||
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
|
||||
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);
|
||||
process.stderr.write(`MCP server error: ${err.message}\n`);
|
||||
process.exit(1);
|
||||
});
|
||||
```
|
||||
|
||||
@@ -238,18 +239,19 @@ main().catch((err) => {
|
||||
|
||||
```json
|
||||
{
|
||||
"scripts": {
|
||||
"mcp:start": "node --experimental-vm-modules src/mcp/index.ts"
|
||||
}
|
||||
"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"
|
||||
}
|
||||
"scripts": {
|
||||
"mcp:start": "tsx src/mcp/index.ts"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -261,30 +263,31 @@ 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"
|
||||
}
|
||||
}
|
||||
}
|
||||
"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"
|
||||
}
|
||||
}
|
||||
}
|
||||
"mcpServers": {
|
||||
"trueref": {
|
||||
"command": "npx",
|
||||
"args": ["tsx", "/path/to/trueref/src/mcp/index.ts"],
|
||||
"env": {
|
||||
"TRUEREF_API_URL": "http://localhost:5173"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -295,13 +298,15 @@ Or with tsx for development:
|
||||
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 -->
|
||||
---
|
||||
## <!-- .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
|
||||
|
||||
Reference in New Issue
Block a user