diff --git a/apps/docs/package.json b/apps/docs/package.json index 52ecf0e..2c699bd 100644 --- a/apps/docs/package.json +++ b/apps/docs/package.json @@ -23,7 +23,7 @@ "dependencies": { "@vercel/agent-readability": "0.7.0", "@vercel/analytics": "2.0.1", - "@vercel/geistdocs": "2.4.1", + "@vercel/geistdocs": "2.7.5", "fumadocs-core": "16.2.2", "fumadocs-mdx": "14.0.4", "fumadocs-ui": "16.2.2", diff --git a/apps/docs/scripts/test-routes.ts b/apps/docs/scripts/test-routes.ts index 9e993fe..98078ec 100644 --- a/apps/docs/scripts/test-routes.ts +++ b/apps/docs/scripts/test-routes.ts @@ -442,6 +442,46 @@ assert.match(publicSkill.body, /^---\nname: issue-graph\n/); assert.equal(publicSkill.body, skill.body); assert.match(publicSkill.body, /issue-graph skills get core/); checks += 2; +const bridge = await request("/api/mcp?webmcp-script", { accept: "*/*" }); +assert.equal(bridge.response.status, 200); +assert.match(bridge.response.headers.get("content-type") ?? "", /javascript/); +assert.match(bridge.body, /registerTool/); +async function rpc(method: string, params: Record) { + const response = await fetch(new URL("/api/mcp", origin), { + method: "POST", + headers: { "content-type": "application/json", accept: "application/json, text/event-stream" }, + body: JSON.stringify({ jsonrpc: "2.0", id: 1, method, params }), + }); + assert.equal(response.status, 200); + const text = await response.text(); + const message = JSON.parse( + response.headers.get("content-type")?.includes("text/event-stream") + ? (text + .split(/\r?\n/) + .find((line) => line.startsWith("data:")) + ?.slice(5) ?? "") + : text, + ); + assert.equal(message.error, undefined); + return message.result; +} +const { tools } = await rpc("tools/list", {}); +assert.deepEqual( + tools.map((tool: { name: string }) => tool.name), + ["search_docs"], +); +const found = await rpc("tools/call", { + name: "search_docs", + arguments: { query: "competing PRs", locale: "en" }, +}); +assert.notEqual(found.isError, true); +const matches = found.content + .filter((item: { type: string }) => item.type === "text") + .flatMap((item: { text: string }) => JSON.parse(item.text)); +assert.ok( + matches.some((item: { url: string }) => item.url === "/docs/backlog#compare-competing-prs"), +); +checks += 3; console.log( `PASS: ${checks} route checks against ${origin.origin}${preview ? " (preview noindex)" : ""}`, ); diff --git a/apps/docs/src/app/api/mcp/route.ts b/apps/docs/src/app/api/mcp/route.ts new file mode 100644 index 0000000..e0a634e --- /dev/null +++ b/apps/docs/src/app/api/mcp/route.ts @@ -0,0 +1,7 @@ +import { createMcpRoute } from "@vercel/geistdocs/routes/mcp"; +import { config } from "@/lib/geistdocs/config"; +import { GET as search } from "../search/route"; + +const handler = createMcpRoute({ config, search }); + +export { handler as GET, handler as POST }; diff --git a/apps/docs/tests/content.test.ts b/apps/docs/tests/content.test.ts index 72bdb78..b9c88ac 100644 --- a/apps/docs/tests/content.test.ts +++ b/apps/docs/tests/content.test.ts @@ -229,7 +229,7 @@ describe("documentation content contract", () => { expect(pkg.name).toBe("@issue-graph/docs"); expect(pkg.private).toBe(true); for (const [name, version] of Object.entries({ - "@vercel/geistdocs": "2.4.1", + "@vercel/geistdocs": "2.7.5", "@vercel/agent-readability": "0.7.0", next: "16.3.5", react: "19.3.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 37b1ed7..76042c6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -37,6 +37,9 @@ importers: apps/dashboard: dependencies: + geist: + specifier: 1.7.2 + version: 1.7.2(next@16.3.5(patch_hash=a620718f9d934500c4f0364aa9312b2539559e0ac911cdc37c97b92e1e9b4550)(@opentelemetry/api@1.9.1)(@types/node@26.6.1)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)) next: specifier: 16.3.5 version: 16.3.5(patch_hash=a620718f9d934500c4f0364aa9312b2539559e0ac911cdc37c97b92e1e9b4550)(@opentelemetry/api@1.9.1)(@types/node@26.6.1)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) @@ -46,17 +49,14 @@ importers: react-dom: specifier: 19.3.0 version: 19.3.0(react@19.3.0) - geist: - specifier: 1.7.2 - version: 1.7.2(next@16.3.5(patch_hash=a620718f9d934500c4f0364aa9312b2539559e0ac911cdc37c97b92e1e9b4550)(@opentelemetry/api@1.9.1)(@types/node@26.6.1)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)) devDependencies: - "@types/node": + '@types/node': specifier: ^26.1.1 version: 26.6.1 - "@types/react": + '@types/react': specifier: ^19.2.7 version: 19.3.0 - "@types/react-dom": + '@types/react-dom': specifier: ^19.2.3 version: 19.3.0(@types/react@19.3.0) typescript: @@ -72,8 +72,8 @@ importers: specifier: 2.0.1 version: 2.0.1(next@16.3.5(patch_hash=a620718f9d934500c4f0364aa9312b2539559e0ac911cdc37c97b92e1e9b4550)(@opentelemetry/api@1.9.1)(@types/node@26.6.1)(react-dom@19.3.0(react@19.3.0))(react@19.3.0))(react@19.3.0) '@vercel/geistdocs': - specifier: 2.4.1 - version: 2.4.1(@svta/cml-cta@1.0.6(@svta/cml-structured-field-values@1.1.3(@svta/cml-utils@1.5.0))(@svta/cml-utils@1.5.0))(@svta/cml-structured-field-values@1.1.3(@svta/cml-utils@1.5.0))(@svta/cml-utils@1.5.0)(@types/mdast@4.0.4)(@types/react-dom@19.3.0(@types/react@19.3.0))(micromark-util-types@2.0.2)(micromark@4.0.2)(next@16.3.5(patch_hash=a620718f9d934500c4f0364aa9312b2539559e0ac911cdc37c97b92e1e9b4550)(@opentelemetry/api@1.9.1)(@types/node@26.6.1)(react-dom@19.3.0(react@19.3.0))(react@19.3.0))(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(tailwindcss@4.3.3)(vite@7.3.6(@types/node@26.6.1)(jiti@2.7.0)(lightningcss@1.33.0)(tsx@4.23.13)(yaml@2.9.1)) + specifier: 2.7.5 + version: 2.7.5(@svta/cml-cta@1.0.6(@svta/cml-structured-field-values@1.1.3(@svta/cml-utils@1.5.0))(@svta/cml-utils@1.5.0))(@svta/cml-structured-field-values@1.1.3(@svta/cml-utils@1.5.0))(@svta/cml-utils@1.5.0)(@types/mdast@4.0.4)(@types/react-dom@19.3.0(@types/react@19.3.0))(micromark-util-types@2.0.2)(micromark@4.0.2)(next@16.3.5(patch_hash=a620718f9d934500c4f0364aa9312b2539559e0ac911cdc37c97b92e1e9b4550)(@opentelemetry/api@1.9.1)(@types/node@26.6.1)(react-dom@19.3.0(react@19.3.0))(react@19.3.0))(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(tailwindcss@4.3.3)(vite@7.3.6(@types/node@26.6.1)(jiti@2.7.0)(lightningcss@1.33.0)(tsx@4.23.13)(yaml@2.9.1)) fumadocs-core: specifier: 16.2.2 version: 16.2.2(@types/react@19.3.0)(next@16.3.5(patch_hash=a620718f9d934500c4f0364aa9312b2539559e0ac911cdc37c97b92e1e9b4550)(@opentelemetry/api@1.9.1)(@types/node@26.6.1)(react-dom@19.3.0(react@19.3.0))(react@19.3.0))(react-dom@19.3.0(react@19.3.0))(react@19.3.0) @@ -763,6 +763,14 @@ packages: '@mermaid-js/parser@1.2.1': resolution: {integrity: sha512-n12NohV3mrUyUL2o93IgG/ifeW9FTyeJn3zDxkhwa8MJ9Fxg3HQMlA3RiGmD/3UnJvheztkjjQAjA2T4LmUcpw==} + '@modelcontextprotocol/core@2.2.0': + resolution: {integrity: sha512-iLhmprRmWI8EcosOA3wVvww22z02NkgqhV4fBH6f/odQBsi7xnJc0HGmi21yWO+/iKw2yzqVE127Zhna5/+JXw==} + engines: {node: '>=20'} + + '@modelcontextprotocol/server@2.2.0': + resolution: {integrity: sha512-qFnltjus6Gk8Lx6J1w2RXmr3YPN22MNT6qD6zjAkaiWIlFIFxWClz0rXHNuJF/KSUyn5YGhkSvXkWDbQHVLFEQ==} + engines: {node: '>=20'} + '@mux/mux-data-google-ima@0.3.17': resolution: {integrity: sha512-4wpH6dYybyZhqLn9qGn/+67Z8MZnQRAdqTFEEZw2bx61M9q01uPYYHxd8qwOnYtUGEeafsdTwVHVxKHGD3oc1A==} @@ -2451,8 +2459,8 @@ packages: resolution: {integrity: sha512-g9XerViJ/paZujufXYcu5XYI2vU2rtB4sgdpjUHde5RnOkdmpu0ngH46LCFGHoPXO/C+qDPSczIHIRN+8Q2YKQ==} engines: {node: '>= 18'} - '@vercel/geistdocs@2.4.1': - resolution: {integrity: sha512-mTGqEWG0SATR95I2sKzG37UEllFQRwfKVCy0mYY7Iu0Vrq6Nr4Iere7DlDrnPVMXUIT2ksFBz9qhlXrum+9+4Q==} + '@vercel/geistdocs@2.7.5': + resolution: {integrity: sha512-SjEOusPjrviI0fO3NPWCmL/Kw/63Yh3Hyb3ysxyZg0+cLrlRoMxq2YqeQ1deI85xZhva5i3vSR22r+rw+FSw1w==} hasBin: true peerDependencies: next: ^16.3.3 @@ -2568,6 +2576,10 @@ packages: resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} engines: {node: '>=18'} + chalk@5.6.2: + resolution: {integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==} + engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} + character-entities-html4@2.1.0: resolution: {integrity: sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==} @@ -2609,6 +2621,10 @@ packages: comma-separated-tokens@2.0.3: resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==} + commander@11.1.0: + resolution: {integrity: sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==} + engines: {node: '>=16'} + commander@14.0.3: resolution: {integrity: sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==} engines: {node: '>=20'} @@ -3418,6 +3434,17 @@ packages: engines: {node: '>= 20'} hasBin: true + mcp-handler@2.2.0: + resolution: {integrity: sha512-loJsIem4jM1jLUsI4x2errOHSzvU+Et6XV4Aqq1AEUkHOqridUoY4dLM02OlRfsku+OjtMlk+tHyYjpfO65X6g==} + engines: {node: '>=20'} + hasBin: true + peerDependencies: + '@modelcontextprotocol/server': ^2.0.0 + next: '>=13.0.0' + peerDependenciesMeta: + next: + optional: true + mdast-util-find-and-replace@3.0.2: resolution: {integrity: sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==} @@ -4835,6 +4862,15 @@ snapshots: dependencies: '@chevrotain/types': 11.1.2 + '@modelcontextprotocol/core@2.2.0': + dependencies: + zod: 4.6.5 + + '@modelcontextprotocol/server@2.2.0': + dependencies: + '@modelcontextprotocol/core': 2.2.0 + zod: 4.6.5 + '@mux/mux-data-google-ima@0.3.17': dependencies: mux-embed: 5.18.1 @@ -6462,10 +6498,11 @@ snapshots: dependencies: execa: 5.1.1 - '@vercel/geistdocs@2.4.1(@svta/cml-cta@1.0.6(@svta/cml-structured-field-values@1.1.3(@svta/cml-utils@1.5.0))(@svta/cml-utils@1.5.0))(@svta/cml-structured-field-values@1.1.3(@svta/cml-utils@1.5.0))(@svta/cml-utils@1.5.0)(@types/mdast@4.0.4)(@types/react-dom@19.3.0(@types/react@19.3.0))(micromark-util-types@2.0.2)(micromark@4.0.2)(next@16.3.5(patch_hash=a620718f9d934500c4f0364aa9312b2539559e0ac911cdc37c97b92e1e9b4550)(@opentelemetry/api@1.9.1)(@types/node@26.6.1)(react-dom@19.3.0(react@19.3.0))(react@19.3.0))(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(tailwindcss@4.3.3)(vite@7.3.6(@types/node@26.6.1)(jiti@2.7.0)(lightningcss@1.33.0)(tsx@4.23.13)(yaml@2.9.1))': + '@vercel/geistdocs@2.7.5(@svta/cml-cta@1.0.6(@svta/cml-structured-field-values@1.1.3(@svta/cml-utils@1.5.0))(@svta/cml-utils@1.5.0))(@svta/cml-structured-field-values@1.1.3(@svta/cml-utils@1.5.0))(@svta/cml-utils@1.5.0)(@types/mdast@4.0.4)(@types/react-dom@19.3.0(@types/react@19.3.0))(micromark-util-types@2.0.2)(micromark@4.0.2)(next@16.3.5(patch_hash=a620718f9d934500c4f0364aa9312b2539559e0ac911cdc37c97b92e1e9b4550)(@opentelemetry/api@1.9.1)(@types/node@26.6.1)(react-dom@19.3.0(react@19.3.0))(react@19.3.0))(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(tailwindcss@4.3.3)(vite@7.3.6(@types/node@26.6.1)(jiti@2.7.0)(lightningcss@1.33.0)(tsx@4.23.13)(yaml@2.9.1))': dependencies: '@ai-sdk/react': 3.0.286(react@19.3.0)(zod@4.6.5) '@clack/prompts': 0.11.0 + '@modelcontextprotocol/server': 2.2.0 '@next/env': 16.3.3 '@orama/tokenizers': 3.1.18 '@streamdown/cjk': 1.0.3(@types/mdast@4.0.4)(micromark-util-types@2.0.2)(micromark@4.0.2)(react@19.3.0)(unified@11.0.5) @@ -6484,6 +6521,7 @@ snapshots: fumadocs-ui: 16.2.2(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(next@16.3.5(patch_hash=a620718f9d934500c4f0364aa9312b2539559e0ac911cdc37c97b92e1e9b4550)(@opentelemetry/api@1.9.1)(@types/node@26.6.1)(react-dom@19.3.0(react@19.3.0))(react@19.3.0))(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(tailwindcss@4.3.3) geist: 1.7.1(next@16.3.5(patch_hash=a620718f9d934500c4f0364aa9312b2539559e0ac911cdc37c97b92e1e9b4550)(@opentelemetry/api@1.9.1)(@types/node@26.6.1)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)) glob: 11.1.0 + mcp-handler: 2.2.0(@modelcontextprotocol/server@2.2.0)(next@16.3.5(patch_hash=a620718f9d934500c4f0364aa9312b2539559e0ac911cdc37c97b92e1e9b4550)(@opentelemetry/api@1.9.1)(@types/node@26.6.1)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)) mermaid: 11.17.2 motion: 12.43.0(react-dom@19.3.0(react@19.3.0))(react@19.3.0) next: 16.3.5(patch_hash=a620718f9d934500c4f0364aa9312b2539559e0ac911cdc37c97b92e1e9b4550)(@opentelemetry/api@1.9.1)(@types/node@26.6.1)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) @@ -6637,6 +6675,8 @@ snapshots: chai@6.2.2: {} + chalk@5.6.2: {} + character-entities-html4@2.1.0: {} character-entities-legacy@3.0.0: {} @@ -6667,6 +6707,8 @@ snapshots: comma-separated-tokens@2.0.3: {} + commander@11.1.0: {} + commander@14.0.3: {} commander@7.2.0: {} @@ -7574,6 +7616,14 @@ snapshots: marked@17.0.6: {} + mcp-handler@2.2.0(@modelcontextprotocol/server@2.2.0)(next@16.3.5(patch_hash=a620718f9d934500c4f0364aa9312b2539559e0ac911cdc37c97b92e1e9b4550)(@opentelemetry/api@1.9.1)(@types/node@26.6.1)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)): + dependencies: + '@modelcontextprotocol/server': 2.2.0 + chalk: 5.6.2 + commander: 11.1.0 + optionalDependencies: + next: 16.3.5(patch_hash=a620718f9d934500c4f0364aa9312b2539559e0ac911cdc37c97b92e1e9b4550)(@opentelemetry/api@1.9.1)(@types/node@26.6.1)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + mdast-util-find-and-replace@3.0.2: dependencies: '@types/mdast': 4.0.4