feat(mcp): add MCP server tools (list-pages, get-page)#33
Conversation
Port @nuxtjs/mcp-toolkit MCP server tools from upstream docus so MCP
clients can query documentation pages directly.
Tools added (auto-discovered from `server/mcp/tools/*.ts`):
- `list-pages`: lists all pages with title, path, description, locale,
and absolute URL. Optional `locale` filter. Skips `.navigation` paths.
- `get-page`: returns a single page by exact path, with the full
markdown body fetched from the existing `/raw/{path}.md` route. Skips
`.navigation` paths.
URL derivation prefers the live request origin (via `getRequestURL`),
then falls back to `inferSiteURL()`. `app.baseURL` is joined into the
URL via `ufo.joinURL` so prefixed deployments stay correct.
New shared helpers in `server/utils/content.ts`:
`getAvailableLocales`, `getCollectionsToQuery`, `isNavigationPath`, and
`getCollectionFromPath`. The existing `server/routes/sitemap.xml.ts`
now reuses `getAvailableLocales` and `isNavigationPath` from there
(behavior unchanged).
Also:
- Add `@nuxtjs/mcp-toolkit` (^0.17.2) to layer dependencies and
register it in `nuxt.config.ts` modules.
- Tools are auto-imported via the toolkit's `defineMcpTool`,
`useEvent`, `useRuntimeConfig`, `createError`, `getRequestURL`.
- Each tool caches its response for `'1h'`.
Tolerate per-collection query failures in `list-pages` (missing i18n
locale collections, etc.) so a single bad collection doesn't fail the
whole listing.
Verified: `bun lint` passes, `bun typecheck` introduces no new errors,
`bun --filter @pleaseai/docs-site build` succeeds with the
`node-server` preset, and `bun dev` starts cleanly with
`[@nuxtjs/mcp-toolkit] /mcp enabled with 2 tools`.
Note: under the Cloudflare Pages preset (the docs site's default
preset), the toolkit's Cloudflare transport dynamically imports
`agents/mcp`, which lazy-loads `ai`. Both are optional peers of the
toolkit, but `noExternals: true` on the Cloudflare preset breaks the
dynamic import chain at build time. Working through this is tracked
separately — the dev server and node-server build are unaffected.
Refs upstream docus commits:
- ec3b468c (initial)
- 3eb54da4 (exclude .navigation)
- f7e42ebf (baseURL fix)
- 6043beb1 (derive URLs from request origin)
Survey item docs/docus-upstream-changes.md#8.
There was a problem hiding this comment.
Code Review
This pull request integrates the @nuxtjs/mcp-toolkit module and introduces two new MCP tools, get-page and list-pages, to enable querying and retrieving documentation pages. It also refactors common content and locale resolution logic into a shared utility file (packages/layer/server/utils/content.ts). The review feedback highlights three main improvement opportunities: refining getCollectionsToQuery to return an empty array when an unsupported locale is requested on an i18n-enabled site, using the validated page.path instead of the raw user-input path when fetching raw markdown in get-page.ts, and safely checking the error object type before accessing its statusCode property in the catch block of get-page.ts.
Deploying docs-please with
|
| Latest commit: |
287a92b
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://8e4075c6.docs-please.pages.dev |
| Branch Preview URL: | https://feat-mcp-server-tools.docs-please.pages.dev |
There was a problem hiding this comment.
3 issues found across 7 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="packages/layer/server/mcp/tools/get-page.ts">
<violation number="1" location="packages/layer/server/mcp/tools/get-page.ts:62">
P1: Raw markdown fetch path is not `app.baseURL`-aware, which can break `get-page` on subpath deployments.</violation>
</file>
Architecture diagram
sequenceDiagram
participant MC as MCP Client (Claude Desktop)
participant Nuxt as Nuxt App (@nuxtjs/mcp-toolkit)
participant Event as Nuxt Event (useEvent)
participant Content as @nuxt/content (queryCollection)
participant Raw as /raw/[...slug].md Route
participant Config as Runtime Config
participant Helpers as Server Utils (content.ts)
Note over MC,Helpers: NEW: MCP Tool Discovery & Registration
Nuxt->>Nuxt: Initialize @nuxtjs/mcp-toolkit module
Nuxt->>Nuxt: Auto-discover server/mcp/tools/*.ts
Nuxt-->>Nuxt: Register "list-pages" & "get-page" tools
Note over MC,Raw: Happy Path: MCP Client queries list-pages
MC->>Nuxt: POST /mcp (method: "tools/call", name: "list-pages", locale?)
Nuxt->>Event: defineMcpTool handler invoked
Event->>Config: useRuntimeConfig(event)
Config-->>Event: runtime config (public, app.baseURL)
Event->>Event: getRequestURL(event).origin || inferSiteURL()
Event->>Helpers: getAvailableLocales(publicConfig)
Helpers-->>Event: e.g., ["en", "fr"]
Event->>Helpers: getCollectionsToQuery(locale, availableLocales)
Helpers-->>Event: e.g., ["docs_en", "docs_fr"]
loop For each collection name
Event->>Content: queryCollection(event, collectionName).select(...).all()
Content-->>Event: Array of pages
Event->>Event: Filter out .navigation paths (isNavigationPath)
Event->>Event: Map pages with locale & url (joinURL)
end
alt Collection query fails (e.g., missing i18n collection)
Event->>Event: Catch error, return empty array []
end
Event-->>Nuxt: Flattened page list
Nuxt-->>MC: MCP response (page metadata array)
Note over MC,Raw: Happy Path: MCP Client queries get-page
MC->>Nuxt: POST /mcp (method: "tools/call", name: "get-page", path: "/en/getting-started/installation")
Nuxt->>Event: defineMcpTool handler invoked
Event->>Event: isNavigationPath(path) → false, continue
Event->>Config: useRuntimeConfig(event)
Config-->>Event: runtime config
Event->>Event: getRequestURL(event).origin || inferSiteURL()
Event->>Helpers: getAvailableLocales(publicConfig)
Event->>Helpers: getCollectionFromPath(path, availableLocales)
Helpers-->>Event: e.g., "docs_en"
Event->>Content: queryCollection(event, "docs_en").path(path).select(...).first()
alt Page found
Content-->>Event: { title, path, description }
Event->>Raw: event.$fetch(`/raw${withLeadingSlash(path)}.md`)
Raw-->>Event: Full markdown content
Event->>Event: Build absolute url with joinURL(siteUrl, baseURL, path)
Event-->>Nuxt: { title, path, description, content, url }
else Page not found
Content-->>Event: null
Event->>Event: throw createError(404, "Page not found")
Event-->>Nuxt: Error response
end
Nuxt-->>MC: MCP response (page content object)
Note over Nuxt,Helpers: Error Handling & Edge Cases
alt Navigation path requested
Event->>Event: isNavigationPath(path) → true
Event->>Event: throw createError(404, "Page not found")
Event-->>Nuxt: Error response
Nuxt-->>MC: MCP error
end
alt Server error during get-page
Event->>Content: Query or fetch fails (non-404)
Event->>Event: throw createError(500, "Failed to get page")
Event-->>Nuxt: Error response
Nuxt-->>MC: MCP error
end
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
- content.ts: return [] when locale is given but unsupported on i18n sites, so list-pages no longer silently falls back to every other language. Single-locale (non-i18n) sites still return ['docs']. - get-page.ts: build the /raw<path>.md fetch URL from the canonical page.path rather than the raw user input, so trailing slashes or non-canonical casing don't produce a 404. - get-page.ts: guard error type before reading .statusCode so a primitive thrown value cannot trigger a TypeError. Addresses gemini-code-assist and cubic-dev-ai review threads on #33.
|
Resolve modules-array conflict in packages/layer/nuxt.config.ts (#33 landed @nuxtjs/mcp-toolkit; keep ./modules/markdown-rewrite from this branch alongside it).



Summary
Continues the docus-upstream-changes survey port after #27 by adding MCP server tools so MCP clients (Claude Desktop, etc.) can query our documentation directly.
Implements survey item #8 (
docs/docus-upstream-changes.md). Ports upstream docus commits:ec3b468c— initial MCP tool surface3eb54da4— exclude.navigationpathsf7e42ebf—app.baseURLcorrectness6043beb1— derive URLs from the request originWhat's added
packages/layer/server/mcp/tools/list-pages.ts— Zod-schema MCP tool exposing every page'stitle,path,description,locale, and absoluteurl. Optionallocaleinput filter..navigationpaths are skipped. Per-collection query failures (missing i18n collections etc.) degrade gracefully instead of failing the whole listing.packages/layer/server/mcp/tools/get-page.ts— Zod-schema MCP tool that returns a single page'stitle,path,description,content(markdown), and absoluteurl. The body is fetched viaevent.$fetch('/raw{path}.md'), reusing the existing route atserver/routes/raw/[...slug].md.get.ts.packages/layer/server/utils/content.ts— shared helpers (getAvailableLocales,getCollectionsToQuery,isNavigationPath,getCollectionFromPath).sitemap.xml.tsnow consumes the first two so the helpers have a single owner.packages/layer/nuxt.config.ts+packages/layer/package.json— adds@nuxtjs/mcp-toolkit@^0.17.2(matches upstream docus pin) and registers it in the modules array alphabetically near the other@nuxtjs/*modules. Tools are auto-discovered fromserver/mcp/tools/*.ts.Each tool sets
cache: '1h'and the read-only annotation set (readOnlyHint,idempotentHint).Behavior notes
getRequestURL(event).origin) with fallback toinferSiteURL().app.baseURLis joined viaufo.joinURL.defineMcpTool,useEvent,useRuntimeConfig,getRequestURL,createErrorsymbols are server-side auto-imports provided by@nuxtjs/mcp-toolkit/ Nitro / Nuxt — no manual import needed.Verification
bun lint— passesbun typecheck(inpackages/layer) — no new errors introduced by these files (pre-existing errors in unrelated files remain)bun dev— starts cleanly, logs[@nuxtjs/mcp-toolkit] ✔ /mcp enabled with 2 tools.POST /mcpreturns a proper MCP response;GET /mcpreturns 405 as expected.bun --filter @pleaseai/docs-site buildwithNITRO_PRESET=node-server— succeeds.Known limitation: Cloudflare Pages build
Under the docs site's default
cloudflare_pagespreset, the toolkit's Cloudflare transport (providers/cloudflare.js) dynamically importsagents/mcp, which in turn lazy-loadsai. Both are optional peer dependencies of the toolkit, but the Cloudflare preset setsnoExternals: trueand Rollup fails to follow the dynamic-import chain at build time even when the packages are installed at the workspace root:This is independent of the new tool code (it reproduces on any project that just adds
@nuxtjs/mcp-toolkitto a Cloudflare-targeted Nuxt app). Tracking this separately — likely needs an upstream toolkit fix (markagents/aiasexternals.inlinein the toolkit's nitro hook when the CF preset is detected, or skip the CF provider when the user has no Durable Object streaming). The dev server and the node-server build are unaffected, and MCP functionality is fully exercised in dev.Summary by cubic
Adds MCP server tools so clients can list and fetch documentation pages directly from apps like Claude Desktop. URLs are correct for prefixed and multi-host setups, responses are cached, and
@nuxtjs/mcp-toolkitis registered; Cloudflare Pages builds still fail due to the toolkit’s dynamic imports (dev and node-server builds are fine).New Features
Bug Fixes
Written for commit 287a92b. Summary will update on new commits.