diff --git a/README.md b/README.md index 3cae94f..6ab66ba 100644 --- a/README.md +++ b/README.md @@ -257,7 +257,7 @@ The default import stays small. Add the other pieces only when that screen needs | 📡 Schedules | `@crup/react-timer-hook/schedules` | Polling, cadence callbacks, overdue timing context | 8.62 kB | 3.02 kB | 2.78 kB | | 🧩 Duration | `@crup/react-timer-hook/duration` | `days`, `hours`, `minutes`, `seconds`, `milliseconds` | 318 B | 224 B | 192 B | | 🔎 Diagnostics | `@crup/react-timer-hook/diagnostics` | Optional lifecycle and schedule event logging | 105 B | 115 B | 90 B | -| 🤖 MCP docs server | `react-timer-hook-mcp` | Optional local docs context for MCP clients and coding agents | 6.69 kB | 2.60 kB | 2.25 kB | +| 🤖 MCP docs server | `react-timer-hook-mcp` | Optional local docs context for MCP clients and coding agents | 6.95 kB | 2.72 kB | 2.36 kB | CI writes a size summary to the GitHub Actions UI and posts bundle-size reports on pull requests. @@ -308,11 +308,11 @@ react-timer-hook://recipes It also exposes MCP tools that editors are more likely to call directly: -```txt -get_api_docs -get_recipe -search_docs -``` +| Tool | Title | Description | +| --- | --- | --- | +| `get_api_docs` | Get API docs | Returns compact API notes for `@crup/react-timer-hook`. | +| `get_recipe` | Get recipe | Returns guidance for a named recipe or use case. | +| `search_docs` | Search docs | Searches API and recipe notes for a query. | ## Contributing diff --git a/docs-site/docs/ai.mdx b/docs-site/docs/ai.mdx index 2d8b831..d50a8a8 100644 --- a/docs-site/docs/ai.mdx +++ b/docs-site/docs/ai.mdx @@ -66,11 +66,11 @@ react-timer-hook://recipes It also exposes callable tools for MCP clients that prefer tool calls over resource reads: -```txt -get_api_docs -get_recipe -search_docs -``` +| Tool | Title | Description | +| --- | --- | --- | +| `get_api_docs` | Get API docs | Returns compact API notes for `@crup/react-timer-hook`. | +| `get_recipe` | Get recipe | Returns guidance for a named recipe or use case. | +| `search_docs` | Search docs | Searches API and recipe notes for a query. | Verify locally: diff --git a/docs-site/static/llms-full.txt b/docs-site/static/llms-full.txt index 793b919..5fc7aee 100644 --- a/docs-site/static/llms-full.txt +++ b/docs-site/static/llms-full.txt @@ -105,9 +105,9 @@ Local docs MCP server: The package bundles the MCP server at node_modules/@crup/react-timer-hook/dist/mcp/server.js and exposes it through the react-timer-hook-mcp bin. MCP tools: -- get_api_docs -- get_recipe -- search_docs +- get_api_docs: Get API docs. Returns compact API notes for @crup/react-timer-hook. +- get_recipe: Get recipe. Returns guidance for a named recipe or use case. +- search_docs: Search docs. Searches API and recipe notes for a query. ## Boundaries diff --git a/mcp/server.mjs b/mcp/server.mjs index eb67a25..bfb194c 100644 --- a/mcp/server.mjs +++ b/mcp/server.mjs @@ -94,6 +94,7 @@ const recipes = { const tools = [ { name: 'get_api_docs', + title: 'Get API docs', description: 'Return the compact API notes for @crup/react-timer-hook.', inputSchema: { type: 'object', @@ -103,6 +104,7 @@ const tools = [ }, { name: 'get_recipe', + title: 'Get recipe', description: 'Return guidance for a named recipe or use case.', inputSchema: { type: 'object', @@ -118,6 +120,7 @@ const tools = [ }, { name: 'search_docs', + title: 'Search docs', description: 'Search API and recipe notes for a query.', inputSchema: { type: 'object', @@ -146,6 +149,12 @@ rl.on('line', line => { } const { id, method, params } = request; + const hasId = Object.hasOwn(request, 'id'); + + if (!method) { + if (hasId) respondError(id, -32600, 'Invalid request: missing method.'); + return; + } if (method === 'initialize') { respond(id, { @@ -156,6 +165,15 @@ rl.on('line', line => { return; } + if (method === 'notifications/initialized' || method.startsWith('notifications/')) { + return; + } + + if (method === 'ping') { + if (hasId) respond(id, {}); + return; + } + if (method === 'resources/list') { respond(id, { resources: Object.entries(resources).map(([uri, resource]) => ({ @@ -231,7 +249,7 @@ rl.on('line', line => { return; } - respondError(id, -32601, `Method not found: ${method}`); + if (hasId) respondError(id, -32601, `Method not found: ${method}`); }); function respond(id, result) { @@ -243,6 +261,7 @@ function respondTool(id, text) { } function respondError(id, code, message) { + if (id === undefined) return; process.stdout.write(`${JSON.stringify({ jsonrpc: '2.0', id, error: { code, message } })}\n`); } diff --git a/scripts/check-mcp-server.mjs b/scripts/check-mcp-server.mjs index 30d8607..01a878c 100644 --- a/scripts/check-mcp-server.mjs +++ b/scripts/check-mcp-server.mjs @@ -26,6 +26,7 @@ child.stderr.on('data', chunk => { child.stdin.end( [ JSON.stringify({ jsonrpc: '2.0', id: 1, method: 'initialize', params: {} }), + JSON.stringify({ jsonrpc: '2.0', method: 'notifications/initialized', params: {} }), JSON.stringify({ jsonrpc: '2.0', id: 2, method: 'resources/list', params: {} }), JSON.stringify({ jsonrpc: '2.0', @@ -86,7 +87,10 @@ child.on('close', code => { process.exit(1); } - if (!tools.some(tool => tool.name === 'get_recipe') || !tools.some(tool => tool.name === 'search_docs')) { + if ( + !tools.some(tool => tool.name === 'get_recipe' && tool.title === 'Get recipe') || + !tools.some(tool => tool.name === 'search_docs' && tool.title === 'Search docs') + ) { console.error('MCP tools list is missing expected docs tools.'); process.exit(1); }