|
1 | 1 | import { streamText, convertToModelMessages, smoothStream, jsonSchema, stepCountIs } from 'ai' |
2 | 2 | import type { AnthropicLanguageModelOptions } from '@ai-sdk/anthropic' |
3 | | -import { createMCPClient } from '@ai-sdk/mcp' |
4 | 3 | import { gateway } from '@ai-sdk/gateway' |
| 4 | +import { z } from 'zod' |
| 5 | +// @ts-expect-error virtual module generated by @nuxtjs/mcp-toolkit |
| 6 | +import { tools as mcpToolDefinitions } from '#nuxt-mcp-toolkit/tools.mjs' |
5 | 7 | import { themeIcons, cssVariableDefaults } from '../../app/utils/theme' |
6 | 8 |
|
| 9 | +function mcpToolsToAiTools() { |
| 10 | + const aiTools: Record<string, { description: string, inputSchema: ReturnType<typeof jsonSchema>, execute: (args: any) => Promise<any> }> = {} |
| 11 | + |
| 12 | + for (const def of mcpToolDefinitions as any[]) { |
| 13 | + const filename = def._meta?.filename as string | undefined |
| 14 | + const name = def.name || (filename |
| 15 | + ? filename.replace(/\.(ts|js|mts|mjs)$/, '').replace(/([a-z0-9])([A-Z])/g, '$1-$2').replace(/[_\s]+/g, '-').toLowerCase() |
| 16 | + : null) |
| 17 | + if (!name) continue |
| 18 | + |
| 19 | + const schema = def.inputSchema |
| 20 | + ? z.toJSONSchema(z.object(def.inputSchema)) as Record<string, unknown> |
| 21 | + : { type: 'object' as const, properties: {} } |
| 22 | + |
| 23 | + aiTools[name] = { |
| 24 | + description: def.description || '', |
| 25 | + inputSchema: jsonSchema(schema), |
| 26 | + execute: async (args: any) => { |
| 27 | + try { |
| 28 | + return await def.handler(args, {}) |
| 29 | + } catch (error: any) { |
| 30 | + return { error: error.statusCode ? `[${error.statusCode}] ${error.message}` : error.message || String(error) } |
| 31 | + } |
| 32 | + } |
| 33 | + } |
| 34 | + } |
| 35 | + |
| 36 | + return aiTools |
| 37 | +} |
| 38 | + |
7 | 39 | const applyTheme = { |
8 | 40 | description: 'Apply theme settings live on the docs site. Call this when users ask to change colors, radius, font, or other theme properties. Only include properties that changed.', |
9 | 41 | inputSchema: jsonSchema<Record<string, any>>({ |
@@ -298,30 +330,11 @@ export default defineEventHandler(async (event) => { |
298 | 330 | } |
299 | 331 | } |
300 | 332 |
|
301 | | - let httpClient |
302 | | - let mcpTools |
303 | | - try { |
304 | | - const mcpUrl = import.meta.dev |
305 | | - ? new URL('/mcp', getRequestURL(event).origin).href |
306 | | - : 'https://ui.nuxt.com/mcp' |
307 | | - httpClient = await createMCPClient({ |
308 | | - transport: { type: 'http', url: mcpUrl } |
309 | | - }) |
310 | | - mcpTools = await httpClient.tools() |
311 | | - } catch (error) { |
312 | | - console.error('MCP client error:', error) |
313 | | - |
314 | | - throw createError({ |
315 | | - statusCode: 503, |
316 | | - message: 'Unable to connect to the documentation service. Please try again later.' |
317 | | - }) |
318 | | - } |
| 333 | + const mcpTools = mcpToolsToAiTools() |
319 | 334 |
|
320 | 335 | const abortController = new AbortController() |
321 | 336 | event.node.req.on('close', () => abortController.abort()) |
322 | 337 |
|
323 | | - const closeMcp = () => event.waitUntil(httpClient?.close()) |
324 | | - |
325 | 338 | const system = `You are a helpful assistant for Nuxt UI, a UI library for Nuxt and Vue. Nuxt UI includes \`@nuxt/fonts\` and \`@nuxt/icon\` as built-in dependencies — never tell users to install them separately. Use your knowledge base tools to search for relevant information before answering questions. |
326 | 339 |
|
327 | 340 | The user is using **${framework === 'vue' ? 'Vue' : 'Nuxt'}**. Tailor your answers accordingly — ${framework === 'vue' ? 'use the Vite plugin setup, Vue Router, and vite.config.ts instead of Nuxt-specific features like modules or app.config.ts. IMPORTANT: The Vite plugin auto-imports components and Nuxt UI composables, but Vue core APIs and VueUse must be explicitly imported — always include these in code examples (e.g. `import { ref, computed } from \'vue\'`, `import { useColorMode } from \'@vueuse/core\'`).' : 'use Nuxt modules, auto-imports, app.config.ts, and other Nuxt-specific features. Nuxt auto-imports Vue APIs (ref, computed, etc.), composables, and components — do not include these imports in code examples.'} |
@@ -376,11 +389,8 @@ Guidelines: |
376 | 389 | resetTheme, |
377 | 390 | getComponentTheme |
378 | 391 | }, |
379 | | - onFinish: closeMcp, |
380 | | - onAbort: closeMcp, |
381 | 392 | onError: (error) => { |
382 | 393 | console.error('streamText error:', error) |
383 | | - closeMcp() |
384 | 394 | } |
385 | 395 | }).toUIMessageStreamResponse() |
386 | 396 | }) |
0 commit comments