diff --git a/docs/nuxt.config.ts b/docs/nuxt.config.ts index f31bd365ef..03592bf67c 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', @@ -156,6 +157,7 @@ export default defineNuxtConfig({ }, experimental: { + asyncContext: true, defaults: { nuxtLink: { externalRelAttribute: 'noopener' @@ -261,5 +263,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..a44646793e 100644 --- a/docs/package.json +++ b/docs/package.json @@ -16,10 +16,11 @@ "@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", + "@modelcontextprotocol/sdk": "^1.23.0", "@nuxt/content": "^3.8.2", "@nuxt/image": "^2.0.0", "@nuxt/ui": "workspace:*", + "@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 new file mode 100644 index 0000000000..da2d911c5b --- /dev/null +++ b/docs/server/mcp/prompts/find-component-for-usecase.ts @@ -0,0 +1,30 @@ +import { z } from 'zod' +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")') + }, + 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' as const, + content: { + 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 new file mode 100644 index 0000000000..96855211b4 --- /dev/null +++ b/docs/server/mcp/prompts/implement-component-with-props.ts @@ -0,0 +1,82 @@ +import { z } from 'zod' +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', + inputSchema: { + componentName: z.string().describe('The Nuxt UI component name (PascalCase)'), + requirements: z.string().optional().describe('Specific requirements or customizations needed') + }, + 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' as const, + content: { + 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 new file mode 100644 index 0000000000..07cd1410ff --- /dev/null +++ b/docs/server/mcp/prompts/setup-project-with-template.ts @@ -0,0 +1,28 @@ +import { z } from 'zod' +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.)') + }, + async handler({ projectType }) { + const event = useEvent() + + const templatesCollectionItems = await queryCollection(event, 'templates').first() + + const templates = templatesCollectionItems?.items || [] + + return { + messages: [ + { + role: 'user' as const, + content: { + 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 new file mode 100644 index 0000000000..520f3a9692 --- /dev/null +++ b/docs/server/mcp/resources/components.ts @@ -0,0 +1,24 @@ +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', + 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(components, null, 2) + }] + } + } +}) diff --git a/docs/server/mcp/resources/composables.ts b/docs/server/mcp/resources/composables.ts new file mode 100644 index 0000000000..466e0575f6 --- /dev/null +++ b/docs/server/mcp/resources/composables.ts @@ -0,0 +1,24 @@ +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', + 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(composables, 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..4d1e9177bf --- /dev/null +++ b/docs/server/mcp/resources/documentation-pages.ts @@ -0,0 +1,26 @@ +import { queryCollection } from '@nuxt/content/server' + +export default defineMcpResource({ + uri: 'resource://nuxt-ui/documentation-pages', + description: 'Complete list of available Nuxt UI 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(), + 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..bcc914a90c --- /dev/null +++ b/docs/server/mcp/resources/examples.ts @@ -0,0 +1,21 @@ +// @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', + 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(examples, null, 2) + }] + } + } +}) diff --git a/docs/server/mcp/resources/templates.ts b/docs/server/mcp/resources/templates.ts new file mode 100644 index 0000000000..83f14a5f13 --- /dev/null +++ b/docs/server/mcp/resources/templates.ts @@ -0,0 +1,25 @@ +import { queryCollection } from '@nuxt/content/server' + +export default defineMcpResource({ + uri: 'resource://nuxt-ui/templates', + description: 'Complete list of available Nuxt UI templates with categories', + 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({ + 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 new file mode 100644 index 0000000000..61a317bb3c --- /dev/null +++ b/docs/server/mcp/tools/get-component-metadata.ts @@ -0,0 +1,53 @@ +import { z } from 'zod' +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)') + }, + 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 new file mode 100644 index 0000000000..5841652771 --- /dev/null +++ b/docs/server/mcp/tools/get-component.ts @@ -0,0 +1,43 @@ +import { z } from 'zod' +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)') + }, + 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 new file mode 100644 index 0000000000..feda46ba3d --- /dev/null +++ b/docs/server/mcp/tools/get-documentation-page.ts @@ -0,0 +1,19 @@ +import { z } from 'zod' + +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)') + }, + cache: '30m', + async handler({ path }) { + 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}`) + } + } +}) diff --git a/docs/server/mcp/tools/get-example.ts b/docs/server/mcp/tools/get-example.ts new file mode 100644 index 0000000000..37eb0fc059 --- /dev/null +++ b/docs/server/mcp/tools/get-example.ts @@ -0,0 +1,19 @@ +import { z } from 'zod' + +export default defineMcpTool({ + description: 'Retrieves specific UI example implementation code and details', + inputSchema: { + exampleName: z.string().describe('The name of the example (PascalCase)') + }, + cache: '30m', + async handler({ exampleName }) { + 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.`) + } + } +}) 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..1e4bd5d43a --- /dev/null +++ b/docs/server/mcp/tools/get-migration-guide.ts @@ -0,0 +1,34 @@ +import { z } from 'zod' +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)') + }, + 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 new file mode 100644 index 0000000000..0e271bb4fe --- /dev/null +++ b/docs/server/mcp/tools/get-template.ts @@ -0,0 +1,23 @@ +import { z } from 'zod' +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') + }, + 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 new file mode 100644 index 0000000000..68d6a3e469 --- /dev/null +++ b/docs/server/mcp/tools/list-components.ts @@ -0,0 +1,16 @@ +import { queryCollection } from '@nuxt/content/server' + +export default defineMcpTool({ + description: 'Lists all available Nuxt UI components with their categories and basic information', + 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 new file mode 100644 index 0000000000..47b9d567a8 --- /dev/null +++ b/docs/server/mcp/tools/list-composables.ts @@ -0,0 +1,16 @@ +import { queryCollection } from '@nuxt/content/server' + +export default defineMcpTool({ + description: 'Lists all available Nuxt UI composables with their categories and basic information', + 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 new file mode 100644 index 0000000000..3b120062c4 --- /dev/null +++ b/docs/server/mcp/tools/list-documentation-pages.ts @@ -0,0 +1,17 @@ +import { queryCollection } from '@nuxt/content/server' + +export default defineMcpTool({ + description: 'Lists all documentation pages', + 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 new file mode 100644 index 0000000000..e67b4dac65 --- /dev/null +++ b/docs/server/mcp/tools/list-examples.ts @@ -0,0 +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', + 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 new file mode 100644 index 0000000000..4d6f0c4f08 --- /dev/null +++ b/docs/server/mcp/tools/list-getting-started-guides.ts @@ -0,0 +1,25 @@ +import { queryCollection } from '@nuxt/content/server' + +export default defineMcpTool({ + description: 'Lists all getting started guides and installation instructions', + 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 new file mode 100644 index 0000000000..ecdba28b99 --- /dev/null +++ b/docs/server/mcp/tools/list-templates.ts @@ -0,0 +1,26 @@ +import { z } from 'zod' +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') + }, + 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 new file mode 100644 index 0000000000..6fb0fb5040 --- /dev/null +++ b/docs/server/mcp/tools/search-components-by-category.ts @@ -0,0 +1,51 @@ +import { z } from 'zod' +import { queryCollection } from '@nuxt/content/server' + +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') + }, + 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/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..a06b44d151 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -263,8 +263,8 @@ importers: specifier: ^1.2.36 version: 1.2.36 '@modelcontextprotocol/sdk': - specifier: ^1.22.0 - version: 1.22.0 + 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)) @@ -274,6 +274,9 @@ importers: '@nuxt/ui': specifier: workspace:* version: link:.. + '@nuxtjs/mcp-toolkit': + 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) @@ -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'} @@ -1281,11 +1288,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 @@ -1467,6 +1475,11 @@ packages: '@nuxtjs/color-mode@3.5.2': resolution: {integrity: sha512-cC6RfgZh3guHBMLLjrBB2Uti5eUoGM9KyauOaYS9ETmxNWBMTvpgjvSiSJp1OFljIXPIqVTJ3xtJpSNZiO3ZaA==} + '@nuxtjs/mcp-toolkit@0.4.1': + resolution: {integrity: sha512-vT4B1ujtoIMkkkJ0WufM5ccZ1A+dfVOqNDrJT4ZT5KExHiogWjq5C1nrm58PI0+Fwo004f5tWxBE2B8Mo17xsA==} + peerDependencies: + zod: ^4.1.13 + '@nuxtjs/mdc@0.18.3': resolution: {integrity: sha512-Fl64a9OZBH3J7ZpqzSWkrS64oFmLLvledZMcnYH3UzVtgFPo/GaICdJN3Ml83NSs/J9At6HHaP0k3+nrxu2qJw==} @@ -3093,6 +3106,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 +3774,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 +4211,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 +5027,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 +5113,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 +5152,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==} @@ -7430,6 +7464,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==} @@ -7634,6 +7673,8 @@ snapshots: transitivePeerDependencies: - supports-color + '@babel/runtime@7.28.4': {} + '@babel/template@7.27.2': dependencies: '@babel/code-frame': 7.27.1 @@ -8302,7 +8343,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) @@ -8315,8 +8356,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 @@ -9163,6 +9204,24 @@ snapshots: transitivePeerDependencies: - magicast + '@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) + '@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 + ms: 2.1.3 + 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 +10773,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 +11462,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 +11989,8 @@ snapshots: fast-uri@3.1.0: {} + fastest-levenshtein@1.0.16: {} + fastq@1.19.1: dependencies: reusify: 1.1.0 @@ -12832,6 +12921,8 @@ snapshots: lodash.capitalize@4.2.1: {} + lodash.deburr@4.1.0: {} + lodash.defaults@4.2.0: {} lodash.escaperegexp@4.1.2: {} @@ -12910,6 +13001,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 +13117,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: {} @@ -16096,6 +16193,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: {}