From 9afe5bd6fced3b271b7e65784bf5eaf7b02daca7 Mon Sep 17 00:00:00 2001 From: Hugo Richard Date: Thu, 27 Nov 2025 10:26:27 +0000 Subject: [PATCH 1/9] feat(mcp): use `@nuxtjs/mcp-toolkit` --- docs/nuxt.config.ts | 5 + docs/package.json | 1 + .../mcp/prompts/find-component-for-usecase.ts | 22 + .../prompts/implement-component-with-props.ts | 25 + .../prompts/setup-project-with-template.ts | 22 + docs/server/mcp/resources/components.ts | 16 + docs/server/mcp/resources/composables.ts | 16 + .../mcp/resources/documentation-pages.ts | 16 + docs/server/mcp/resources/examples.ts | 16 + docs/server/mcp/resources/templates.ts | 16 + .../mcp/tools/get-component-metadata.ts | 14 + docs/server/mcp/tools/get-component.ts | 14 + .../mcp/tools/get-documentation-page.ts | 14 + docs/server/mcp/tools/get-example.ts | 14 + docs/server/mcp/tools/get-migration-guide.ts | 14 + docs/server/mcp/tools/get-template.ts | 15 + docs/server/mcp/tools/list-components.ts | 9 + docs/server/mcp/tools/list-composables.ts | 9 + .../mcp/tools/list-documentation-pages.ts | 9 + docs/server/mcp/tools/list-examples.ts | 9 + .../mcp/tools/list-getting-started-guides.ts | 9 + docs/server/mcp/tools/list-templates.ts | 15 + .../tools/search-components-by-category.ts | 15 + docs/server/routes/mcp.ts | 440 ------------------ pnpm-lock.yaml | 90 ++++ 25 files changed, 405 insertions(+), 440 deletions(-) create mode 100644 docs/server/mcp/prompts/find-component-for-usecase.ts create mode 100644 docs/server/mcp/prompts/implement-component-with-props.ts create mode 100644 docs/server/mcp/prompts/setup-project-with-template.ts create mode 100644 docs/server/mcp/resources/components.ts create mode 100644 docs/server/mcp/resources/composables.ts create mode 100644 docs/server/mcp/resources/documentation-pages.ts create mode 100644 docs/server/mcp/resources/examples.ts create mode 100644 docs/server/mcp/resources/templates.ts create mode 100644 docs/server/mcp/tools/get-component-metadata.ts create mode 100644 docs/server/mcp/tools/get-component.ts create mode 100644 docs/server/mcp/tools/get-documentation-page.ts create mode 100644 docs/server/mcp/tools/get-example.ts create mode 100644 docs/server/mcp/tools/get-migration-guide.ts create mode 100644 docs/server/mcp/tools/get-template.ts create mode 100644 docs/server/mcp/tools/list-components.ts create mode 100644 docs/server/mcp/tools/list-composables.ts create mode 100644 docs/server/mcp/tools/list-documentation-pages.ts create mode 100644 docs/server/mcp/tools/list-examples.ts create mode 100644 docs/server/mcp/tools/list-getting-started-guides.ts create mode 100644 docs/server/mcp/tools/list-templates.ts create mode 100644 docs/server/mcp/tools/search-components-by-category.ts delete mode 100644 docs/server/routes/mcp.ts diff --git a/docs/nuxt.config.ts b/docs/nuxt.config.ts index f31bd365ef..8b632422f1 100644 --- a/docs/nuxt.config.ts +++ b/docs/nuxt.config.ts @@ -9,6 +9,7 @@ export default defineNuxtConfig({ '@nuxt/content', '@nuxt/image', '@nuxtjs/plausible', + '@nuxtjs/mcp-toolkit', '@vueuse/nuxt', 'nuxt-component-meta', 'nuxt-og-image', @@ -261,5 +262,9 @@ export default defineNuxtConfig({ 'The documentation excludes Nuxt UI v2 content.', 'The content is automatically generated from the same source as the official documentation.' ] + }, + mcp: { + name: 'Nuxt UI', + browserRedirect: '/docs/getting-started/ai/mcp' } }) diff --git a/docs/package.json b/docs/package.json index d65722cb40..0193c48a9d 100644 --- a/docs/package.json +++ b/docs/package.json @@ -21,6 +21,7 @@ "@nuxt/image": "^2.0.0", "@nuxt/ui": "workspace:*", "@nuxtjs/plausible": "^2.0.1", + "@nuxtjs/mcp-toolkit": "^0.1.1", "@octokit/rest": "^22.0.1", "@regle/core": "^1.10.2", "@regle/rules": "^1.10.2", diff --git a/docs/server/mcp/prompts/find-component-for-usecase.ts b/docs/server/mcp/prompts/find-component-for-usecase.ts new file mode 100644 index 0000000000..0799baa41c --- /dev/null +++ b/docs/server/mcp/prompts/find-component-for-usecase.ts @@ -0,0 +1,22 @@ +import { z } from 'zod/v3' + +export default defineMcpPrompt({ + description: 'Find the best Nuxt UI component for a specific use case', + inputSchema: { + usecase: z.string().describe('Describe what you want to build (e.g., "user login form", "data table", "navigation menu")') + }, + handler: async ({ usecase }) => { + const components = await $fetch('/api/mcp/list-components') + return { + messages: [ + { + role: 'user', + content: { + type: 'text', + text: `Help me find the best Nuxt UI component for this use case: "${usecase}". Here are all available components: ${JSON.stringify(components, null, 2)}` + } + } + ] + } + } +}) diff --git a/docs/server/mcp/prompts/implement-component-with-props.ts b/docs/server/mcp/prompts/implement-component-with-props.ts new file mode 100644 index 0000000000..1e7d02eb5e --- /dev/null +++ b/docs/server/mcp/prompts/implement-component-with-props.ts @@ -0,0 +1,25 @@ +import { z } from 'zod/v3' + +export default defineMcpPrompt({ + description: 'Generate complete component implementation with proper props and styling', + inputSchema: { + componentName: z.string().describe('The Nuxt UI component name (PascalCase)'), + requirements: z.string().optional().describe('Specific requirements or customizations needed') + }, + handler: async ({ componentName, requirements }) => { + const component = await $fetch('/api/mcp/get-component', { + query: { componentName, includeMetadata: true } + }) + return { + messages: [ + { + role: 'user', + content: { + type: 'text', + text: `Generate a complete implementation of the ${componentName} component with proper props and styling. ${requirements ? `Requirements: ${requirements}` : ''}\n\nComponent details: ${JSON.stringify(component, null, 2)}` + } + } + ] + } + } +}) diff --git a/docs/server/mcp/prompts/setup-project-with-template.ts b/docs/server/mcp/prompts/setup-project-with-template.ts new file mode 100644 index 0000000000..cfa39cc41b --- /dev/null +++ b/docs/server/mcp/prompts/setup-project-with-template.ts @@ -0,0 +1,22 @@ +import { z } from 'zod/v3' + +export default defineMcpPrompt({ + description: 'Guide through setting up a new project with a Nuxt UI template', + inputSchema: { + projectType: z.string().describe('Type of project (dashboard, landing page, admin panel, etc.)') + }, + handler: async ({ projectType }) => { + const templates = await $fetch('/api/mcp/list-templates') + return { + messages: [ + { + role: 'user', + content: { + type: 'text', + text: `Guide me through setting up a new ${projectType} project with Nuxt UI. Here are available templates: ${JSON.stringify(templates, null, 2)}` + } + } + ] + } + } +}) diff --git a/docs/server/mcp/resources/components.ts b/docs/server/mcp/resources/components.ts new file mode 100644 index 0000000000..cbd22c41ff --- /dev/null +++ b/docs/server/mcp/resources/components.ts @@ -0,0 +1,16 @@ +export default defineMcpResource({ + uri: 'resource://nuxt-ui/components', + metadata: { + description: 'Complete list of available Nuxt UI v4 components with metadata and categories' + }, + handler: async (uri: URL) => { + const result = await $fetch('/api/mcp/list-components') + return { + contents: [{ + uri: uri.toString(), + mimeType: 'application/json', + text: JSON.stringify(result, null, 2) + }] + } + } +}) diff --git a/docs/server/mcp/resources/composables.ts b/docs/server/mcp/resources/composables.ts new file mode 100644 index 0000000000..44210ebd9d --- /dev/null +++ b/docs/server/mcp/resources/composables.ts @@ -0,0 +1,16 @@ +export default defineMcpResource({ + uri: 'resource://nuxt-ui/composables', + metadata: { + description: 'Complete list of available Nuxt UI v4 composables with metadata and categories' + }, + handler: async (uri: URL) => { + const result = await $fetch('/api/mcp/list-composables') + return { + contents: [{ + uri: uri.toString(), + mimeType: 'application/json', + text: JSON.stringify(result, null, 2) + }] + } + } +}) diff --git a/docs/server/mcp/resources/documentation-pages.ts b/docs/server/mcp/resources/documentation-pages.ts new file mode 100644 index 0000000000..94f1b9aba8 --- /dev/null +++ b/docs/server/mcp/resources/documentation-pages.ts @@ -0,0 +1,16 @@ +export default defineMcpResource({ + uri: 'resource://nuxt-ui/documentation-pages', + metadata: { + description: 'Complete list of available Nuxt UI documentation pages' + }, + handler: async (uri: URL) => { + const result = await $fetch('/api/mcp/list-documentation-pages') + return { + contents: [{ + uri: uri.toString(), + mimeType: 'application/json', + text: JSON.stringify(result, null, 2) + }] + } + } +}) diff --git a/docs/server/mcp/resources/examples.ts b/docs/server/mcp/resources/examples.ts new file mode 100644 index 0000000000..898a4ed874 --- /dev/null +++ b/docs/server/mcp/resources/examples.ts @@ -0,0 +1,16 @@ +export default defineMcpResource({ + uri: 'resource://nuxt-ui/examples', + metadata: { + description: 'Complete list of available Nuxt UI example code and demonstrations' + }, + handler: async (uri: URL) => { + const result = await $fetch('/api/mcp/list-examples') + return { + contents: [{ + uri: uri.toString(), + mimeType: 'application/json', + text: JSON.stringify(result, null, 2) + }] + } + } +}) diff --git a/docs/server/mcp/resources/templates.ts b/docs/server/mcp/resources/templates.ts new file mode 100644 index 0000000000..6c4fdf2a68 --- /dev/null +++ b/docs/server/mcp/resources/templates.ts @@ -0,0 +1,16 @@ +export default defineMcpResource({ + uri: 'resource://nuxt-ui/templates', + metadata: { + description: 'Complete list of available Nuxt UI templates with categories' + }, + handler: async (uri: URL) => { + const result = await $fetch('/api/mcp/list-templates') + return { + contents: [{ + uri: uri.toString(), + mimeType: 'application/json', + text: JSON.stringify(result, null, 2) + }] + } + } +}) diff --git a/docs/server/mcp/tools/get-component-metadata.ts b/docs/server/mcp/tools/get-component-metadata.ts new file mode 100644 index 0000000000..369e387a3c --- /dev/null +++ b/docs/server/mcp/tools/get-component-metadata.ts @@ -0,0 +1,14 @@ +import { z } from 'zod/v3' + +export default defineMcpTool({ + description: 'Retrieves detailed metadata for a Nuxt UI component including props, slots, and events', + inputSchema: { + componentName: z.string().describe('The name of the component (PascalCase)') + }, + handler: async ({ componentName }) => { + const result = await $fetch('/api/mcp/get-component-metadata', { query: { componentName } }) + return { + content: [{ type: 'text' as const, text: JSON.stringify(result, null, 2) }] + } + } +}) diff --git a/docs/server/mcp/tools/get-component.ts b/docs/server/mcp/tools/get-component.ts new file mode 100644 index 0000000000..084cbe36d6 --- /dev/null +++ b/docs/server/mcp/tools/get-component.ts @@ -0,0 +1,14 @@ +import { z } from 'zod/v3' + +export default defineMcpTool({ + description: 'Retrieves Nuxt UI component documentation and details', + inputSchema: { + componentName: z.string().describe('The name of the component (PascalCase)') + }, + handler: async ({ componentName }) => { + const result = await $fetch('/api/mcp/get-component', { query: { componentName } }) + return { + content: [{ type: 'text' as const, text: JSON.stringify(result, null, 2) }] + } + } +}) diff --git a/docs/server/mcp/tools/get-documentation-page.ts b/docs/server/mcp/tools/get-documentation-page.ts new file mode 100644 index 0000000000..ed3ff761ca --- /dev/null +++ b/docs/server/mcp/tools/get-documentation-page.ts @@ -0,0 +1,14 @@ +import { z } from 'zod/v3' + +export default defineMcpTool({ + description: 'Retrieves documentation page content by URL path', + inputSchema: { + path: z.string().describe('The path to the content page (e.g., /docs/components/button)') + }, + handler: async ({ path }) => { + const result = await $fetch(`/raw${path}.md`) + return { + content: [{ type: 'text' as const, text: JSON.stringify(result, null, 2) }] + } + } +}) diff --git a/docs/server/mcp/tools/get-example.ts b/docs/server/mcp/tools/get-example.ts new file mode 100644 index 0000000000..1d3106d37c --- /dev/null +++ b/docs/server/mcp/tools/get-example.ts @@ -0,0 +1,14 @@ +import { z } from 'zod/v3' + +export default defineMcpTool({ + description: 'Retrieves specific UI example implementation code and details', + inputSchema: { + exampleName: z.string().describe('The name of the example (PascalCase)') + }, + handler: async ({ exampleName }) => { + const result = await $fetch<{ code: string }>(`/api/component-example/${exampleName}.json`) + return { + content: [{ type: 'text' as const, text: result.code }] + } + } +}) diff --git a/docs/server/mcp/tools/get-migration-guide.ts b/docs/server/mcp/tools/get-migration-guide.ts new file mode 100644 index 0000000000..47b5c02376 --- /dev/null +++ b/docs/server/mcp/tools/get-migration-guide.ts @@ -0,0 +1,14 @@ +import { z } from 'zod/v3' + +export default defineMcpTool({ + description: 'Retrieves version-specific migration guides and upgrade instructions', + inputSchema: { + version: z.enum(['v3', 'v4']).describe('The migration version (e.g., v4, v3)') + }, + handler: async ({ version }) => { + const result = await $fetch('/api/mcp/get-migration-guide', { query: { version } }) + return { + content: [{ type: 'text' as const, text: JSON.stringify(result, null, 2) }] + } + } +}) diff --git a/docs/server/mcp/tools/get-template.ts b/docs/server/mcp/tools/get-template.ts new file mode 100644 index 0000000000..9bc63c22d2 --- /dev/null +++ b/docs/server/mcp/tools/get-template.ts @@ -0,0 +1,15 @@ +import { z } from 'zod/v3' + +export default defineMcpTool({ + description: 'Retrieves template details and setup instructions', + inputSchema: { + templateName: z.string().describe('The name of the template') + }, + handler: async ({ templateName }) => { + const result = await $fetch('/api/mcp/get-template', { query: { templateName } }) + return { + content: [{ type: 'text' as const, text: JSON.stringify(result, null, 2) }], + structuredContent: result as any + } + } +}) diff --git a/docs/server/mcp/tools/list-components.ts b/docs/server/mcp/tools/list-components.ts new file mode 100644 index 0000000000..1e753045c8 --- /dev/null +++ b/docs/server/mcp/tools/list-components.ts @@ -0,0 +1,9 @@ +export default defineMcpTool({ + description: 'Lists all available Nuxt UI components with their categories and basic information', + handler: async () => { + const result = await $fetch('/api/mcp/list-components') + return { + content: [{ type: 'text' as const, text: JSON.stringify(result, null, 2) }] + } + } +}) diff --git a/docs/server/mcp/tools/list-composables.ts b/docs/server/mcp/tools/list-composables.ts new file mode 100644 index 0000000000..92a2685c53 --- /dev/null +++ b/docs/server/mcp/tools/list-composables.ts @@ -0,0 +1,9 @@ +export default defineMcpTool({ + description: 'Lists all available Nuxt UI composables with their categories and basic information', + handler: async () => { + const result = await $fetch('/api/mcp/list-composables') + return { + content: [{ type: 'text' as const, text: JSON.stringify(result, null, 2) }] + } + } +}) diff --git a/docs/server/mcp/tools/list-documentation-pages.ts b/docs/server/mcp/tools/list-documentation-pages.ts new file mode 100644 index 0000000000..ef0fc95d64 --- /dev/null +++ b/docs/server/mcp/tools/list-documentation-pages.ts @@ -0,0 +1,9 @@ +export default defineMcpTool({ + description: 'Lists all documentation pages', + handler: async () => { + const result = await $fetch('/api/mcp/list-documentation-pages') + return { + content: [{ type: 'text' as const, text: JSON.stringify(result, null, 2) }] + } + } +}) diff --git a/docs/server/mcp/tools/list-examples.ts b/docs/server/mcp/tools/list-examples.ts new file mode 100644 index 0000000000..a33daad04e --- /dev/null +++ b/docs/server/mcp/tools/list-examples.ts @@ -0,0 +1,9 @@ +export default defineMcpTool({ + description: 'Lists all available UI examples and code demonstrations', + handler: async () => { + const result = await $fetch('/api/mcp/list-examples') + return { + content: [{ type: 'text' as const, text: JSON.stringify(result, null, 2) }] + } + } +}) diff --git a/docs/server/mcp/tools/list-getting-started-guides.ts b/docs/server/mcp/tools/list-getting-started-guides.ts new file mode 100644 index 0000000000..3e067b1a74 --- /dev/null +++ b/docs/server/mcp/tools/list-getting-started-guides.ts @@ -0,0 +1,9 @@ +export default defineMcpTool({ + description: 'Lists all getting started guides and installation instructions', + handler: async () => { + const result = await $fetch('/api/mcp/list-getting-started-guides') + return { + content: [{ type: 'text' as const, text: JSON.stringify(result, null, 2) }] + } + } +}) diff --git a/docs/server/mcp/tools/list-templates.ts b/docs/server/mcp/tools/list-templates.ts new file mode 100644 index 0000000000..fc458b4d56 --- /dev/null +++ b/docs/server/mcp/tools/list-templates.ts @@ -0,0 +1,15 @@ +import { z } from 'zod/v3' + +export default defineMcpTool({ + description: 'Lists all available Nuxt UI templates with optional category filtering', + inputSchema: { + category: z.string().optional().describe('Filter templates by category') + }, + handler: async ({ category }) => { + const result = await $fetch('/api/mcp/list-templates', { query: { category } }) + return { + content: [{ type: 'text' as const, text: JSON.stringify(result, null, 2) }], + structuredContent: result as any + } + } +}) diff --git a/docs/server/mcp/tools/search-components-by-category.ts b/docs/server/mcp/tools/search-components-by-category.ts new file mode 100644 index 0000000000..a573a4a888 --- /dev/null +++ b/docs/server/mcp/tools/search-components-by-category.ts @@ -0,0 +1,15 @@ +import { z } from 'zod/v3' + +export default defineMcpTool({ + description: 'Searches components by category or text filter', + inputSchema: { + category: z.string().optional().describe('Filter components by category'), + search: z.string().optional().describe('Search term to filter components by name or description') + }, + handler: async ({ category, search }) => { + const result = await $fetch('/api/mcp/search-components-by-category', { query: { category, search } }) + return { + content: [{ type: 'text' as const, text: JSON.stringify(result, null, 2) }] + } + } +}) diff --git a/docs/server/routes/mcp.ts b/docs/server/routes/mcp.ts deleted file mode 100644 index dfc509195f..0000000000 --- a/docs/server/routes/mcp.ts +++ /dev/null @@ -1,440 +0,0 @@ -/*** - Workaround for using zod 3 for the mcp validation - Read here: https://github.com/modelcontextprotocol/typescript-sdk/issues/906 - */ -import { z } from 'zod/v3' -import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js' -import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js' - -function createServer() { - const server = new McpServer({ - name: 'nuxt-ui', - version: '1.0.0' - }) - - // RESOURCES - - server.registerResource( - 'nuxt-ui-documentation-pages', - 'resource://nuxt-ui/documentation-pages', - { - title: 'Nuxt UI Documentation Pages', - description: 'Complete list of available Nuxt UI documentation pages' - }, - async (uri) => { - const result = await $fetch('/api/mcp/list-documentation-pages') - return { - contents: [{ - uri: uri.href, - mimeType: 'application/json', - text: JSON.stringify(result, null, 2) - }] - } - } - ) - - server.registerResource( - 'nuxt-ui-components', - 'resource://nuxt-ui/components', - { - title: 'Nuxt UI Components', - description: 'Complete list of available Nuxt UI v4 components with metadata and categories' - }, - async (uri) => { - const result = await $fetch('/api/mcp/list-components') - return { - contents: [{ - uri: uri.href, - mimeType: 'application/json', - text: JSON.stringify(result, null, 2) - }] - } - } - ) - - server.registerResource( - 'nuxt-ui-composables', - 'resource://nuxt-ui/composables', - { - title: 'Nuxt UI Composables', - description: 'Complete list of available Nuxt UI v4 composables with metadata and categories' - }, - async (uri) => { - const result = await $fetch('/api/mcp/list-composables') - return { - contents: [{ - uri: uri.href, - mimeType: 'application/json', - text: JSON.stringify(result, null, 2) - }] - } - } - ) - - server.registerResource( - 'nuxt-ui-examples', - 'resource://nuxt-ui/examples', - { - title: 'Nuxt UI Examples', - description: 'Complete list of available Nuxt UI example code and demonstrations' - }, - async (uri) => { - const result = await $fetch('/api/mcp/list-examples') - return { - contents: [{ - uri: uri.href, - mimeType: 'application/json', - text: JSON.stringify(result, null, 2) - }] - } - } - ) - - server.registerResource( - 'nuxt-ui-templates', - 'resource://nuxt-ui/templates', - { - title: 'Nuxt UI Templates', - description: 'Complete list of available Nuxt UI templates with categories' - }, - async (uri) => { - const result = await $fetch('/api/mcp/list-templates') - return { - contents: [{ - uri: uri.href, - mimeType: 'application/json', - text: JSON.stringify(result, null, 2) - }] - } - } - ) - - // PROMPTS - - server.registerPrompt( - 'find_component_for_usecase', - { - title: 'Find Component for Use Case', - description: 'Find the best Nuxt UI component for a specific use case', - argsSchema: { - // @ts-expect-error - need to wait for support for zod 4, this works correctly just a type mismatch from zod 3 to zod 4 (https://github.com/modelcontextprotocol/typescript-sdk/pull/869) - usecase: z.string().describe('Describe what you want to build (e.g., "user login form", "data table", "navigation menu")') - } - }, - async ({ usecase }) => { - const components = await $fetch('/api/mcp/list-components') - return { - messages: [ - { - role: 'user', - content: { - type: 'text', - text: `Help me find the best Nuxt UI component for this use case: "${usecase}". Here are all available components: ${JSON.stringify(components, null, 2)}` - } - } - ] - } - } - ) - - server.registerPrompt( - 'implement_component_with_props', - { - title: 'Implement Component with Props', - description: 'Generate complete component implementation with proper props and styling', - argsSchema: { - // @ts-expect-error - need to wait for support for zod 4, this works correctly just a type mismatch from zod 3 to zod 4 (https://github.com/modelcontextprotocol/typescript-sdk/pull/869) - componentName: z.string().describe('The Nuxt UI component name (PascalCase)'), - // @ts-expect-error - need to wait for support for zod 4, this works correctly just a type mismatch from zod 3 to zod 4 (https://github.com/modelcontextprotocol/typescript-sdk/pull/869) - requirements: z.string().optional().describe('Specific requirements or customizations needed') - } - }, - async ({ componentName, requirements }) => { - const component = await $fetch('/api/mcp/get-component', { - query: { componentName, includeMetadata: true } - }) - return { - messages: [ - { - role: 'user', - content: { - type: 'text', - text: `Generate a complete implementation of the ${componentName} component with proper props and styling. ${requirements ? `Requirements: ${requirements}` : ''}\n\nComponent details: ${JSON.stringify(component, null, 2)}` - } - } - ] - } - } - ) - - server.registerPrompt( - 'setup_project_with_template', - { - title: 'Setup Project with Template', - description: 'Guide through setting up a new project with a Nuxt UI template', - argsSchema: { - // @ts-expect-error - need to wait for support for zod 4, this works correctly just a type mismatch from zod 3 to zod 4 (https://github.com/modelcontextprotocol/typescript-sdk/pull/869) - projectType: z.string().describe('Type of project (dashboard, landing page, admin panel, etc.)') - } - }, - async ({ projectType }) => { - const templates = await $fetch('/api/mcp/list-templates') - return { - messages: [ - { - role: 'user', - content: { - type: 'text', - text: `Guide me through setting up a new ${projectType} project with Nuxt UI. Here are available templates: ${JSON.stringify(templates, null, 2)}` - } - } - ] - } - } - ) - - // TOOLS - - server.registerTool( - 'list_components', - { - title: 'List Components', - description: 'Lists all available Nuxt UI components with their categories and basic information' - }, - async () => { - const result = await $fetch('/api/mcp/list-components') - return { - content: [{ type: 'text' as const, text: JSON.stringify(result, null, 2) }] - } - } - ) - - server.registerTool( - 'list_composables', - { - title: 'List Composables', - description: 'Lists all available Nuxt UI composables with their categories and basic information' - }, - async () => { - const result = await $fetch('/api/mcp/list-composables') - return { - content: [{ type: 'text' as const, text: JSON.stringify(result, null, 2) }] - } - } - ) - - server.registerTool( - 'get_component', - { - title: 'Get Component', - description: 'Retrieves Nuxt UI component documentation and details', - inputSchema: { - // @ts-expect-error - need to wait for support for zod 4, this works correctly just a type mismatch from zod 3 to zod 4 (https://github.com/modelcontextprotocol/typescript-sdk/pull/869) - componentName: z.string().describe('The name of the component (PascalCase)') - } - }, - async (params: { componentName: string }) => { - const result = await $fetch('/api/mcp/get-component', { query: params }) - return { - content: [{ type: 'text' as const, text: JSON.stringify(result, null, 2) }] - } - } - ) - - server.registerTool( - 'get_component_metadata', - { - title: 'Get Component Metadata', - description: 'Retrieves detailed metadata for a Nuxt UI component including props, slots, and events', - inputSchema: { - // @ts-expect-error - need to wait for support for zod 4, this works correctly just a type mismatch from zod 3 to zod 4 (https://github.com/modelcontextprotocol/typescript-sdk/pull/869) - componentName: z.string().describe('The name of the component (PascalCase)') - } - }, - async (params: { componentName: string }) => { - const result = await $fetch('/api/mcp/get-component-metadata', { query: params }) - return { - content: [{ type: 'text' as const, text: JSON.stringify(result, null, 2) }] - } - } - ) - - server.registerTool( - 'list_templates', - { - title: 'List Templates', - description: 'Lists all available Nuxt UI templates with optional category filtering', - inputSchema: { - // @ts-expect-error - need to wait for support for zod 4, this works correctly just a type mismatch from zod 3 to zod 4 (https://github.com/modelcontextprotocol/typescript-sdk/pull/869) - category: z.string().optional().describe('Filter templates by category') - } - }, - async (params: { category?: string }) => { - const result = await $fetch('/api/mcp/list-templates', { query: params }) - return { - content: [{ type: 'text' as const, text: JSON.stringify(result, null, 2) }], - structuredContent: result as any - } - } - ) - - server.registerTool( - 'get_template', - { - title: 'Get Template', - description: 'Retrieves template details and setup instructions', - inputSchema: { - // @ts-expect-error - need to wait for support for zod 4, this works correctly just a type mismatch from zod 3 to zod 4 (https://github.com/modelcontextprotocol/typescript-sdk/pull/869) - templateName: z.string().describe('The name of the template') - } - }, - async (params: { templateName: string }) => { - const result = await $fetch('/api/mcp/get-template', { query: params }) - return { - content: [{ type: 'text' as const, text: JSON.stringify(result, null, 2) }], - structuredContent: result as any - } - } - ) - - server.registerTool( - 'get_documentation_page', - { - title: 'Get Documentation Page', - description: 'Retrieves documentation page content by URL path', - inputSchema: { - // @ts-expect-error - need to wait for support for zod 4, this works correctly just a type mismatch from zod 3 to zod 4 (https://github.com/modelcontextprotocol/typescript-sdk/pull/869) - path: z.string().describe('The path to the content page (e.g., /docs/components/button)') - } - }, - async (params: { path: string }) => { - const result = await $fetch(`/raw${params.path}.md`) - return { - content: [{ type: 'text' as const, text: JSON.stringify(result, null, 2) }] - } - } - ) - - server.registerTool( - 'list_documentation_pages', - { - title: 'List Documentation Pages', - description: 'Lists all documentation pages' - }, - async () => { - const result = await $fetch('/api/mcp/list-documentation-pages') - return { - content: [{ type: 'text' as const, text: JSON.stringify(result, null, 2) }] - } - } - ) - - server.registerTool( - 'list_getting_started_guides', - { - title: 'List Getting Started Guides', - description: 'Lists all getting started guides and installation instructions' - }, - async () => { - const result = await $fetch('/api/mcp/list-getting-started-guides') - return { - content: [{ type: 'text' as const, text: JSON.stringify(result, null, 2) }] - } - } - ) - - server.registerTool( - 'get_migration_guide', - { - title: 'Get Migration Guide', - description: 'Retrieves version-specific migration guides and upgrade instructions', - inputSchema: { - // @ts-expect-error - need to wait for support for zod 4, this works correctly just a type mismatch from zod 3 to zod 4 (https://github.com/modelcontextprotocol/typescript-sdk/pull/869) - version: z.enum(['v3', 'v4']).describe('The migration version (e.g., v4, v3)') - } - }, - async (params: { version: 'v3' | 'v4' }) => { - const result = await $fetch('/api/mcp/get-migration-guide', { query: params }) - return { - content: [{ type: 'text' as const, text: JSON.stringify(result, null, 2) }] - } - } - ) - - server.registerTool( - 'list_examples', - { - title: 'List Examples', - description: 'Lists all available UI examples and code demonstrations' - }, - async () => { - const result = await $fetch('/api/mcp/list-examples') - return { - content: [{ type: 'text' as const, text: JSON.stringify(result, null, 2) }] - } - } - ) - - server.registerTool( - 'get_example', - { - title: 'Get Example', - description: 'Retrieves specific UI example implementation code and details', - inputSchema: { - // @ts-expect-error - need to wait for support for zod 4, this works correctly just a type mismatch from zod 3 to zod 4 (https://github.com/modelcontextprotocol/typescript-sdk/pull/869) - exampleName: z.string().describe('The name of the example (PascalCase)') - } - }, - async ({ exampleName }: { exampleName: string }) => { - const result = await $fetch(`/api/component-example/${exampleName}.json`) - return { - content: [{ type: 'text' as const, text: result.code }] - } - } - ) - - server.registerTool( - 'search_components_by_category', - { - title: 'Search Components by Category', - description: 'Searches components by category or text filter', - inputSchema: { - // @ts-expect-error - need to wait for support for zod 4, this works correctly just a type mismatch from zod 3 to zod 4 (https://github.com/modelcontextprotocol/typescript-sdk/pull/869) - category: z.string().optional().describe('Filter components by category'), - // @ts-expect-error - need to wait for support for zod 4, this works correctly just a type mismatch from zod 3 to zod 4 (https://github.com/modelcontextprotocol/typescript-sdk/pull/869) - search: z.string().optional().describe('Search term to filter components by name or description') - } - }, - async (params: { category?: string, search?: string }) => { - const result = await $fetch('/api/mcp/search-components-by-category', { query: params }) - return { - content: [{ type: 'text' as const, text: JSON.stringify(result, null, 2) }] - } - } - ) - - return server -} - -export default defineEventHandler(async (event) => { - if (getHeader(event, 'accept')?.includes('text/html')) { - return sendRedirect(event, '/docs/getting-started/ai/mcp') - } - - const server = createServer() - - const transport: StreamableHTTPServerTransport = new StreamableHTTPServerTransport({ - sessionIdGenerator: undefined - }) - - event.node.res.on('close', () => { - transport.close() - server.close() - }) - - await server.connect(transport) - - const body = await readBody(event) - - await transport.handleRequest(event.node.req, event.node.res, body) -}) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 188b93fa61..04c4260422 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -274,6 +274,9 @@ importers: '@nuxt/ui': specifier: workspace:* version: link:.. + '@nuxtjs/mcp-toolkit': + specifier: ^0.1.1 + version: 0.1.1(magicast@0.5.1)(zod@4.1.13) '@nuxtjs/plausible': specifier: ^2.0.1 version: 2.0.1(magicast@0.5.1) @@ -579,6 +582,10 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 + '@babel/runtime@7.28.4': + resolution: {integrity: sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==} + engines: {node: '>=6.9.0'} + '@babel/template@7.27.2': resolution: {integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==} engines: {node: '>=6.9.0'} @@ -1467,6 +1474,11 @@ packages: '@nuxtjs/color-mode@3.5.2': resolution: {integrity: sha512-cC6RfgZh3guHBMLLjrBB2Uti5eUoGM9KyauOaYS9ETmxNWBMTvpgjvSiSJp1OFljIXPIqVTJ3xtJpSNZiO3ZaA==} + '@nuxtjs/mcp-toolkit@0.1.1': + resolution: {integrity: sha512-K/JSrPMbPplafDTmiSGLpxIdE9Ve3OLm7F3rvTuk/HNxw7Q/phkQLn+42VI0tEBGUjpMDjFKO+gnkJDTt2zUCQ==} + peerDependencies: + zod: ^3.25.76 + '@nuxtjs/mdc@0.18.3': resolution: {integrity: sha512-Fl64a9OZBH3J7ZpqzSWkrS64oFmLLvledZMcnYH3UzVtgFPo/GaICdJN3Ml83NSs/J9At6HHaP0k3+nrxu2qJw==} @@ -3093,6 +3105,10 @@ packages: asynckit@0.4.0: resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + automd@0.4.2: + resolution: {integrity: sha512-9Gey0OG4gu2IzoLbwRj2fqyntJPbEFox/5KdOgg0zflkzq5lyOapWE324xYOvVdk9hgyjiMvDcT6XUPAIJhmag==} + hasBin: true + autoprefixer@10.4.21: resolution: {integrity: sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ==} engines: {node: ^10 || ^12 || >=14} @@ -3757,6 +3773,10 @@ packages: dfa@1.2.0: resolution: {integrity: sha512-ED3jP8saaweFTjeGX8HQPjeC1YYyZs98jGNZx6IiBvxW7JG5v492kamAQB3m2wop07CvU/RQmzcKr6bgcC5D/Q==} + didyoumean2@7.0.4: + resolution: {integrity: sha512-+yW4SNY7W2DOWe2Jx5H4c2qMTFbLGM6wIyoDPkAPy66X+sD1KfYjBPAIWPVsYqMxelflaMQCloZDudELIPhLqA==} + engines: {node: ^18.12.0 || >=20.9.0} + diff@8.0.2: resolution: {integrity: sha512-sSuxWU5j5SR9QQji/o2qMvqRNYRDOcBTgsJ/DeCf4iSN4gW+gNMXM7wFIP+fdXZxoNiAnHUTGjCr+TSWXdRDKg==} engines: {node: '>=0.3.1'} @@ -4190,6 +4210,10 @@ packages: fast-uri@3.1.0: resolution: {integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==} + fastest-levenshtein@1.0.16: + resolution: {integrity: sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==} + engines: {node: '>= 4.9.1'} + fastq@1.19.1: resolution: {integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==} @@ -5002,6 +5026,9 @@ packages: lodash.capitalize@4.2.1: resolution: {integrity: sha512-kZzYOKspf8XVX5AvmQF94gQW0lejFVgb80G85bU4ZWzoJ6C03PQg3coYAUpSTpQWelrZELd3XWgHzw4Ck5kaIw==} + lodash.deburr@4.1.0: + resolution: {integrity: sha512-m/M1U1f3ddMCs6Hq2tAsYThTBDaAKFDX3dwDo97GEYzamXi9SqUpjWi/Rrj/gf3X2n8ktwgZrlP1z6E3v/IExQ==} + lodash.defaults@4.2.0: resolution: {integrity: sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==} @@ -5085,6 +5112,9 @@ packages: resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} engines: {node: '>= 0.4'} + md4w@0.2.7: + resolution: {integrity: sha512-lFM7vwk3d4MzkV2mija7aPkK6OjKXZDQsH2beX+e2cvccBoqc6RraheMtAO0Wcr/gjj5L+WS5zhb+06AmuGZrg==} + mdast-util-find-and-replace@3.0.2: resolution: {integrity: sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==} @@ -5121,6 +5151,9 @@ packages: mdast-util-to-string@4.0.0: resolution: {integrity: sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==} + mdbox@0.1.1: + resolution: {integrity: sha512-jvLISenzbLRPWWamTG3THlhTcMbKWzJQNyTi61AVXhCBOC+gsldNTUfUNH8d3Vay83zGehFw3wZpF3xChzkTIQ==} + mdn-data@2.0.28: resolution: {integrity: sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==} @@ -7634,6 +7667,8 @@ snapshots: transitivePeerDependencies: - supports-color + '@babel/runtime@7.28.4': {} + '@babel/template@7.27.2': dependencies: '@babel/code-frame': 7.27.1 @@ -9163,6 +9198,23 @@ snapshots: transitivePeerDependencies: - magicast + '@nuxtjs/mcp-toolkit@0.1.1(magicast@0.5.1)(zod@4.1.13)': + dependencies: + '@clack/prompts': 0.11.0 + '@modelcontextprotocol/sdk': 1.22.0 + '@nuxt/kit': 4.2.1(magicast@0.5.1) + automd: 0.4.2(magicast@0.5.1) + chokidar: 4.0.3 + defu: 6.1.4 + pathe: 2.0.3 + scule: 1.3.0 + tinyglobby: 0.2.15 + zod: 4.1.13 + transitivePeerDependencies: + - '@cfworker/json-schema' + - magicast + - supports-color + '@nuxtjs/mdc@0.18.3(magicast@0.3.5)': dependencies: '@nuxt/kit': 4.2.1(magicast@0.3.5) @@ -10714,6 +10766,28 @@ snapshots: asynckit@0.4.0: {} + automd@0.4.2(magicast@0.5.1): + dependencies: + '@parcel/watcher': 2.5.1 + c12: 3.3.2(magicast@0.5.1) + citty: 0.1.6 + consola: 3.4.2 + defu: 6.1.4 + destr: 2.0.5 + didyoumean2: 7.0.4 + magic-string: 0.30.21 + mdbox: 0.1.1 + mlly: 1.8.0 + ofetch: 1.5.1 + pathe: 2.0.3 + perfect-debounce: 2.0.0 + pkg-types: 2.3.0 + scule: 1.3.0 + tinyglobby: 0.2.15 + untyped: 2.0.0 + transitivePeerDependencies: + - magicast + autoprefixer@10.4.21(postcss@8.5.6): dependencies: browserslist: 4.27.0 @@ -11381,6 +11455,12 @@ snapshots: dfa@1.2.0: {} + didyoumean2@7.0.4: + dependencies: + '@babel/runtime': 7.28.4 + fastest-levenshtein: 1.0.16 + lodash.deburr: 4.1.0 + diff@8.0.2: {} dom-accessibility-api@0.5.16: {} @@ -11902,6 +11982,8 @@ snapshots: fast-uri@3.1.0: {} + fastest-levenshtein@1.0.16: {} + fastq@1.19.1: dependencies: reusify: 1.1.0 @@ -12832,6 +12914,8 @@ snapshots: lodash.capitalize@4.2.1: {} + lodash.deburr@4.1.0: {} + lodash.defaults@4.2.0: {} lodash.escaperegexp@4.1.2: {} @@ -12910,6 +12994,8 @@ snapshots: math-intrinsics@1.1.0: {} + md4w@0.2.7: {} + mdast-util-find-and-replace@3.0.2: dependencies: '@types/mdast': 4.0.4 @@ -13024,6 +13110,10 @@ snapshots: dependencies: '@types/mdast': 4.0.4 + mdbox@0.1.1: + dependencies: + md4w: 0.2.7 + mdn-data@2.0.28: {} mdn-data@2.12.2: {} From 2abe56bf534eac98c9d312ac8e501498da26abba Mon Sep 17 00:00:00 2001 From: Hugo Date: Thu, 27 Nov 2025 12:07:35 +0100 Subject: [PATCH 2/9] Update docs/server/mcp/tools/get-documentation-page.ts Co-authored-by: vercel[bot] <35613825+vercel[bot]@users.noreply.github.com> --- docs/server/mcp/tools/get-documentation-page.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/server/mcp/tools/get-documentation-page.ts b/docs/server/mcp/tools/get-documentation-page.ts index ed3ff761ca..bcb3d3d033 100644 --- a/docs/server/mcp/tools/get-documentation-page.ts +++ b/docs/server/mcp/tools/get-documentation-page.ts @@ -8,7 +8,7 @@ export default defineMcpTool({ handler: async ({ path }) => { const result = await $fetch(`/raw${path}.md`) return { - content: [{ type: 'text' as const, text: JSON.stringify(result, null, 2) }] + content: [{ type: 'text' as const, text: result }] } } }) From 5ff50f5b56485b438af219412893e35987f0320e Mon Sep 17 00:00:00 2001 From: Hugo Richard Date: Thu, 27 Nov 2025 11:20:16 +0000 Subject: [PATCH 3/9] chore: remove unused dependency `@modelcontextprotocol/sdk` from package.json and pnpm-lock.yaml --- docs/package.json | 1 - pnpm-lock.yaml | 3 --- 2 files changed, 4 deletions(-) diff --git a/docs/package.json b/docs/package.json index 0193c48a9d..41b1352548 100644 --- a/docs/package.json +++ b/docs/package.json @@ -16,7 +16,6 @@ "@iconify-json/lucide": "^1.2.74", "@iconify-json/simple-icons": "^1.2.59", "@iconify-json/vscode-icons": "^1.2.36", - "@modelcontextprotocol/sdk": "^1.22.0", "@nuxt/content": "^3.8.2", "@nuxt/image": "^2.0.0", "@nuxt/ui": "workspace:*", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 04c4260422..dfe335559b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -262,9 +262,6 @@ importers: '@iconify-json/vscode-icons': specifier: ^1.2.36 version: 1.2.36 - '@modelcontextprotocol/sdk': - specifier: ^1.22.0 - version: 1.22.0 '@nuxt/content': specifier: ^3.8.2 version: 3.8.2(better-sqlite3@12.4.6)(magicast@0.5.1)(valibot@1.1.0(typescript@5.8.3)) From 96c698dfd648c94d59ee86bed5bb37bd50d9ff21 Mon Sep 17 00:00:00 2001 From: Hugo Richard Date: Thu, 27 Nov 2025 13:07:52 +0000 Subject: [PATCH 4/9] update `@nuxtjs/mcp-toolkit` version --- docs/package.json | 2 +- docs/server/mcp/resources/components.ts | 4 +-- docs/server/mcp/resources/composables.ts | 4 +-- .../mcp/resources/documentation-pages.ts | 4 +-- docs/server/mcp/resources/examples.ts | 4 +-- docs/server/mcp/resources/templates.ts | 4 +-- pnpm-lock.yaml | 34 ++++++++++++------- 7 files changed, 28 insertions(+), 28 deletions(-) diff --git a/docs/package.json b/docs/package.json index 41b1352548..dd0e2627ca 100644 --- a/docs/package.json +++ b/docs/package.json @@ -20,7 +20,7 @@ "@nuxt/image": "^2.0.0", "@nuxt/ui": "workspace:*", "@nuxtjs/plausible": "^2.0.1", - "@nuxtjs/mcp-toolkit": "^0.1.1", + "@nuxtjs/mcp-toolkit": "^0.2.0", "@octokit/rest": "^22.0.1", "@regle/core": "^1.10.2", "@regle/rules": "^1.10.2", diff --git a/docs/server/mcp/resources/components.ts b/docs/server/mcp/resources/components.ts index cbd22c41ff..d5283e0be3 100644 --- a/docs/server/mcp/resources/components.ts +++ b/docs/server/mcp/resources/components.ts @@ -1,8 +1,6 @@ export default defineMcpResource({ uri: 'resource://nuxt-ui/components', - metadata: { - description: 'Complete list of available Nuxt UI v4 components with metadata and categories' - }, + description: 'Complete list of available Nuxt UI v4 components with metadata and categories', handler: async (uri: URL) => { const result = await $fetch('/api/mcp/list-components') return { diff --git a/docs/server/mcp/resources/composables.ts b/docs/server/mcp/resources/composables.ts index 44210ebd9d..0b18851403 100644 --- a/docs/server/mcp/resources/composables.ts +++ b/docs/server/mcp/resources/composables.ts @@ -1,8 +1,6 @@ export default defineMcpResource({ uri: 'resource://nuxt-ui/composables', - metadata: { - description: 'Complete list of available Nuxt UI v4 composables with metadata and categories' - }, + description: 'Complete list of available Nuxt UI v4 composables with metadata and categories', handler: async (uri: URL) => { const result = await $fetch('/api/mcp/list-composables') return { diff --git a/docs/server/mcp/resources/documentation-pages.ts b/docs/server/mcp/resources/documentation-pages.ts index 94f1b9aba8..3371c17e8c 100644 --- a/docs/server/mcp/resources/documentation-pages.ts +++ b/docs/server/mcp/resources/documentation-pages.ts @@ -1,8 +1,6 @@ export default defineMcpResource({ uri: 'resource://nuxt-ui/documentation-pages', - metadata: { - description: 'Complete list of available Nuxt UI documentation pages' - }, + description: 'Complete list of available Nuxt UI documentation pages', handler: async (uri: URL) => { const result = await $fetch('/api/mcp/list-documentation-pages') return { diff --git a/docs/server/mcp/resources/examples.ts b/docs/server/mcp/resources/examples.ts index 898a4ed874..c6312e2acb 100644 --- a/docs/server/mcp/resources/examples.ts +++ b/docs/server/mcp/resources/examples.ts @@ -1,8 +1,6 @@ export default defineMcpResource({ uri: 'resource://nuxt-ui/examples', - metadata: { - description: 'Complete list of available Nuxt UI example code and demonstrations' - }, + description: 'Complete list of available Nuxt UI example code and demonstrations', handler: async (uri: URL) => { const result = await $fetch('/api/mcp/list-examples') return { diff --git a/docs/server/mcp/resources/templates.ts b/docs/server/mcp/resources/templates.ts index 6c4fdf2a68..f8c763f853 100644 --- a/docs/server/mcp/resources/templates.ts +++ b/docs/server/mcp/resources/templates.ts @@ -1,8 +1,6 @@ export default defineMcpResource({ uri: 'resource://nuxt-ui/templates', - metadata: { - description: 'Complete list of available Nuxt UI templates with categories' - }, + description: 'Complete list of available Nuxt UI templates with categories', handler: async (uri: URL) => { const result = await $fetch('/api/mcp/list-templates') return { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index dfe335559b..b6a678e53a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -272,8 +272,8 @@ importers: specifier: workspace:* version: link:.. '@nuxtjs/mcp-toolkit': - specifier: ^0.1.1 - version: 0.1.1(magicast@0.5.1)(zod@4.1.13) + specifier: ^0.2.0 + version: 0.2.0(magicast@0.5.1)(zod@4.1.13) '@nuxtjs/plausible': specifier: ^2.0.1 version: 2.0.1(magicast@0.5.1) @@ -1285,11 +1285,12 @@ packages: engines: {node: '>=18'} hasBin: true - '@modelcontextprotocol/sdk@1.22.0': - resolution: {integrity: sha512-VUpl106XVTCpDmTBil2ehgJZjhyLY2QZikzF8NvTXtLRF1CvO5iEE2UNZdVIUer35vFOwMKYeUGbjJtvPWan3g==} + '@modelcontextprotocol/sdk@1.23.0': + resolution: {integrity: sha512-MCGd4K9aZKvuSqdoBkdMvZNcYXCkZRYVs/Gh92mdV5IHbctX9H9uIvd4X93+9g8tBbXv08sxc/QHXTzf8y65bA==} engines: {node: '>=18'} peerDependencies: '@cfworker/json-schema': ^4.1.1 + zod: ^3.25 || ^4.0 peerDependenciesMeta: '@cfworker/json-schema': optional: true @@ -1471,10 +1472,10 @@ packages: '@nuxtjs/color-mode@3.5.2': resolution: {integrity: sha512-cC6RfgZh3guHBMLLjrBB2Uti5eUoGM9KyauOaYS9ETmxNWBMTvpgjvSiSJp1OFljIXPIqVTJ3xtJpSNZiO3ZaA==} - '@nuxtjs/mcp-toolkit@0.1.1': - resolution: {integrity: sha512-K/JSrPMbPplafDTmiSGLpxIdE9Ve3OLm7F3rvTuk/HNxw7Q/phkQLn+42VI0tEBGUjpMDjFKO+gnkJDTt2zUCQ==} + '@nuxtjs/mcp-toolkit@0.2.0': + resolution: {integrity: sha512-7BBS6IiibSkzCdW5QgLLpETuiyGdzhWXKCJHvRwSMpu4aNYm0dghSt7z5Ahao3xWPpHlmWHajBzQYbfiZZCiZQ==} peerDependencies: - zod: ^3.25.76 + zod: ^4.1.13 '@nuxtjs/mdc@0.18.3': resolution: {integrity: sha512-Fl64a9OZBH3J7ZpqzSWkrS64oFmLLvledZMcnYH3UzVtgFPo/GaICdJN3Ml83NSs/J9At6HHaP0k3+nrxu2qJw==} @@ -7460,6 +7461,11 @@ packages: peerDependencies: zod: ^3.24.1 + zod-to-json-schema@3.25.0: + resolution: {integrity: sha512-HvWtU2UG41LALjajJrML6uQejQhNJx+JBO9IflpSja4R03iNWfKXrj6W2h7ljuLyc1nKS+9yDyL/9tD1U/yBnQ==} + peerDependencies: + zod: ^3.25 || ^4 + zod@3.25.76: resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} @@ -8334,7 +8340,7 @@ snapshots: - encoding - supports-color - '@modelcontextprotocol/sdk@1.22.0': + '@modelcontextprotocol/sdk@1.23.0(zod@4.1.13)': dependencies: ajv: 8.17.1 ajv-formats: 3.0.1(ajv@8.17.1) @@ -8347,8 +8353,8 @@ snapshots: express-rate-limit: 7.5.1(express@5.1.0) pkce-challenge: 5.0.0 raw-body: 3.0.1 - zod: 3.25.76 - zod-to-json-schema: 3.24.6(zod@3.25.76) + zod: 4.1.13 + zod-to-json-schema: 3.25.0(zod@4.1.13) transitivePeerDependencies: - supports-color @@ -9195,10 +9201,10 @@ snapshots: transitivePeerDependencies: - magicast - '@nuxtjs/mcp-toolkit@0.1.1(magicast@0.5.1)(zod@4.1.13)': + '@nuxtjs/mcp-toolkit@0.2.0(magicast@0.5.1)(zod@4.1.13)': dependencies: '@clack/prompts': 0.11.0 - '@modelcontextprotocol/sdk': 1.22.0 + '@modelcontextprotocol/sdk': 1.23.0(zod@4.1.13) '@nuxt/kit': 4.2.1(magicast@0.5.1) automd: 0.4.2(magicast@0.5.1) chokidar: 4.0.3 @@ -16183,6 +16189,10 @@ snapshots: dependencies: zod: 3.25.76 + zod-to-json-schema@3.25.0(zod@4.1.13): + dependencies: + zod: 4.1.13 + zod@3.25.76: {} zod@4.1.13: {} From 14f06e592d9ad5a3425c8d1d5076f106ad1e448a Mon Sep 17 00:00:00 2001 From: Hugo Richard Date: Thu, 27 Nov 2025 13:30:04 +0000 Subject: [PATCH 5/9] chore: add back `@modelcontextprotocol/sdk` --- docs/package.json | 3 ++- pnpm-lock.yaml | 3 +++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/package.json b/docs/package.json index dd0e2627ca..c4afde425e 100644 --- a/docs/package.json +++ b/docs/package.json @@ -16,11 +16,12 @@ "@iconify-json/lucide": "^1.2.74", "@iconify-json/simple-icons": "^1.2.59", "@iconify-json/vscode-icons": "^1.2.36", + "@modelcontextprotocol/sdk": "^1.23.0", "@nuxt/content": "^3.8.2", "@nuxt/image": "^2.0.0", "@nuxt/ui": "workspace:*", - "@nuxtjs/plausible": "^2.0.1", "@nuxtjs/mcp-toolkit": "^0.2.0", + "@nuxtjs/plausible": "^2.0.1", "@octokit/rest": "^22.0.1", "@regle/core": "^1.10.2", "@regle/rules": "^1.10.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b6a678e53a..3a4864732b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -262,6 +262,9 @@ importers: '@iconify-json/vscode-icons': specifier: ^1.2.36 version: 1.2.36 + '@modelcontextprotocol/sdk': + specifier: ^1.23.0 + version: 1.23.0(zod@4.1.13) '@nuxt/content': specifier: ^3.8.2 version: 3.8.2(better-sqlite3@12.4.6)(magicast@0.5.1)(valibot@1.1.0(typescript@5.8.3)) From 7289831c2d2fdc75a9644693cd8fb0d2e4c1128f Mon Sep 17 00:00:00 2001 From: Hugo Richard Date: Fri, 28 Nov 2025 10:47:27 +0000 Subject: [PATCH 6/9] feat: use internal tools cache --- docs/nuxt.config.ts | 1 + docs/package.json | 2 +- docs/server/api/mcp/get-component-metadata.ts | 56 --------------- docs/server/api/mcp/get-component.ts | 46 ------------- docs/server/api/mcp/get-migration-guide.ts | 37 ---------- docs/server/api/mcp/get-template.ts | 26 ------- docs/server/api/mcp/list-components.ts | 13 ---- docs/server/api/mcp/list-composables.ts | 13 ---- .../api/mcp/list-documentation-pages.ts | 14 ---- docs/server/api/mcp/list-examples.ts | 13 ---- .../api/mcp/list-getting-started-guides.ts | 20 ------ docs/server/api/mcp/list-templates.ts | 26 ------- .../api/mcp/search-components-by-category.ts | 51 -------------- .../mcp/prompts/find-component-for-usecase.ts | 16 +++-- .../prompts/implement-component-with-props.ts | 69 +++++++++++++++++-- .../prompts/setup-project-with-template.ts | 14 ++-- docs/server/mcp/resources/components.ts | 16 ++++- docs/server/mcp/resources/composables.ts | 16 ++++- .../mcp/resources/documentation-pages.ts | 16 ++++- docs/server/mcp/resources/examples.ts | 13 +++- docs/server/mcp/resources/templates.ts | 17 ++++- .../mcp/tools/get-component-metadata.ts | 47 +++++++++++-- docs/server/mcp/tools/get-component.ts | 37 ++++++++-- .../mcp/tools/get-documentation-page.ts | 3 +- docs/server/mcp/tools/get-example.ts | 3 +- docs/server/mcp/tools/get-migration-guide.ts | 28 ++++++-- docs/server/mcp/tools/get-template.ts | 18 +++-- docs/server/mcp/tools/list-components.ts | 17 +++-- docs/server/mcp/tools/list-composables.ts | 17 +++-- .../mcp/tools/list-documentation-pages.ts | 18 +++-- docs/server/mcp/tools/list-examples.ts | 15 ++-- .../mcp/tools/list-getting-started-guides.ts | 26 +++++-- docs/server/mcp/tools/list-templates.ts | 23 +++++-- .../tools/search-components-by-category.ts | 44 ++++++++++-- pnpm-lock.yaml | 11 +-- 35 files changed, 399 insertions(+), 403 deletions(-) delete mode 100644 docs/server/api/mcp/get-component-metadata.ts delete mode 100644 docs/server/api/mcp/get-component.ts delete mode 100644 docs/server/api/mcp/get-migration-guide.ts delete mode 100644 docs/server/api/mcp/get-template.ts delete mode 100644 docs/server/api/mcp/list-components.ts delete mode 100644 docs/server/api/mcp/list-composables.ts delete mode 100644 docs/server/api/mcp/list-documentation-pages.ts delete mode 100644 docs/server/api/mcp/list-examples.ts delete mode 100644 docs/server/api/mcp/list-getting-started-guides.ts delete mode 100644 docs/server/api/mcp/list-templates.ts delete mode 100644 docs/server/api/mcp/search-components-by-category.ts diff --git a/docs/nuxt.config.ts b/docs/nuxt.config.ts index 8b632422f1..03592bf67c 100644 --- a/docs/nuxt.config.ts +++ b/docs/nuxt.config.ts @@ -157,6 +157,7 @@ export default defineNuxtConfig({ }, experimental: { + asyncContext: true, defaults: { nuxtLink: { externalRelAttribute: 'noopener' diff --git a/docs/package.json b/docs/package.json index c4afde425e..a44646793e 100644 --- a/docs/package.json +++ b/docs/package.json @@ -20,7 +20,7 @@ "@nuxt/content": "^3.8.2", "@nuxt/image": "^2.0.0", "@nuxt/ui": "workspace:*", - "@nuxtjs/mcp-toolkit": "^0.2.0", + "@nuxtjs/mcp-toolkit": "^0.4.1", "@nuxtjs/plausible": "^2.0.1", "@octokit/rest": "^22.0.1", "@regle/core": "^1.10.2", diff --git a/docs/server/api/mcp/get-component-metadata.ts b/docs/server/api/mcp/get-component-metadata.ts deleted file mode 100644 index efec167a44..0000000000 --- a/docs/server/api/mcp/get-component-metadata.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { z } from 'zod' -import { camelCase, upperFirst, kebabCase } from 'scule' -import { normalizeComponentName } from '~~/server/utils/normalizeComponentName' -import { queryCollection } from '@nuxt/content/server' - -const querySchema = z.object({ - componentName: z.string() -}) - -export default defineCachedEventHandler(async (event) => { - const { componentName } = await getValidatedQuery(event, querySchema.parse) - - // Normalize component name by removing "U" or "u-" prefix if present - const normalizedName = normalizeComponentName(componentName) - - // Convert to kebab-case for path lookup - const kebabName = kebabCase(normalizedName) - - // Get basic component info without documentation content - const page = await queryCollection(event, 'docs') - .where('path', 'LIKE', `%/components/${kebabName}`) - .where('extension', '=', 'md') - .select('id', 'title', 'description', 'path', 'category', 'links') - .first() - - if (!page) { - throw createError({ - statusCode: 404, - statusMessage: `Component '${componentName}' not found in documentation` - }) - } - - // Use the same approach as the docs components for metadata - const camelName = camelCase(normalizedName) - const componentMetaName = `U${upperFirst(camelName)}` - - const metadata = await $fetch(`/api/component-meta/${componentMetaName}.json`) - - return { - name: normalizedName, - title: page.title, - description: page.description, - category: page.category, - documentation_url: `https://ui.nuxt.com${page.path}`, - metadata: { - pascalName: metadata.pascalName, - kebabName: metadata.kebabName, - props: metadata.meta.props, - slots: metadata.meta.slots, - emits: metadata.meta.emits - } - } -}, { - name: 'mcp-get-component-metadata-only', - maxAge: 1800 // 30 minutes -}) diff --git a/docs/server/api/mcp/get-component.ts b/docs/server/api/mcp/get-component.ts deleted file mode 100644 index aaab60f9d1..0000000000 --- a/docs/server/api/mcp/get-component.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { z } from 'zod' -import { kebabCase } from 'scule' -import { normalizeComponentName } from '~~/server/utils/normalizeComponentName' -import { queryCollection } from '@nuxt/content/server' - -const querySchema = z.object({ - componentName: z.string() -}) - -export default defineCachedEventHandler(async (event) => { - const { componentName } = await getValidatedQuery(event, querySchema.parse) - - // Normalize component name by removing "U" or "u-" prefix if present - const normalizedName = normalizeComponentName(componentName) - - // Convert to kebab-case for path lookup - const kebabName = kebabCase(normalizedName) - - // Get component documentation using queryCollection like in pages/components.vue - const page = await queryCollection(event, 'docs') - .where('path', 'LIKE', `%/components/${kebabName}`) - .where('extension', '=', 'md') - .select('id', 'title', 'description', 'path', 'category', 'links') - .first() - - if (!page) { - throw createError({ - statusCode: 404, - statusMessage: `Component '${componentName}' not found in documentation` - }) - } - - const documentation = await $fetch(`/raw${page.path}.md`) - - return { - name: normalizedName, - title: page.title, - description: page.description, - category: page.category, - documentation, - documentation_url: `https://ui.nuxt.com${page.path}` - } -}, { - name: 'mcp-get-component', - maxAge: 1800 // 30 minutes -}) diff --git a/docs/server/api/mcp/get-migration-guide.ts b/docs/server/api/mcp/get-migration-guide.ts deleted file mode 100644 index 74555a1e77..0000000000 --- a/docs/server/api/mcp/get-migration-guide.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { z } from 'zod' -import { queryCollection } from '@nuxt/content/server' - -const querySchema = z.object({ - version: z.string().describe('The migration version (e.g., v4, v3)') -}) - -export default defineCachedEventHandler(async (event) => { - const { version } = await getValidatedQuery(event, querySchema.parse) - - const page = await queryCollection(event, 'docs') - .where('path', 'LIKE', `%/migration/${version}`) - .where('extension', '=', 'md') - .select('id', 'title', 'description', 'path', 'body') - .first() - - if (!page) { - throw createError({ - statusCode: 404, - statusMessage: `Migration guide for '${version}' not found` - }) - } - - const documentation = await $fetch(`/raw${page.path}.md`) - - return { - version, - title: page.title, - description: page.description, - path: page.path, - documentation, - url: `https://ui.nuxt.com${page.path}` - } -}, { - name: 'mcp-get-migration-guide', - maxAge: 1800 // 30 minutes -}) diff --git a/docs/server/api/mcp/get-template.ts b/docs/server/api/mcp/get-template.ts deleted file mode 100644 index 2d6726f0dd..0000000000 --- a/docs/server/api/mcp/get-template.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { z } from 'zod' -import { queryCollection } from '@nuxt/content/server' - -const querySchema = z.object({ - templateName: z.string().min(1, 'Missing templateName') -}) - -export default defineCachedEventHandler(async (event) => { - const { templateName } = await getValidatedQuery(event, querySchema.parse) - - const template = await queryCollection(event, 'templates') - .where('title', '=', templateName) - .first() - - if (!template) { - throw createError({ - statusCode: 404, - statusMessage: `Template "${templateName}" not found. Use the list_templates tool to see all available templates.` - }) - } - - return template -}, { - name: 'mcp-get-template', - maxAge: 1800 // 30 minutes -}) diff --git a/docs/server/api/mcp/list-components.ts b/docs/server/api/mcp/list-components.ts deleted file mode 100644 index 211b2b5104..0000000000 --- a/docs/server/api/mcp/list-components.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { queryCollection } from '@nuxt/content/server' - -export default defineCachedEventHandler(async (event) => { - // Use the same approach as /pages/components.vue - return await queryCollection(event, 'docs') - .where('path', 'LIKE', '%/components/%') - .where('extension', '=', 'md') - .select('path', 'title', 'description', 'category') - .all() -}, { - name: 'mcp-list-components', - maxAge: 3600 // 1 hour -}) diff --git a/docs/server/api/mcp/list-composables.ts b/docs/server/api/mcp/list-composables.ts deleted file mode 100644 index d748383923..0000000000 --- a/docs/server/api/mcp/list-composables.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { queryCollection } from '@nuxt/content/server' - -export default defineCachedEventHandler(async (event) => { - // Use the same approach as /pages/components.vue - return await queryCollection(event, 'docs') - .where('path', 'LIKE', '%/composables/%') - .where('extension', '=', 'md') - .select('path', 'title', 'description') - .all() -}, { - name: 'mcp-list-composables', - maxAge: 3600 // 1 hour -}) diff --git a/docs/server/api/mcp/list-documentation-pages.ts b/docs/server/api/mcp/list-documentation-pages.ts deleted file mode 100644 index a409edf5a0..0000000000 --- a/docs/server/api/mcp/list-documentation-pages.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { queryCollection } from '@nuxt/content/server' - -export default defineCachedEventHandler(async (event) => { - const page = await queryCollection(event, 'docs').all() - - return page.map(doc => ({ - title: doc.title, - description: doc.description, - path: doc.path - })) -}, { - name: 'mcp-list-documentation-pages', - maxAge: 3600 // 1 hour -}) diff --git a/docs/server/api/mcp/list-examples.ts b/docs/server/api/mcp/list-examples.ts deleted file mode 100644 index edaaab2def..0000000000 --- a/docs/server/api/mcp/list-examples.ts +++ /dev/null @@ -1,13 +0,0 @@ -// @ts-expect-error - no types available -import components from '#component-example/nitro' - -export default defineCachedEventHandler((event) => { - appendHeader(event, 'Access-Control-Allow-Origin', '*') - - return Object.entries<{ pascalName: string }>(components).map(([_key, value]) => { - return value.pascalName - }) -}, { - name: 'mcp-list-examples', - maxAge: 3600 // 1 hour -}) diff --git a/docs/server/api/mcp/list-getting-started-guides.ts b/docs/server/api/mcp/list-getting-started-guides.ts deleted file mode 100644 index a06599a4e2..0000000000 --- a/docs/server/api/mcp/list-getting-started-guides.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { queryCollection } from '@nuxt/content/server' - -export default defineCachedEventHandler(async (event) => { - const pages = await queryCollection(event, 'docs') - .where('path', 'LIKE', '/docs/getting-started/%') - .where('extension', '=', 'md') - .select('id', 'title', 'description', 'path', 'navigation') - .all() - - return pages.map(page => ({ - title: page.title, - description: page.description, - path: page.path, - url: `https://ui.nuxt.com${page.path}`, - navigation: page.navigation - })).sort((a, b) => a.path.localeCompare(b.path)) -}, { - name: 'mcp-list-getting-started-guides', - maxAge: 1800 // 30 minutes -}) diff --git a/docs/server/api/mcp/list-templates.ts b/docs/server/api/mcp/list-templates.ts deleted file mode 100644 index a42a759df4..0000000000 --- a/docs/server/api/mcp/list-templates.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { z } from 'zod' -import { queryCollection } from '@nuxt/content/server' - -const querySchema = z.object({ - framework: z.enum(['vue', 'nuxt']).optional() -}) - -export default defineCachedEventHandler(async (event) => { - const { framework } = await getValidatedQuery(event, querySchema.parse) - - const templatesCollectionItems = await queryCollection(event, 'templates').first() - - const templateListing = templatesCollectionItems?.items || [] - - const filteredTemplates = framework - ? templateListing.filter(template => template.framework === framework) - : templateListing - - return { - templates: filteredTemplates, - total: filteredTemplates.length - } -}, { - name: 'mcp-list-templates', - maxAge: 0 // 1 hour -}) diff --git a/docs/server/api/mcp/search-components-by-category.ts b/docs/server/api/mcp/search-components-by-category.ts deleted file mode 100644 index c9679cb450..0000000000 --- a/docs/server/api/mcp/search-components-by-category.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { z } from 'zod' -import { queryCollection } from '@nuxt/content/server' - -const querySchema = z.object({ - category: z.string().optional().describe('Filter components by category'), - search: z.string().optional().describe('Search term to filter components by name or description') -}) - -export default defineCachedEventHandler(async (event) => { - const { category, search } = await getValidatedQuery(event, querySchema.parse) - - let query = queryCollection(event, 'docs') - .where('path', 'LIKE', '/docs/components/%') - .where('extension', '=', 'md') - .select('id', 'title', 'description', 'path', 'category', 'links') - - if (category) { - query = query.where('category', '=', category) - } - - const components = await query.all() - - let results = components.map(component => ({ - name: component.path.split('/').pop(), - title: component.title, - description: component.description, - category: component.category, - path: component.path, - url: `https://ui.nuxt.com${component.path}`, - links: component.links - })) - - // Apply search filter if provided - if (search) { - const searchLower = search.toLowerCase() - results = results.filter(component => - component.name?.toLowerCase().includes(searchLower) - || component.title?.toLowerCase().includes(searchLower) - || component.description?.toLowerCase().includes(searchLower) - ) - } - - return { - components: results.sort((a, b) => (a.name || '').localeCompare(b.name || '')), - total: results.length, - filters: { category, search } - } -}, { - name: 'mcp-search-components-by-category', - maxAge: 1800 // 30 minutes -}) diff --git a/docs/server/mcp/prompts/find-component-for-usecase.ts b/docs/server/mcp/prompts/find-component-for-usecase.ts index 0799baa41c..d3a560431d 100644 --- a/docs/server/mcp/prompts/find-component-for-usecase.ts +++ b/docs/server/mcp/prompts/find-component-for-usecase.ts @@ -1,18 +1,26 @@ import { z } from 'zod/v3' +import { queryCollection } from '@nuxt/content/server' export default defineMcpPrompt({ description: 'Find the best Nuxt UI component for a specific use case', inputSchema: { usecase: z.string().describe('Describe what you want to build (e.g., "user login form", "data table", "navigation menu")') }, - handler: async ({ usecase }) => { - const components = await $fetch('/api/mcp/list-components') + async handler({ usecase }) { + const event = useEvent() + + const components = await queryCollection(event, 'docs') + .where('path', 'LIKE', '%/components/%') + .where('extension', '=', 'md') + .select('path', 'title', 'description', 'category') + .all() + return { messages: [ { - role: 'user', + role: 'user' as const, content: { - type: 'text', + type: 'text' as const, text: `Help me find the best Nuxt UI component for this use case: "${usecase}". Here are all available components: ${JSON.stringify(components, null, 2)}` } } diff --git a/docs/server/mcp/prompts/implement-component-with-props.ts b/docs/server/mcp/prompts/implement-component-with-props.ts index 1e7d02eb5e..272b83ee40 100644 --- a/docs/server/mcp/prompts/implement-component-with-props.ts +++ b/docs/server/mcp/prompts/implement-component-with-props.ts @@ -1,4 +1,7 @@ import { z } from 'zod/v3' +import { kebabCase, camelCase, upperFirst } from 'scule' +import { queryCollection } from '@nuxt/content/server' +import { normalizeComponentName } from '~~/server/utils/normalizeComponentName' export default defineMcpPrompt({ description: 'Generate complete component implementation with proper props and styling', @@ -6,16 +9,70 @@ export default defineMcpPrompt({ componentName: z.string().describe('The Nuxt UI component name (PascalCase)'), requirements: z.string().optional().describe('Specific requirements or customizations needed') }, - handler: async ({ componentName, requirements }) => { - const component = await $fetch('/api/mcp/get-component', { - query: { componentName, includeMetadata: true } - }) + async handler({ componentName, requirements }) { + const event = useEvent() + + // Normalize component name by removing "U" or "u-" prefix if present + const normalizedName = normalizeComponentName(componentName) + + // Convert to kebab-case for path lookup + const kebabName = kebabCase(normalizedName) + + // Get component documentation + const page = await queryCollection(event, 'docs') + .where('path', 'LIKE', `%/components/${kebabName}`) + .where('extension', '=', 'md') + .select('id', 'title', 'description', 'path', 'category', 'links') + .first() + + if (!page) { + return { + messages: [ + { + role: 'user' as const, + content: { + type: 'text' as const, + text: `Component '${componentName}' not found in documentation. Please use a valid Nuxt UI component name.` + } + } + ] + } + } + + // Get component metadata + const camelName = camelCase(normalizedName) + const componentMetaName = `U${upperFirst(camelName)}` + + let metadata = null + try { + metadata = await $fetch(`/api/component-meta/${componentMetaName}.json`) + } catch { + // Metadata not available + } + + const component = { + name: normalizedName, + title: page.title, + description: page.description, + category: page.category, + documentation_url: `https://ui.nuxt.com${page.path}`, + metadata: metadata + ? { + pascalName: metadata.pascalName, + kebabName: metadata.kebabName, + props: metadata.meta.props, + slots: metadata.meta.slots, + emits: metadata.meta.emits + } + : null + } + return { messages: [ { - role: 'user', + role: 'user' as const, content: { - type: 'text', + type: 'text' as const, text: `Generate a complete implementation of the ${componentName} component with proper props and styling. ${requirements ? `Requirements: ${requirements}` : ''}\n\nComponent details: ${JSON.stringify(component, null, 2)}` } } diff --git a/docs/server/mcp/prompts/setup-project-with-template.ts b/docs/server/mcp/prompts/setup-project-with-template.ts index cfa39cc41b..1799219dcd 100644 --- a/docs/server/mcp/prompts/setup-project-with-template.ts +++ b/docs/server/mcp/prompts/setup-project-with-template.ts @@ -1,18 +1,24 @@ import { z } from 'zod/v3' +import { queryCollection } from '@nuxt/content/server' export default defineMcpPrompt({ description: 'Guide through setting up a new project with a Nuxt UI template', inputSchema: { projectType: z.string().describe('Type of project (dashboard, landing page, admin panel, etc.)') }, - handler: async ({ projectType }) => { - const templates = await $fetch('/api/mcp/list-templates') + async handler({ projectType }) { + const event = useEvent() + + const templatesCollectionItems = await queryCollection(event, 'templates').first() + + const templates = templatesCollectionItems?.items || [] + return { messages: [ { - role: 'user', + role: 'user' as const, content: { - type: 'text', + type: 'text' as const, text: `Guide me through setting up a new ${projectType} project with Nuxt UI. Here are available templates: ${JSON.stringify(templates, null, 2)}` } } diff --git a/docs/server/mcp/resources/components.ts b/docs/server/mcp/resources/components.ts index d5283e0be3..520f3a9692 100644 --- a/docs/server/mcp/resources/components.ts +++ b/docs/server/mcp/resources/components.ts @@ -1,13 +1,23 @@ +import { queryCollection } from '@nuxt/content/server' + export default defineMcpResource({ uri: 'resource://nuxt-ui/components', description: 'Complete list of available Nuxt UI v4 components with metadata and categories', - handler: async (uri: URL) => { - const result = await $fetch('/api/mcp/list-components') + cache: '1h', + async handler(uri: URL) { + const event = useEvent() + + const components = await queryCollection(event, 'docs') + .where('path', 'LIKE', '%/components/%') + .where('extension', '=', 'md') + .select('path', 'title', 'description', 'category') + .all() + return { contents: [{ uri: uri.toString(), mimeType: 'application/json', - text: JSON.stringify(result, null, 2) + text: JSON.stringify(components, null, 2) }] } } diff --git a/docs/server/mcp/resources/composables.ts b/docs/server/mcp/resources/composables.ts index 0b18851403..466e0575f6 100644 --- a/docs/server/mcp/resources/composables.ts +++ b/docs/server/mcp/resources/composables.ts @@ -1,13 +1,23 @@ +import { queryCollection } from '@nuxt/content/server' + export default defineMcpResource({ uri: 'resource://nuxt-ui/composables', description: 'Complete list of available Nuxt UI v4 composables with metadata and categories', - handler: async (uri: URL) => { - const result = await $fetch('/api/mcp/list-composables') + cache: '1h', + async handler(uri: URL) { + const event = useEvent() + + const composables = await queryCollection(event, 'docs') + .where('path', 'LIKE', '%/composables/%') + .where('extension', '=', 'md') + .select('path', 'title', 'description') + .all() + return { contents: [{ uri: uri.toString(), mimeType: 'application/json', - text: JSON.stringify(result, null, 2) + text: JSON.stringify(composables, null, 2) }] } } diff --git a/docs/server/mcp/resources/documentation-pages.ts b/docs/server/mcp/resources/documentation-pages.ts index 3371c17e8c..4d1e9177bf 100644 --- a/docs/server/mcp/resources/documentation-pages.ts +++ b/docs/server/mcp/resources/documentation-pages.ts @@ -1,8 +1,20 @@ +import { queryCollection } from '@nuxt/content/server' + export default defineMcpResource({ uri: 'resource://nuxt-ui/documentation-pages', description: 'Complete list of available Nuxt UI documentation pages', - handler: async (uri: URL) => { - const result = await $fetch('/api/mcp/list-documentation-pages') + cache: '1h', + async handler(uri: URL) { + const event = useEvent() + + const pages = await queryCollection(event, 'docs').all() + + const result = pages.map(doc => ({ + title: doc.title, + description: doc.description, + path: doc.path + })) + return { contents: [{ uri: uri.toString(), diff --git a/docs/server/mcp/resources/examples.ts b/docs/server/mcp/resources/examples.ts index c6312e2acb..bcc914a90c 100644 --- a/docs/server/mcp/resources/examples.ts +++ b/docs/server/mcp/resources/examples.ts @@ -1,13 +1,20 @@ +// @ts-expect-error - no types available +import components from '#component-example/nitro' + export default defineMcpResource({ uri: 'resource://nuxt-ui/examples', description: 'Complete list of available Nuxt UI example code and demonstrations', - handler: async (uri: URL) => { - const result = await $fetch('/api/mcp/list-examples') + cache: '1h', + handler(uri: URL) { + const examples = Object.entries<{ pascalName: string }>(components).map(([_key, value]) => { + return value.pascalName + }) + return { contents: [{ uri: uri.toString(), mimeType: 'application/json', - text: JSON.stringify(result, null, 2) + text: JSON.stringify(examples, null, 2) }] } } diff --git a/docs/server/mcp/resources/templates.ts b/docs/server/mcp/resources/templates.ts index f8c763f853..83f14a5f13 100644 --- a/docs/server/mcp/resources/templates.ts +++ b/docs/server/mcp/resources/templates.ts @@ -1,13 +1,24 @@ +import { queryCollection } from '@nuxt/content/server' + export default defineMcpResource({ uri: 'resource://nuxt-ui/templates', description: 'Complete list of available Nuxt UI templates with categories', - handler: async (uri: URL) => { - const result = await $fetch('/api/mcp/list-templates') + cache: '1h', + async handler(uri: URL) { + const event = useEvent() + + const templatesCollectionItems = await queryCollection(event, 'templates').first() + + const templateListing = templatesCollectionItems?.items || [] + return { contents: [{ uri: uri.toString(), mimeType: 'application/json', - text: JSON.stringify(result, null, 2) + text: JSON.stringify({ + templates: templateListing, + total: templateListing.length + }, null, 2) }] } } diff --git a/docs/server/mcp/tools/get-component-metadata.ts b/docs/server/mcp/tools/get-component-metadata.ts index 369e387a3c..d9c3dafffb 100644 --- a/docs/server/mcp/tools/get-component-metadata.ts +++ b/docs/server/mcp/tools/get-component-metadata.ts @@ -1,14 +1,53 @@ import { z } from 'zod/v3' +import { camelCase, upperFirst, kebabCase } from 'scule' +import { queryCollection } from '@nuxt/content/server' +import { normalizeComponentName } from '~~/server/utils/normalizeComponentName' export default defineMcpTool({ description: 'Retrieves detailed metadata for a Nuxt UI component including props, slots, and events', inputSchema: { componentName: z.string().describe('The name of the component (PascalCase)') }, - handler: async ({ componentName }) => { - const result = await $fetch('/api/mcp/get-component-metadata', { query: { componentName } }) - return { - content: [{ type: 'text' as const, text: JSON.stringify(result, null, 2) }] + cache: '30m', + async handler({ componentName }) { + const event = useEvent() + + // Normalize component name by removing "U" or "u-" prefix if present + const normalizedName = normalizeComponentName(componentName) + + // Convert to kebab-case for path lookup + const kebabName = kebabCase(normalizedName) + + // Get basic component info without documentation content + const page = await queryCollection(event, 'docs') + .where('path', 'LIKE', `%/components/${kebabName}`) + .where('extension', '=', 'md') + .select('id', 'title', 'description', 'path', 'category', 'links') + .first() + + if (!page) { + return errorResult(`Component '${componentName}' not found in documentation`) } + + // Use the same approach as the docs components for metadata + const camelName = camelCase(normalizedName) + const componentMetaName = `U${upperFirst(camelName)}` + + const metadata = await $fetch(`/api/component-meta/${componentMetaName}.json`) + + return jsonResult({ + name: normalizedName, + title: page.title, + description: page.description, + category: page.category, + documentation_url: `https://ui.nuxt.com${page.path}`, + metadata: { + pascalName: metadata.pascalName, + kebabName: metadata.kebabName, + props: metadata.meta.props, + slots: metadata.meta.slots, + emits: metadata.meta.emits + } + }) } }) diff --git a/docs/server/mcp/tools/get-component.ts b/docs/server/mcp/tools/get-component.ts index 084cbe36d6..31640ef415 100644 --- a/docs/server/mcp/tools/get-component.ts +++ b/docs/server/mcp/tools/get-component.ts @@ -1,14 +1,43 @@ import { z } from 'zod/v3' +import { kebabCase } from 'scule' +import { queryCollection } from '@nuxt/content/server' +import { normalizeComponentName } from '~~/server/utils/normalizeComponentName' export default defineMcpTool({ description: 'Retrieves Nuxt UI component documentation and details', inputSchema: { componentName: z.string().describe('The name of the component (PascalCase)') }, - handler: async ({ componentName }) => { - const result = await $fetch('/api/mcp/get-component', { query: { componentName } }) - return { - content: [{ type: 'text' as const, text: JSON.stringify(result, null, 2) }] + cache: '30m', + async handler({ componentName }) { + const event = useEvent() + + // Normalize component name by removing "U" or "u-" prefix if present + const normalizedName = normalizeComponentName(componentName) + + // Convert to kebab-case for path lookup + const kebabName = kebabCase(normalizedName) + + // Get component documentation using queryCollection + const page = await queryCollection(event, 'docs') + .where('path', 'LIKE', `%/components/${kebabName}`) + .where('extension', '=', 'md') + .select('id', 'title', 'description', 'path', 'category', 'links') + .first() + + if (!page) { + return errorResult(`Component '${componentName}' not found in documentation`) } + + const documentation = await $fetch(`/raw${page.path}.md`) + + return jsonResult({ + name: normalizedName, + title: page.title, + description: page.description, + category: page.category, + documentation, + documentation_url: `https://ui.nuxt.com${page.path}` + }) } }) diff --git a/docs/server/mcp/tools/get-documentation-page.ts b/docs/server/mcp/tools/get-documentation-page.ts index bcb3d3d033..1d1c4a306c 100644 --- a/docs/server/mcp/tools/get-documentation-page.ts +++ b/docs/server/mcp/tools/get-documentation-page.ts @@ -5,7 +5,8 @@ export default defineMcpTool({ inputSchema: { path: z.string().describe('The path to the content page (e.g., /docs/components/button)') }, - handler: async ({ path }) => { + cache: '30m', + async handler({ path }) { const result = await $fetch(`/raw${path}.md`) return { content: [{ type: 'text' as const, text: result }] diff --git a/docs/server/mcp/tools/get-example.ts b/docs/server/mcp/tools/get-example.ts index 1d3106d37c..c1659bd34f 100644 --- a/docs/server/mcp/tools/get-example.ts +++ b/docs/server/mcp/tools/get-example.ts @@ -5,7 +5,8 @@ export default defineMcpTool({ inputSchema: { exampleName: z.string().describe('The name of the example (PascalCase)') }, - handler: async ({ exampleName }) => { + cache: '30m', + async handler({ exampleName }) { const result = await $fetch<{ code: string }>(`/api/component-example/${exampleName}.json`) return { content: [{ type: 'text' as const, text: result.code }] diff --git a/docs/server/mcp/tools/get-migration-guide.ts b/docs/server/mcp/tools/get-migration-guide.ts index 47b5c02376..e550f7e976 100644 --- a/docs/server/mcp/tools/get-migration-guide.ts +++ b/docs/server/mcp/tools/get-migration-guide.ts @@ -1,14 +1,34 @@ import { z } from 'zod/v3' +import { queryCollection } from '@nuxt/content/server' export default defineMcpTool({ description: 'Retrieves version-specific migration guides and upgrade instructions', inputSchema: { version: z.enum(['v3', 'v4']).describe('The migration version (e.g., v4, v3)') }, - handler: async ({ version }) => { - const result = await $fetch('/api/mcp/get-migration-guide', { query: { version } }) - return { - content: [{ type: 'text' as const, text: JSON.stringify(result, null, 2) }] + cache: '30m', + async handler({ version }) { + const event = useEvent() + + const page = await queryCollection(event, 'docs') + .where('path', 'LIKE', `%/migration/${version}`) + .where('extension', '=', 'md') + .select('id', 'title', 'description', 'path', 'body') + .first() + + if (!page) { + return errorResult(`Migration guide for '${version}' not found`) } + + const documentation = await $fetch(`/raw${page.path}.md`) + + return jsonResult({ + version, + title: page.title, + description: page.description, + path: page.path, + documentation, + url: `https://ui.nuxt.com${page.path}` + }) } }) diff --git a/docs/server/mcp/tools/get-template.ts b/docs/server/mcp/tools/get-template.ts index 9bc63c22d2..9ff8650cec 100644 --- a/docs/server/mcp/tools/get-template.ts +++ b/docs/server/mcp/tools/get-template.ts @@ -1,15 +1,23 @@ import { z } from 'zod/v3' +import { queryCollection } from '@nuxt/content/server' export default defineMcpTool({ description: 'Retrieves template details and setup instructions', inputSchema: { templateName: z.string().describe('The name of the template') }, - handler: async ({ templateName }) => { - const result = await $fetch('/api/mcp/get-template', { query: { templateName } }) - return { - content: [{ type: 'text' as const, text: JSON.stringify(result, null, 2) }], - structuredContent: result as any + cache: '30m', + async handler({ templateName }) { + const event = useEvent() + + const template = await queryCollection(event, 'templates') + .where('title', '=', templateName) + .first() + + if (!template) { + return errorResult(`Template "${templateName}" not found. Use the list_templates tool to see all available templates.`) } + + return jsonResult(template) } }) diff --git a/docs/server/mcp/tools/list-components.ts b/docs/server/mcp/tools/list-components.ts index 1e753045c8..68d6a3e469 100644 --- a/docs/server/mcp/tools/list-components.ts +++ b/docs/server/mcp/tools/list-components.ts @@ -1,9 +1,16 @@ +import { queryCollection } from '@nuxt/content/server' + export default defineMcpTool({ description: 'Lists all available Nuxt UI components with their categories and basic information', - handler: async () => { - const result = await $fetch('/api/mcp/list-components') - return { - content: [{ type: 'text' as const, text: JSON.stringify(result, null, 2) }] - } + cache: '1h', + async handler() { + const event = useEvent() + const components = await queryCollection(event, 'docs') + .where('path', 'LIKE', '%/components/%') + .where('extension', '=', 'md') + .select('path', 'title', 'description', 'category') + .all() + + return jsonResult(components) } }) diff --git a/docs/server/mcp/tools/list-composables.ts b/docs/server/mcp/tools/list-composables.ts index 92a2685c53..47b9d567a8 100644 --- a/docs/server/mcp/tools/list-composables.ts +++ b/docs/server/mcp/tools/list-composables.ts @@ -1,9 +1,16 @@ +import { queryCollection } from '@nuxt/content/server' + export default defineMcpTool({ description: 'Lists all available Nuxt UI composables with their categories and basic information', - handler: async () => { - const result = await $fetch('/api/mcp/list-composables') - return { - content: [{ type: 'text' as const, text: JSON.stringify(result, null, 2) }] - } + cache: '1h', + async handler() { + const event = useEvent() + const composables = await queryCollection(event, 'docs') + .where('path', 'LIKE', '%/composables/%') + .where('extension', '=', 'md') + .select('path', 'title', 'description') + .all() + + return jsonResult(composables) } }) diff --git a/docs/server/mcp/tools/list-documentation-pages.ts b/docs/server/mcp/tools/list-documentation-pages.ts index ef0fc95d64..3b120062c4 100644 --- a/docs/server/mcp/tools/list-documentation-pages.ts +++ b/docs/server/mcp/tools/list-documentation-pages.ts @@ -1,9 +1,17 @@ +import { queryCollection } from '@nuxt/content/server' + export default defineMcpTool({ description: 'Lists all documentation pages', - handler: async () => { - const result = await $fetch('/api/mcp/list-documentation-pages') - return { - content: [{ type: 'text' as const, text: JSON.stringify(result, null, 2) }] - } + cache: '1h', + async handler() { + const event = useEvent() + + const pages = await queryCollection(event, 'docs').all() + + return jsonResult(pages.map(doc => ({ + title: doc.title, + description: doc.description, + path: doc.path + }))) } }) diff --git a/docs/server/mcp/tools/list-examples.ts b/docs/server/mcp/tools/list-examples.ts index a33daad04e..e67b4dac65 100644 --- a/docs/server/mcp/tools/list-examples.ts +++ b/docs/server/mcp/tools/list-examples.ts @@ -1,9 +1,14 @@ +// @ts-expect-error - no types available +import components from '#component-example/nitro' + export default defineMcpTool({ description: 'Lists all available UI examples and code demonstrations', - handler: async () => { - const result = await $fetch('/api/mcp/list-examples') - return { - content: [{ type: 'text' as const, text: JSON.stringify(result, null, 2) }] - } + cache: '1h', + handler() { + const examples = Object.entries<{ pascalName: string }>(components).map(([_key, value]) => { + return value.pascalName + }) + + return jsonResult(examples) } }) diff --git a/docs/server/mcp/tools/list-getting-started-guides.ts b/docs/server/mcp/tools/list-getting-started-guides.ts index 3e067b1a74..4d6f0c4f08 100644 --- a/docs/server/mcp/tools/list-getting-started-guides.ts +++ b/docs/server/mcp/tools/list-getting-started-guides.ts @@ -1,9 +1,25 @@ +import { queryCollection } from '@nuxt/content/server' + export default defineMcpTool({ description: 'Lists all getting started guides and installation instructions', - handler: async () => { - const result = await $fetch('/api/mcp/list-getting-started-guides') - return { - content: [{ type: 'text' as const, text: JSON.stringify(result, null, 2) }] - } + cache: '30m', + async handler() { + const event = useEvent() + + const pages = await queryCollection(event, 'docs') + .where('path', 'LIKE', '/docs/getting-started/%') + .where('extension', '=', 'md') + .select('id', 'title', 'description', 'path', 'navigation') + .all() + + const result = pages.map(page => ({ + title: page.title, + description: page.description, + path: page.path, + url: `https://ui.nuxt.com${page.path}`, + navigation: page.navigation + })).sort((a, b) => a.path.localeCompare(b.path)) + + return jsonResult(result) } }) diff --git a/docs/server/mcp/tools/list-templates.ts b/docs/server/mcp/tools/list-templates.ts index fc458b4d56..2cb11d36d2 100644 --- a/docs/server/mcp/tools/list-templates.ts +++ b/docs/server/mcp/tools/list-templates.ts @@ -1,15 +1,26 @@ import { z } from 'zod/v3' +import { queryCollection } from '@nuxt/content/server' export default defineMcpTool({ description: 'Lists all available Nuxt UI templates with optional category filtering', inputSchema: { category: z.string().optional().describe('Filter templates by category') }, - handler: async ({ category }) => { - const result = await $fetch('/api/mcp/list-templates', { query: { category } }) - return { - content: [{ type: 'text' as const, text: JSON.stringify(result, null, 2) }], - structuredContent: result as any - } + cache: '1h', + async handler({ category }) { + const event = useEvent() + + const templatesCollectionItems = await queryCollection(event, 'templates').first() + + const templateListing = templatesCollectionItems?.items || [] + + const filteredTemplates = category + ? templateListing.filter((template: any) => template.framework === category) + : templateListing + + return jsonResult({ + templates: filteredTemplates, + total: filteredTemplates.length + }) } }) diff --git a/docs/server/mcp/tools/search-components-by-category.ts b/docs/server/mcp/tools/search-components-by-category.ts index a573a4a888..4ce816a286 100644 --- a/docs/server/mcp/tools/search-components-by-category.ts +++ b/docs/server/mcp/tools/search-components-by-category.ts @@ -1,4 +1,5 @@ import { z } from 'zod/v3' +import { queryCollection } from '@nuxt/content/server' export default defineMcpTool({ description: 'Searches components by category or text filter', @@ -6,10 +7,45 @@ export default defineMcpTool({ category: z.string().optional().describe('Filter components by category'), search: z.string().optional().describe('Search term to filter components by name or description') }, - handler: async ({ category, search }) => { - const result = await $fetch('/api/mcp/search-components-by-category', { query: { category, search } }) - return { - content: [{ type: 'text' as const, text: JSON.stringify(result, null, 2) }] + cache: '30m', + async handler({ category, search }) { + const event = useEvent() + + let query = queryCollection(event, 'docs') + .where('path', 'LIKE', '/docs/components/%') + .where('extension', '=', 'md') + .select('id', 'title', 'description', 'path', 'category', 'links') + + if (category) { + query = query.where('category', '=', category) } + + const components = await query.all() + + let results = components.map(component => ({ + name: component.path.split('/').pop(), + title: component.title, + description: component.description, + category: component.category, + path: component.path, + url: `https://ui.nuxt.com${component.path}`, + links: component.links + })) + + // Apply search filter if provided + if (search) { + const searchLower = search.toLowerCase() + results = results.filter(component => + component.name?.toLowerCase().includes(searchLower) + || component.title?.toLowerCase().includes(searchLower) + || component.description?.toLowerCase().includes(searchLower) + ) + } + + return jsonResult({ + components: results.sort((a, b) => (a.name || '').localeCompare(b.name || '')), + total: results.length, + filters: { category, search } + }) } }) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3a4864732b..a06b44d151 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -275,8 +275,8 @@ importers: specifier: workspace:* version: link:.. '@nuxtjs/mcp-toolkit': - specifier: ^0.2.0 - version: 0.2.0(magicast@0.5.1)(zod@4.1.13) + specifier: ^0.4.1 + version: 0.4.1(magicast@0.5.1)(zod@4.1.13) '@nuxtjs/plausible': specifier: ^2.0.1 version: 2.0.1(magicast@0.5.1) @@ -1475,8 +1475,8 @@ packages: '@nuxtjs/color-mode@3.5.2': resolution: {integrity: sha512-cC6RfgZh3guHBMLLjrBB2Uti5eUoGM9KyauOaYS9ETmxNWBMTvpgjvSiSJp1OFljIXPIqVTJ3xtJpSNZiO3ZaA==} - '@nuxtjs/mcp-toolkit@0.2.0': - resolution: {integrity: sha512-7BBS6IiibSkzCdW5QgLLpETuiyGdzhWXKCJHvRwSMpu4aNYm0dghSt7z5Ahao3xWPpHlmWHajBzQYbfiZZCiZQ==} + '@nuxtjs/mcp-toolkit@0.4.1': + resolution: {integrity: sha512-vT4B1ujtoIMkkkJ0WufM5ccZ1A+dfVOqNDrJT4ZT5KExHiogWjq5C1nrm58PI0+Fwo004f5tWxBE2B8Mo17xsA==} peerDependencies: zod: ^4.1.13 @@ -9204,7 +9204,7 @@ snapshots: transitivePeerDependencies: - magicast - '@nuxtjs/mcp-toolkit@0.2.0(magicast@0.5.1)(zod@4.1.13)': + '@nuxtjs/mcp-toolkit@0.4.1(magicast@0.5.1)(zod@4.1.13)': dependencies: '@clack/prompts': 0.11.0 '@modelcontextprotocol/sdk': 1.23.0(zod@4.1.13) @@ -9212,6 +9212,7 @@ snapshots: automd: 0.4.2(magicast@0.5.1) chokidar: 4.0.3 defu: 6.1.4 + ms: 2.1.3 pathe: 2.0.3 scule: 1.3.0 tinyglobby: 0.2.15 From 4a1f1ea83b0faa32825f61cd907050f96fa0f396 Mon Sep 17 00:00:00 2001 From: Hugo Richard Date: Fri, 28 Nov 2025 16:41:28 +0000 Subject: [PATCH 7/9] remove zod/v3 --- docs/server/mcp/prompts/find-component-for-usecase.ts | 2 +- docs/server/mcp/prompts/implement-component-with-props.ts | 2 +- docs/server/mcp/prompts/setup-project-with-template.ts | 2 +- docs/server/mcp/tools/get-component-metadata.ts | 2 +- docs/server/mcp/tools/get-component.ts | 2 +- docs/server/mcp/tools/get-documentation-page.ts | 2 +- docs/server/mcp/tools/get-example.ts | 2 +- docs/server/mcp/tools/get-migration-guide.ts | 2 +- docs/server/mcp/tools/get-template.ts | 2 +- docs/server/mcp/tools/list-templates.ts | 2 +- docs/server/mcp/tools/search-components-by-category.ts | 2 +- 11 files changed, 11 insertions(+), 11 deletions(-) diff --git a/docs/server/mcp/prompts/find-component-for-usecase.ts b/docs/server/mcp/prompts/find-component-for-usecase.ts index d3a560431d..da2d911c5b 100644 --- a/docs/server/mcp/prompts/find-component-for-usecase.ts +++ b/docs/server/mcp/prompts/find-component-for-usecase.ts @@ -1,4 +1,4 @@ -import { z } from 'zod/v3' +import { z } from 'zod' import { queryCollection } from '@nuxt/content/server' export default defineMcpPrompt({ diff --git a/docs/server/mcp/prompts/implement-component-with-props.ts b/docs/server/mcp/prompts/implement-component-with-props.ts index 272b83ee40..96855211b4 100644 --- a/docs/server/mcp/prompts/implement-component-with-props.ts +++ b/docs/server/mcp/prompts/implement-component-with-props.ts @@ -1,4 +1,4 @@ -import { z } from 'zod/v3' +import { z } from 'zod' import { kebabCase, camelCase, upperFirst } from 'scule' import { queryCollection } from '@nuxt/content/server' import { normalizeComponentName } from '~~/server/utils/normalizeComponentName' diff --git a/docs/server/mcp/prompts/setup-project-with-template.ts b/docs/server/mcp/prompts/setup-project-with-template.ts index 1799219dcd..07cd1410ff 100644 --- a/docs/server/mcp/prompts/setup-project-with-template.ts +++ b/docs/server/mcp/prompts/setup-project-with-template.ts @@ -1,4 +1,4 @@ -import { z } from 'zod/v3' +import { z } from 'zod' import { queryCollection } from '@nuxt/content/server' export default defineMcpPrompt({ diff --git a/docs/server/mcp/tools/get-component-metadata.ts b/docs/server/mcp/tools/get-component-metadata.ts index d9c3dafffb..61a317bb3c 100644 --- a/docs/server/mcp/tools/get-component-metadata.ts +++ b/docs/server/mcp/tools/get-component-metadata.ts @@ -1,4 +1,4 @@ -import { z } from 'zod/v3' +import { z } from 'zod' import { camelCase, upperFirst, kebabCase } from 'scule' import { queryCollection } from '@nuxt/content/server' import { normalizeComponentName } from '~~/server/utils/normalizeComponentName' diff --git a/docs/server/mcp/tools/get-component.ts b/docs/server/mcp/tools/get-component.ts index 31640ef415..5841652771 100644 --- a/docs/server/mcp/tools/get-component.ts +++ b/docs/server/mcp/tools/get-component.ts @@ -1,4 +1,4 @@ -import { z } from 'zod/v3' +import { z } from 'zod' import { kebabCase } from 'scule' import { queryCollection } from '@nuxt/content/server' import { normalizeComponentName } from '~~/server/utils/normalizeComponentName' diff --git a/docs/server/mcp/tools/get-documentation-page.ts b/docs/server/mcp/tools/get-documentation-page.ts index 1d1c4a306c..b23d9b6524 100644 --- a/docs/server/mcp/tools/get-documentation-page.ts +++ b/docs/server/mcp/tools/get-documentation-page.ts @@ -1,4 +1,4 @@ -import { z } from 'zod/v3' +import { z } from 'zod' export default defineMcpTool({ description: 'Retrieves documentation page content by URL path', diff --git a/docs/server/mcp/tools/get-example.ts b/docs/server/mcp/tools/get-example.ts index c1659bd34f..7949cf3cce 100644 --- a/docs/server/mcp/tools/get-example.ts +++ b/docs/server/mcp/tools/get-example.ts @@ -1,4 +1,4 @@ -import { z } from 'zod/v3' +import { z } from 'zod' export default defineMcpTool({ description: 'Retrieves specific UI example implementation code and details', diff --git a/docs/server/mcp/tools/get-migration-guide.ts b/docs/server/mcp/tools/get-migration-guide.ts index e550f7e976..1e4bd5d43a 100644 --- a/docs/server/mcp/tools/get-migration-guide.ts +++ b/docs/server/mcp/tools/get-migration-guide.ts @@ -1,4 +1,4 @@ -import { z } from 'zod/v3' +import { z } from 'zod' import { queryCollection } from '@nuxt/content/server' export default defineMcpTool({ diff --git a/docs/server/mcp/tools/get-template.ts b/docs/server/mcp/tools/get-template.ts index 9ff8650cec..0e271bb4fe 100644 --- a/docs/server/mcp/tools/get-template.ts +++ b/docs/server/mcp/tools/get-template.ts @@ -1,4 +1,4 @@ -import { z } from 'zod/v3' +import { z } from 'zod' import { queryCollection } from '@nuxt/content/server' export default defineMcpTool({ diff --git a/docs/server/mcp/tools/list-templates.ts b/docs/server/mcp/tools/list-templates.ts index 2cb11d36d2..ecdba28b99 100644 --- a/docs/server/mcp/tools/list-templates.ts +++ b/docs/server/mcp/tools/list-templates.ts @@ -1,4 +1,4 @@ -import { z } from 'zod/v3' +import { z } from 'zod' import { queryCollection } from '@nuxt/content/server' export default defineMcpTool({ diff --git a/docs/server/mcp/tools/search-components-by-category.ts b/docs/server/mcp/tools/search-components-by-category.ts index 4ce816a286..6fb0fb5040 100644 --- a/docs/server/mcp/tools/search-components-by-category.ts +++ b/docs/server/mcp/tools/search-components-by-category.ts @@ -1,4 +1,4 @@ -import { z } from 'zod/v3' +import { z } from 'zod' import { queryCollection } from '@nuxt/content/server' export default defineMcpTool({ From 7b9404a7083cd76c64ec6d099963ac3ff8d5e389 Mon Sep 17 00:00:00 2001 From: Hugo Date: Fri, 28 Nov 2025 17:58:28 +0100 Subject: [PATCH 8/9] Update docs/server/mcp/tools/get-example.ts Co-authored-by: vercel[bot] <35613825+vercel[bot]@users.noreply.github.com> --- docs/server/mcp/tools/get-example.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/docs/server/mcp/tools/get-example.ts b/docs/server/mcp/tools/get-example.ts index 7949cf3cce..37eb0fc059 100644 --- a/docs/server/mcp/tools/get-example.ts +++ b/docs/server/mcp/tools/get-example.ts @@ -7,9 +7,13 @@ export default defineMcpTool({ }, cache: '30m', async handler({ exampleName }) { - const result = await $fetch<{ code: string }>(`/api/component-example/${exampleName}.json`) - return { - content: [{ type: 'text' as const, text: result.code }] + try { + const result = await $fetch<{ code: string }>(`/api/component-example/${exampleName}.json`) + return { + content: [{ type: 'text' as const, text: result.code }] + } + } catch { + return errorResult(`Example '${exampleName}' not found. Use the list_examples tool to see all available examples.`) } } }) From b24374272696c25bba54ef194e966c6c7fb10d4a Mon Sep 17 00:00:00 2001 From: Hugo Richard Date: Fri, 28 Nov 2025 17:01:02 +0000 Subject: [PATCH 9/9] fix from cr --- docs/server/mcp/tools/get-documentation-page.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/docs/server/mcp/tools/get-documentation-page.ts b/docs/server/mcp/tools/get-documentation-page.ts index b23d9b6524..feda46ba3d 100644 --- a/docs/server/mcp/tools/get-documentation-page.ts +++ b/docs/server/mcp/tools/get-documentation-page.ts @@ -7,9 +7,13 @@ export default defineMcpTool({ }, cache: '30m', async handler({ path }) { - const result = await $fetch(`/raw${path}.md`) - return { - content: [{ type: 'text' as const, text: result }] + try { + const result = await $fetch(`/raw${path}.md`) + return { + content: [{ type: 'text' as const, text: result }] + } + } catch (error) { + return errorResult(`Failed to fetch documentation page: ${error}`) } } })