@movk/nuxt-docs 1.8.1 → 1.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -20,6 +20,10 @@
20
20
 
21
21
  ### 🤖 AI 增强体验
22
22
 
23
+ <div style="padding: 40px 0; display: flex; justify-content: center;">
24
+ <img src="https://docs.mhaibaraai.cn/ai/AiChat.png" alt="AiChat" width="400">
25
+ </div>
26
+
23
27
  - **AI 聊天助手** - 内置智能文档助手,基于 Vercel AI SDK 支持多种 LLM 模型(Mistral、Qwen、OpenRouter)
24
28
  - **MCP Server 支持** - 集成 Model Context Protocol 服务器,为 AI 助手提供结构化的文档访问能力
25
29
  - **LLM 优化** - 通过 `nuxt-llms` 模块自动生成 `llms.txt` 和 `llms-full.txt`,为 AI 工具提供优化的文档索引
@@ -0,0 +1,40 @@
1
+ @import "tailwindcss" theme(static) source("../../../..");
2
+ @import "@nuxt/ui";
3
+
4
+ @theme static {
5
+ --container-8xl: 90rem;
6
+
7
+ --font-sans: 'Public Sans', sans-serif;
8
+
9
+ --color-green-50: #EFFDF5;
10
+ --color-green-100: #D9FBE8;
11
+ --color-green-200: #B3F5D1;
12
+ --color-green-300: #75EDAE;
13
+ --color-green-400: #00DC82;
14
+ --color-green-500: #00C16A;
15
+ --color-green-600: #00A155;
16
+ --color-green-700: #007F45;
17
+ --color-green-800: #016538;
18
+ --color-green-900: #0A5331;
19
+ --color-green-950: #052E16;
20
+ }
21
+
22
+ :root {
23
+ --ui-container: var(--container-8xl);
24
+ }
25
+
26
+ /* Shiki icon highlight transformer styles */
27
+ .shiki-icon-highlight {
28
+ display: inline-block;
29
+ width: 1.25em;
30
+ height: 1.25em;
31
+ vertical-align: -0.25em;
32
+ margin-right: 0.125em;
33
+ background-color: var(--ui-text-highlighted);
34
+ -webkit-mask-repeat: no-repeat;
35
+ mask-repeat: no-repeat;
36
+ -webkit-mask-size: 100% 100%;
37
+ mask-size: 100% 100%;
38
+ -webkit-mask-image: var(--shiki-icon-url);
39
+ mask-image: var(--shiki-icon-url);
40
+ }
@@ -1,7 +1,9 @@
1
1
  export function useToolCall() {
2
2
  const tools: Record<string, string | ((args: any) => string)> = {
3
3
  'list-pages': '列出所有文档页面',
4
- 'get-page': (args: any) => `检索 ${args?.path || '页面'}`
4
+ 'get-page': (args: any) => `检索 ${args?.path || '页面'}`,
5
+ 'list-examples': '列出所有示例',
6
+ 'get-example': (args: any) => `获取示例:${args?.exampleName || '示例'}`
5
7
  }
6
8
  return {
7
9
  tools
@@ -14,7 +14,7 @@ const { toc, github } = useAppConfig()
14
14
  const { data: page } = await useAsyncData(`docs-${kebabCase(route.path)}`, () => queryCollection('docs').path(route.path).first())
15
15
 
16
16
  if (!page.value) {
17
- throw createError({ statusCode: 404, statusMessage: 'Page not found', fatal: true })
17
+ throw createError({ status: 404, statusText: 'Page not found', fatal: true })
18
18
  }
19
19
 
20
20
  const navigation = inject<Ref<ContentNavigationItem[]>>('navigation')
@@ -1,7 +1,7 @@
1
1
  <script setup lang="ts">
2
2
  const { data: page } = await useAsyncData('landing', () => queryCollection('landing').path('/').first())
3
3
  if (!page.value) {
4
- throw createError({ statusCode: 404, statusMessage: 'Page not found', fatal: true })
4
+ throw createError({ status: 404, statusText: 'Page not found', fatal: true })
5
5
  }
6
6
 
7
7
  const title = page.value.seo?.title || page.value.title
@@ -3,7 +3,7 @@ import type { ButtonProps } from '@nuxt/ui'
3
3
 
4
4
  const { data: page } = await useAsyncData('releases', () => queryCollection('releases').first())
5
5
  if (!page.value) {
6
- throw createError({ statusCode: 404, statusMessage: 'Page not found', fatal: true })
6
+ throw createError({ status: 404, statusText: 'Page not found', fatal: true })
7
7
  }
8
8
 
9
9
  const title = page.value.seo?.title || page.value.title
@@ -1,4 +1,3 @@
1
- /** copy from https://github.com/nuxt/ui/blob/v4/docs/app/utils/shiki-transformer-icon-highlight.ts */
2
1
  import type { ShikiTransformer } from 'shiki'
3
2
 
4
3
  export interface TransformerIconHighlightOptions {
package/content.config.ts CHANGED
@@ -1,9 +1,8 @@
1
1
  import { existsSync } from 'node:fs'
2
2
  import { defineCollection, defineContentConfig } from '@nuxt/content'
3
3
  import { useNuxt } from '@nuxt/kit'
4
- import { asSeoCollection } from '@nuxtjs/seo/content'
5
4
  import { joinURL } from 'ufo'
6
- import { z } from 'zod/v4'
5
+ import { z } from 'zod'
7
6
 
8
7
  const { options } = useNuxt()
9
8
  const cwd = joinURL(options.rootDir, 'content')
@@ -38,14 +37,14 @@ const PageHero = z.object({
38
37
 
39
38
  export default defineContentConfig({
40
39
  collections: {
41
- landing: defineCollection(asSeoCollection({
40
+ landing: defineCollection({
42
41
  type: 'page',
43
42
  source: {
44
43
  cwd,
45
44
  include: 'index.md'
46
45
  }
47
- })),
48
- docs: defineCollection(asSeoCollection({
46
+ }),
47
+ docs: defineCollection({
49
48
  type: 'page',
50
49
  source: {
51
50
  cwd,
@@ -58,8 +57,8 @@ export default defineContentConfig({
58
57
  title: z.string().optional()
59
58
  })
60
59
  })
61
- })),
62
- releases: defineCollection(asSeoCollection({
60
+ }),
61
+ releases: defineCollection({
63
62
  type: 'page',
64
63
  source: {
65
64
  cwd,
@@ -71,6 +70,6 @@ export default defineContentConfig({
71
70
  releases: z.string(),
72
71
  hero: PageHero
73
72
  })
74
- }))
73
+ })
75
74
  }
76
75
  })
@@ -1,5 +1,5 @@
1
1
  import { tool, stepCountIs, generateText } from 'ai'
2
- import { z } from 'zod/v4'
2
+ import { z } from 'zod'
3
3
 
4
4
  function getSubAgentSystemPrompt(siteName: string) {
5
5
  return `您是 ${siteName} 的文档搜索代理。您的工作是从文档中查找并检索相关信息。
package/modules/config.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  import { createResolver, defineNuxtModule } from '@nuxt/kit'
2
- import { join } from 'pathe'
3
2
  import { defu } from 'defu'
4
3
  import { getGitBranch, getGitEnv, getLocalGitInfo } from '../utils/git'
5
4
  import { getPackageJsonMetadata, inferSiteURL } from '../utils/meta'
5
+ import { createComponentMetaExcludeFilters } from '../utils/component-meta'
6
6
 
7
7
  export default defineNuxtModule({
8
8
  meta: {
@@ -17,28 +17,25 @@ export default defineNuxtModule({
17
17
  const url = inferSiteURL()
18
18
  const meta = await getPackageJsonMetadata(dir)
19
19
  const gitInfo = await getLocalGitInfo(dir) || getGitEnv()
20
- const siteName = nuxt.options?.site?.name || meta.name || gitInfo?.name || ''
20
+ const siteName = meta.name || gitInfo?.name || ''
21
+
22
+ nuxt.options.site = defu(nuxt.options.site, {
23
+ url,
24
+ name: siteName,
25
+ debug: false
26
+ })
21
27
 
22
28
  nuxt.options.llms = defu(nuxt.options.llms, {
23
29
  domain: url || 'https://example.com',
24
30
  title: siteName,
25
31
  description: meta.description || '',
32
+ contentRawMarkdown: false as const,
26
33
  full: {
27
34
  title: siteName,
28
35
  description: meta.description || ''
29
36
  }
30
37
  })
31
38
 
32
- nuxt.options.site = defu(nuxt.options.site, {
33
- url,
34
- name: siteName,
35
- debug: false
36
- })
37
-
38
- nuxt.options.robots = defu(nuxt.options.robots, {
39
- sitemap: url ? `${url.replace(/\/$/, '')}/sitemap.xml` : undefined
40
- })
41
-
42
39
  nuxt.options.appConfig.header = defu(nuxt.options.appConfig.header, {
43
40
  title: siteName
44
41
  })
@@ -62,34 +59,16 @@ export default defineNuxtModule({
62
59
  })
63
60
 
64
61
  const layerPath = resolve('..')
65
- const allowedComponents = [
66
- resolve('../app/components/content/CommitChangelog.vue'),
67
- resolve('../app/components/content/ComponentEmits.vue'),
68
- resolve('../app/components/content/ComponentExample.vue'),
69
- resolve('../app/components/content/ComponentProps.vue'),
70
- resolve('../app/components/content/ComponentSlots.vue'),
71
- resolve('../app/components/content/PageLastCommit.vue'),
72
- resolve('../app/components/content/Mermaid.vue'),
73
- resolve('./ai-chat/runtime/components/AiChatToolCall.vue'),
74
- resolve('./ai-chat/runtime/components/AiChatReasoning.vue'),
75
- resolve('./ai-chat/runtime/components/AiChatSlideoverFaq.vue'),
76
- resolve('./ai-chat/runtime/components/AiChatPreStream.vue')
77
- ]
78
- const userComponentPaths = [
79
- join(dir, 'app/components'),
80
- join(dir, 'components'),
81
- join(dir, 'docs/app/components'),
82
- join(dir, 'templates/*/app/components')
83
- ]
84
62
 
85
63
  // @ts-ignore - component-meta is not typed
86
64
  nuxt.hook('component-meta:extend', (options: any) => {
65
+ const userInclude = (nuxt.options.componentMeta && typeof nuxt.options.componentMeta === 'object')
66
+ ? nuxt.options.componentMeta.include || []
67
+ : []
68
+
87
69
  options.exclude = [
88
70
  ...(options.exclude || []),
89
- ({ filePath }: { filePath: string }) =>
90
- filePath.startsWith(layerPath) && !allowedComponents.includes(filePath),
91
- ({ filePath }: { filePath: string }) =>
92
- userComponentPaths.some(path => filePath.startsWith(path))
71
+ ...createComponentMetaExcludeFilters(resolve, dir, layerPath, userInclude)
93
72
  ]
94
73
  })
95
74
  }
package/modules/css.ts CHANGED
@@ -1,46 +1,33 @@
1
- import { addTemplate, createResolver, defineNuxtModule } from '@nuxt/kit'
1
+ import { addTemplate, defineNuxtModule } from '@nuxt/kit'
2
+ import { fileURLToPath } from 'node:url'
3
+ import { dirname, join } from 'node:path'
2
4
  import { joinURL } from 'ufo'
5
+ import { resolveModulePath } from 'exsolve'
3
6
 
4
7
  export default defineNuxtModule({
5
8
  meta: {
6
9
  name: 'css'
7
10
  },
8
- async setup(_options, nuxt) {
9
- const dir = nuxt.options.rootDir
10
- const { resolve } = createResolver(import.meta.url)
11
+ setup(_options, nuxt) {
12
+ const currentDir = dirname(fileURLToPath(import.meta.url))
13
+ const mainCssPath = join(currentDir, '../app/assets/css/main.css')
14
+
15
+ nuxt.options.css ||= []
16
+ nuxt.options.css.unshift(mainCssPath)
11
17
 
12
- const layerDir = resolve('../app')
13
- const aiChatDir = resolve('../modules/ai-chat')
18
+ const dir = nuxt.options.rootDir
14
19
 
15
20
  const contentDir = joinURL(dir, 'content')
21
+ const uiPath = resolveModulePath('@nuxt/ui', { from: import.meta.url, conditions: ['style'] })
22
+ const tailwindPath = resolveModulePath('tailwindcss', { from: import.meta.url, conditions: ['style'] })
16
23
 
17
24
  const cssTemplate = addTemplate({
18
25
  filename: 'movk-nuxt-docs.css',
19
26
  getContents: () => {
20
- return `@import "tailwindcss";
21
- @import "tailwindcss/theme.css" layer(theme) theme(static);
22
- @import "@nuxt/ui";
23
-
24
- @source "${contentDir.replace(/\\/g, '/')}/**/*";
25
- @source "${layerDir.replace(/\\/g, '/')}/**/*";
26
- @source "${aiChatDir.replace(/\\/g, '/')}/**/*";
27
- @source "../../app.config.ts";
27
+ return `@import ${JSON.stringify(tailwindPath)};
28
+ @import ${JSON.stringify(uiPath)};
28
29
 
29
- /* Shiki icon highlight transformer styles */
30
- .shiki-icon-highlight {
31
- display: inline-block;
32
- width: 1.25em;
33
- height: 1.25em;
34
- vertical-align: -0.25em;
35
- margin-right: 0.125em;
36
- background-color: var(--ui-text-highlighted);
37
- -webkit-mask-repeat: no-repeat;
38
- mask-repeat: no-repeat;
39
- -webkit-mask-size: 100% 100%;
40
- mask-size: 100% 100%;
41
- -webkit-mask-image: var(--shiki-icon-url);
42
- mask-image: var(--shiki-icon-url);
43
- }`
30
+ @source "${contentDir.replace(/\\/g, '/')}/**/*";`
44
31
  }
45
32
  })
46
33
 
@@ -1,4 +1,4 @@
1
- import { defineNuxtModule } from 'nuxt/kit'
1
+ import { defineNuxtModule } from '@nuxt/kit'
2
2
 
3
3
  export default defineNuxtModule((_options, nuxt) => {
4
4
  nuxt.hooks.hook('nitro:init', (nitro) => {
package/nuxt.config.ts CHANGED
@@ -1,45 +1,27 @@
1
- import { createResolver, extendViteConfig } from '@nuxt/kit'
2
1
  import { defineNuxtConfig } from 'nuxt/config'
3
2
  import pkg from './package.json'
4
3
 
5
- const { resolve } = createResolver(import.meta.url)
6
-
7
4
  export default defineNuxtConfig({
8
5
  modules: [
9
- resolve('./modules/config'),
10
6
  '@nuxt/ui',
11
7
  '@nuxt/content',
12
8
  '@nuxt/image',
13
9
  '@nuxt/a11y',
14
10
  '@nuxtjs/mcp-toolkit',
15
- '@nuxtjs/seo',
16
11
  '@vueuse/nuxt',
17
12
  'nuxt-component-meta',
18
13
  'nuxt-llms',
19
- 'motion-v/nuxt',
20
- () => {
21
- extendViteConfig((config) => {
22
- config.optimizeDeps ||= {}
23
- config.optimizeDeps.include ||= []
24
- config.optimizeDeps.include.push(
25
- '@nuxt/content > slugify',
26
- 'extend',
27
- '@ai-sdk/gateway > @vercel/oidc',
28
- 'mermaid',
29
- 'dompurify'
30
- )
31
- config.optimizeDeps.include = config.optimizeDeps.include
32
- .map(id => id.replace(/^@nuxt\/content > /, '@movk/nuxt-docs > @nuxt/content > '))
33
- })
34
- }
14
+ 'nuxt-og-image',
15
+ 'motion-v/nuxt'
35
16
  ],
17
+
36
18
  app: {
37
19
  rootAttrs: {
38
20
  'data-vaul-drawer-wrapper': '',
39
21
  'class': 'bg-default'
40
22
  }
41
23
  },
42
- // @ts-ignore - content 配置的类型定义在运行时才能正确解析
24
+
43
25
  content: {
44
26
  build: {
45
27
  markdown: {
@@ -56,22 +38,25 @@ export default defineNuxtConfig({
56
38
  }
57
39
  }
58
40
  },
41
+
59
42
  mdc: {
60
43
  highlight: {
61
44
  noApiRoute: false
62
45
  }
63
46
  },
47
+
64
48
  runtimeConfig: {
65
49
  public: {
66
50
  version: pkg.version
67
51
  }
68
52
  },
53
+
69
54
  routeRules: {
70
55
  '/llms.txt': { isr: true },
71
56
  '/llms-full.txt': { isr: true }
72
57
  },
58
+
73
59
  experimental: {
74
- typescriptPlugin: true,
75
60
  asyncContext: true,
76
61
  defaults: {
77
62
  nuxtLink: {
@@ -79,7 +64,9 @@ export default defineNuxtConfig({
79
64
  }
80
65
  }
81
66
  },
67
+
82
68
  compatibilityDate: 'latest',
69
+
83
70
  nitro: {
84
71
  prerender: {
85
72
  crawlLinks: true,
@@ -87,15 +74,36 @@ export default defineNuxtConfig({
87
74
  autoSubfolderIndex: false
88
75
  }
89
76
  },
77
+
78
+ hooks: {
79
+ // Rewrite optimizeDeps paths for layer architecture
80
+ 'vite:extendConfig': (config) => {
81
+ const include = config.optimizeDeps?.include
82
+ if (!include) return
83
+
84
+ const layerPkgs = /^(?:@nuxt\/content|@nuxtjs\/mdc|@nuxt\/a11y) > /
85
+ include.forEach((id, i) => {
86
+ if (layerPkgs.test(id)) include[i] = `@movk/nuxt-docs > ${id}`
87
+ })
88
+
89
+ // Layer dependencies that need pre-bundling
90
+ include.push(
91
+ '@movk/nuxt-docs > @nuxt/content > slugify',
92
+ '@movk/nuxt-docs > @ai-sdk/gateway > @vercel/oidc'
93
+ )
94
+ }
95
+ },
96
+
90
97
  a11y: {
91
98
  logIssues: false
92
99
  },
100
+
93
101
  componentMeta: {
94
102
  metaFields: {
95
103
  type: false,
96
104
  props: true,
97
- slots: 'no-schema' as const,
98
- events: 'no-schema' as const,
105
+ slots: 'no-schema',
106
+ events: 'no-schema',
99
107
  exposed: false
100
108
  },
101
109
  exclude: [
@@ -110,6 +118,7 @@ export default defineNuxtConfig({
110
118
  'nuxt-og-image'
111
119
  ]
112
120
  },
121
+
113
122
  fonts: {
114
123
  families: [
115
124
  { name: 'Public Sans', provider: 'google', global: true },
@@ -121,9 +130,11 @@ export default defineNuxtConfig({
121
130
  { name: 'Raleway', provider: 'google', global: true }
122
131
  ]
123
132
  },
133
+
124
134
  icon: {
125
135
  provider: 'iconify'
126
136
  },
137
+
127
138
  ogImage: {
128
139
  zeroRuntime: true,
129
140
  googleFontMirror: 'fonts.loli.net',
@@ -134,8 +145,5 @@ export default defineNuxtConfig({
134
145
  'Inter:400',
135
146
  'Inter:700'
136
147
  ]
137
- },
138
- sitemap: {
139
- zeroRuntime: true
140
148
  }
141
149
  })
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@movk/nuxt-docs",
3
3
  "type": "module",
4
- "version": "1.8.1",
4
+ "version": "1.10.0",
5
5
  "private": false,
6
6
  "description": "Modern Nuxt 4 documentation theme with auto-generated component docs, AI chat assistant, MCP server, and complete developer experience optimization.",
7
7
  "author": "YiXuan <mhaibaraai@gmail.com>",
@@ -14,7 +14,8 @@
14
14
  "main": "./nuxt.config.ts",
15
15
  "peerDependencies": {
16
16
  "better-sqlite3": "12.x",
17
- "nuxt": "4.x"
17
+ "nuxt": "4.x",
18
+ "tailwindcss": "4.x"
18
19
  },
19
20
  "files": [
20
21
  "app",
@@ -28,38 +29,40 @@
28
29
  "README.md"
29
30
  ],
30
31
  "dependencies": {
31
- "@ai-sdk/gateway": "^3.0.19",
32
- "@ai-sdk/mcp": "^1.0.11",
33
- "@ai-sdk/vue": "^3.0.45",
34
- "@iconify-json/lucide": "^1.2.86",
32
+ "@ai-sdk/gateway": "^3.0.29",
33
+ "@ai-sdk/mcp": "^1.0.15",
34
+ "@ai-sdk/vue": "^3.0.62",
35
+ "@iconify-json/lucide": "^1.2.87",
35
36
  "@iconify-json/ph": "^1.2.2",
36
- "@iconify-json/simple-icons": "^1.2.67",
37
+ "@iconify-json/simple-icons": "^1.2.68",
37
38
  "@iconify-json/tabler": "^1.2.26",
38
39
  "@iconify-json/vscode-icons": "^1.2.40",
39
40
  "@movk/core": "^1.1.0",
40
41
  "@nuxt/a11y": "^1.0.0-alpha.1",
41
42
  "@nuxt/content": "^3.11.0",
42
43
  "@nuxt/image": "^2.0.0",
43
- "@nuxt/kit": "^4.2.2",
44
+ "@nuxt/kit": "^4.3.0",
44
45
  "@nuxt/ui": "^4.4.0",
45
46
  "@nuxtjs/mcp-toolkit": "^0.6.2",
46
- "@nuxtjs/seo": "^3.3.0",
47
+ "@nuxtjs/mdc": "^0.20.0",
47
48
  "@octokit/rest": "^22.0.1",
48
- "@openrouter/ai-sdk-provider": "^2.0.0",
49
+ "@openrouter/ai-sdk-provider": "^2.1.1",
49
50
  "@vercel/analytics": "^1.6.1",
50
51
  "@vercel/speed-insights": "^1.3.1",
51
52
  "@vueuse/core": "^14.1.0",
52
53
  "@vueuse/nuxt": "^14.1.0",
53
- "ai": "^6.0.45",
54
+ "ai": "^6.0.62",
54
55
  "defu": "^6.1.4",
55
56
  "dompurify": "^3.3.1",
56
57
  "exsolve": "^1.0.8",
57
58
  "git-url-parse": "^16.1.0",
58
59
  "mermaid": "^11.12.2",
59
- "motion-v": "^1.9.0",
60
- "nuxt": "^4.2.2",
60
+ "minimark": "^0.2.0",
61
+ "motion-v": "^1.10.2",
62
+ "nuxt": "^4.3.0",
61
63
  "nuxt-component-meta": "^0.17.1",
62
64
  "nuxt-llms": "^0.2.0",
65
+ "nuxt-og-image": "^5.1.13",
63
66
  "ohash": "^2.0.11",
64
67
  "pathe": "^2.0.3",
65
68
  "pkg-types": "^2.3.0",
@@ -69,7 +72,9 @@
69
72
  "shiki-stream": "^0.1.4",
70
73
  "shiki-transformer-color-highlight": "^1.0.0",
71
74
  "tailwindcss": "^4.1.18",
75
+ "tailwind-merge": "^3.4.0",
72
76
  "ufo": "^1.6.3",
73
- "zod": "^4.3.5"
77
+ "zod": "^4.3.6",
78
+ "vue-component-meta": "^3.2.4"
74
79
  }
75
80
  }
@@ -10,8 +10,8 @@ export default defineEventHandler((event) => {
10
10
  const component = components[pascalCase(componentName)]
11
11
  if (!component) {
12
12
  throw createError({
13
- statusMessage: 'Example not found!',
14
- statusCode: 404
13
+ statusText: 'Example not found!',
14
+ status: 404
15
15
  })
16
16
  }
17
17
  return component
@@ -10,8 +10,8 @@ export default defineCachedEventHandler(async (event) => {
10
10
 
11
11
  if (!paths.length || !paths[0]) {
12
12
  throw createError({
13
- statusCode: 400,
14
- statusMessage: 'Path is required'
13
+ status: 400,
14
+ statusText: 'Path is required'
15
15
  })
16
16
  }
17
17
 
@@ -19,8 +19,8 @@ export default defineCachedEventHandler(async (event) => {
19
19
 
20
20
  if (!github || typeof github === 'boolean') {
21
21
  throw createError({
22
- statusCode: 500,
23
- statusMessage: 'GitHub configuration is not available'
22
+ status: 500,
23
+ statusText: 'GitHub configuration is not available'
24
24
  })
25
25
  }
26
26
 
@@ -8,8 +8,8 @@ export default defineCachedEventHandler(async (event) => {
8
8
  const { path } = getQuery(event) as { path: string }
9
9
  if (!path) {
10
10
  throw createError({
11
- statusCode: 400,
12
- statusMessage: 'Path is required'
11
+ status: 400,
12
+ statusText: 'Path is required'
13
13
  })
14
14
  }
15
15
 
@@ -17,8 +17,8 @@ export default defineCachedEventHandler(async (event) => {
17
17
 
18
18
  if (!github || typeof github === 'boolean') {
19
19
  throw createError({
20
- statusCode: 500,
21
- statusMessage: 'GitHub configuration is not available'
20
+ status: 500,
21
+ statusText: 'GitHub configuration is not available'
22
22
  })
23
23
  }
24
24
 
@@ -0,0 +1,19 @@
1
+ import { z } from 'zod'
2
+
3
+ export default defineMcpTool({
4
+ description: '检索特定的 UI 示例实现代码和详细信息',
5
+ inputSchema: {
6
+ exampleName: z.string().describe('示例名称(PascalCase)')
7
+ },
8
+ cache: '30m',
9
+ async handler({ exampleName }) {
10
+ try {
11
+ const result = await $fetch<{ code: string }>(`/api/component-example/${exampleName}.json`)
12
+ return {
13
+ content: [{ type: 'text' as const, text: result.code }]
14
+ }
15
+ } catch {
16
+ return errorResult(`示例 '${exampleName}' 未找到。使用 list_examples 工具查看所有可用示例。`)
17
+ }
18
+ }
19
+ })