From fb56a35e42bb5837c8b918cfb0f5e378b0bd63e6 Mon Sep 17 00:00:00 2001 From: Santiago Aloi Date: Mon, 8 Jun 2026 04:05:45 +0200 Subject: [PATCH 1/2] fix(mcp): resolve legacy docs paths and update tool examples The get_documentation_page MCP tool advertised Nuxt 3-era directory structure URLs that 404 in queryCollection lookups. nuxt.com already redirects those paths in the browser, but MCP fetches content by path directly. - Add resolveDocumentationPath() mirroring directory-structure redirects - Apply resolution in fetchPageMarkdown for all MCP doc fetches - Update COMMON PAGES and inputExamples to Nuxt 4 paths Co-authored-by: Cursor --- .../mcp/tools/docs/get-documentation-page.ts | 26 ++++---- server/utils/mcp.ts | 59 ++++++++++++++++++- 2 files changed, 74 insertions(+), 11 deletions(-) diff --git a/server/mcp/tools/docs/get-documentation-page.ts b/server/mcp/tools/docs/get-documentation-page.ts index 69017e886..bfad4a51e 100644 --- a/server/mcp/tools/docs/get-documentation-page.ts +++ b/server/mcp/tools/docs/get-documentation-page.ts @@ -5,30 +5,35 @@ export default defineMcpTool({ WHEN TO USE: Use this tool when you know the EXACT path to a documentation page. Common use cases: - User asks for a specific page: "Show me the introduction page" → /docs/4.x/getting-started/introduction -- User asks about a known topic with a dedicated page - You found a relevant path from list_documentation_pages and want the full content -WHEN NOT TO USE: If you don't know the exact path and need to search/explore, use list_documentation_pages first. +WHEN NOT TO USE: If you don't know the exact path and need to search/explore, use list_documentation_pages first (always pass a search term). COMMON PAGES (Nuxt 4.x): Getting Started: - "/docs/4.x/getting-started/introduction" - main intro - "/docs/4.x/getting-started/installation" - setup - "/docs/4.x/getting-started/upgrade" - migration from v3 +- "/docs/4.x/getting-started/error-handling" - error handling Core Concepts: - "/docs/4.x/guide/concepts/rendering" - SSR/CSR/SSG modes - "/docs/4.x/guide/concepts/auto-imports" - auto-imports - "/docs/4.x/guide/concepts/server-engine" - server features -Directory Structure: -- "/docs/4.x/guide/directory-structure/composables" - composables -- "/docs/4.x/guide/directory-structure/components" - components -- "/docs/4.x/guide/directory-structure/pages" - routing +Directory Structure (Nuxt 4 — app dirs live under directory-structure/app/): +- "/docs/4.x/directory-structure" - overview +- "/docs/4.x/directory-structure/app/composables" - composables +- "/docs/4.x/directory-structure/app/components" - components +- "/docs/4.x/directory-structure/app/pages" - routing +- "/docs/4.x/directory-structure/app/middleware" - route middleware +- "/docs/4.x/directory-structure/app/plugins" - plugins +- "/docs/4.x/directory-structure/app/layouts" - layouts +- "/docs/4.x/directory-structure/app/utils" - auto-imported utils +- "/docs/4.x/directory-structure/server" - server directory -Common Issues: -- "/docs/4.x/guide/going-further/debugging" - debugging -- "/docs/4.x/guide/going-further/error-handling" - errors`, +Debugging: +- "/docs/4.x/guide/going-further/debugging" - debugging`, inputSchema: { path: z.string().describe('The path to the documentation page (e.g., /docs/4.x/getting-started/introduction)'), sections: z.array(z.string()).optional().describe('Specific h2 section titles to return (e.g., ["Usage", "API"]). If omitted, returns full documentation.') @@ -39,7 +44,8 @@ Common Issues: }, inputExamples: [ { path: '/docs/4.x/getting-started/introduction' }, - { path: '/docs/4.x/guide/directory-structure/components', sections: ['Usage'] } + { path: '/docs/4.x/directory-structure/app/components', sections: ['Usage'] }, + { path: '/docs/4.x/directory-structure' } ], cache: '30m', async handler({ path, sections }) { diff --git a/server/utils/mcp.ts b/server/utils/mcp.ts index 37092d5ba..47a96a98f 100644 --- a/server/utils/mcp.ts +++ b/server/utils/mcp.ts @@ -5,6 +5,62 @@ import { queryCollection } from '@nuxt/content/server' type CollectionName = Parameters[1] +/** App-scoped dirs that live under `directory-structure/app/` in Nuxt 4+ docs. */ +const APP_SCOPED_DIRECTORIES = new Set([ + 'assets', + 'components', + 'composables', + 'layouts', + 'middleware', + 'pages', + 'plugins', + 'utils', + 'app', + 'app-config', + 'error' +]) + +/** + * Maps legacy documentation paths to current content paths. + * + * nuxt.com route rules redirect old URLs in the browser, but MCP resolves + * content by path via `queryCollection` and does not follow HTTP redirects. + */ +export function resolveDocumentationPath(path: string): string { + if (path === '/docs/4.x/guide/going-further/error-handling' || path === '/docs/5.x/guide/going-further/error-handling') { + return path.replace('/guide/going-further/error-handling', '/getting-started/error-handling') + } + + let resolved = path.replace( + /^(\/docs\/(?:3\.x|4\.x|5\.x))\/guide\/directory-structure$/, + '$1/directory-structure' + ) + + const legacyMatch = resolved.match(/^(\/docs\/(3\.x|4\.x|5\.x))\/guide\/directory-structure\/(.+)$/) + if (!legacyMatch) { + return resolved + } + + const [, prefix, version, rest] = legacyMatch + + if (version === '3.x') { + return rest.startsWith('app/') + ? `${prefix}/directory-structure/${rest.slice(4)}` + : `${prefix}/directory-structure/${rest}` + } + + if (rest.startsWith('app/') || rest.startsWith('guide/')) { + return `${prefix}/directory-structure/${rest.replace(/^guide\//, '')}` + } + + const topLevel = rest.split('/')[0]! + if (APP_SCOPED_DIRECTORIES.has(topLevel)) { + return `${prefix}/directory-structure/app/${rest}` + } + + return `${prefix}/directory-structure/${rest}` +} + /** * Fetches a page from a known content collection and renders it as markdown. * @@ -19,7 +75,8 @@ export async function fetchPageMarkdown( collection: CollectionName, path: string ): Promise { - const page = await queryCollection(event, collection).path(path).first() as + const resolvedPath = resolveDocumentationPath(path) + const page = await queryCollection(event, collection).path(resolvedPath).first() as | { title?: string, description?: string, body?: { value?: MinimarkNode[] }, links?: unknown[], meta?: { links?: unknown[] } } | null From 4c1ba17fc59ff8bc5ed62449e01b23767b3ac5d1 Mon Sep 17 00:00:00 2001 From: Hugo Richard Date: Mon, 8 Jun 2026 10:08:20 +0100 Subject: [PATCH 2/2] fix(mcp): handle incomplete legacy path matches --- server/utils/mcp.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/server/utils/mcp.ts b/server/utils/mcp.ts index 47a96a98f..9252b7bc1 100644 --- a/server/utils/mcp.ts +++ b/server/utils/mcp.ts @@ -31,7 +31,7 @@ export function resolveDocumentationPath(path: string): string { return path.replace('/guide/going-further/error-handling', '/getting-started/error-handling') } - let resolved = path.replace( + const resolved = path.replace( /^(\/docs\/(?:3\.x|4\.x|5\.x))\/guide\/directory-structure$/, '$1/directory-structure' ) @@ -41,7 +41,12 @@ export function resolveDocumentationPath(path: string): string { return resolved } - const [, prefix, version, rest] = legacyMatch + const prefix = legacyMatch[1] + const version = legacyMatch[2] + const rest = legacyMatch[3] + if (!prefix || !version || !rest) { + return resolved + } if (version === '3.x') { return rest.startsWith('app/') @@ -55,6 +60,7 @@ export function resolveDocumentationPath(path: string): string { const topLevel = rest.split('/')[0]! if (APP_SCOPED_DIRECTORIES.has(topLevel)) { + // `rest === 'app'` → `.../app/app` (canonical content path for app.vue) return `${prefix}/directory-structure/app/${rest}` }