From cc8c8716d14d32c384dd50dbdd894bc3d2f23879 Mon Sep 17 00:00:00 2001 From: Lauren Tan Date: Wed, 16 Apr 2025 17:39:42 -0400 Subject: [PATCH 1/2] [mcp] Add inspect script Uses https://github.com/modelcontextprotocol/inspector to inspect and debug the mcp server. `yarn workspace react-mcp-server dev` will build the server in watch mode and launch the inspector. Default address is http://127.0.0.1:6274. --- compiler/packages/react-mcp-server/package.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/compiler/packages/react-mcp-server/package.json b/compiler/packages/react-mcp-server/package.json index 49e5a542d6a41..d4c270513aa7f 100644 --- a/compiler/packages/react-mcp-server/package.json +++ b/compiler/packages/react-mcp-server/package.json @@ -8,6 +8,8 @@ "scripts": { "build": "rimraf dist && tsup", "test": "echo 'no tests'", + "dev": "concurrently --kill-others -n build,inspect \"yarn run watch\" \"wait-on dist/index.js && yarn run inspect\"", + "inspect": "npx @modelcontextprotocol/inspector node dist/index.js", "watch": "yarn build --watch" }, "dependencies": { From 1607df2c7dddaf4ed2d78c5d8039739c31243d5d Mon Sep 17 00:00:00 2001 From: Lauren Tan Date: Wed, 16 Apr 2025 17:39:42 -0400 Subject: [PATCH 2/2] [mcp] Dedupe docs Previously the resource would return a bunch of dupes because the algolia results would return multiple hashes (headings) for the same url. --- .../packages/react-mcp-server/src/index.ts | 25 ++++++++++++++++--- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/compiler/packages/react-mcp-server/src/index.ts b/compiler/packages/react-mcp-server/src/index.ts index 9f81de88ce9ab..fbe5f58f66143 100644 --- a/compiler/packages/react-mcp-server/src/index.ts +++ b/compiler/packages/react-mcp-server/src/index.ts @@ -40,14 +40,30 @@ const server = new McpServer({ version: '0.0.0', }); +function slugify(heading: string): string { + return heading + .split(' ') + .map(w => w.toLowerCase()) + .join('-'); +} + // TODO: how to verify this works? server.resource( 'docs', new ResourceTemplate('docs://{message}', {list: undefined}), - async (uri, {message}) => { + async (_uri, {message}) => { const hits = await queryAlgolia(message); + const deduped = new Map(); + for (const hit of hits) { + // drop hashes to dedupe properly + const u = new URL(hit.url); + if (deduped.has(u.pathname)) { + continue; + } + deduped.set(u.pathname, hit); + } const pages: Array = await Promise.all( - hits.map(hit => { + Array.from(deduped.values()).map(hit => { return fetch(hit.url, { headers: { 'User-Agent': @@ -70,16 +86,17 @@ server.resource( .filter(html => html !== null) .map(html => { const $ = cheerio.load(html); + const title = encodeURIComponent(slugify($('h1').text())); // react.dev should always have at least one
with the main content const article = $('article').html(); if (article != null) { return { - uri: uri.href, + uri: `docs://${title}`, text: turndownService.turndown(article), }; } else { return { - uri: uri.href, + uri: `docs://${title}`, // Fallback to converting the whole page to markdown text: turndownService.turndown($.html()), };