关于 manage-mcp
面向 Nuxt 开发者的 MCP 管理技能,指导使用 `@nuxtjs/mcp-toolkit` 配置服务,并创建工具、资源和提示词。它还涵盖多处理器组织、认证、中间件、MCP Apps 和故障排查。
主要功能
- 01配置 Nuxt MCP 服务
- 02创建工具、资源和提示词
- 03按处理器组织多个端点
- 04使用中间件与条件可见性
- 05构建 MCP Apps 交互组件
- 06提供测试与故障排查指导
适用场景
- 01在 Nuxt 应用中添加 MCP 服务
- 02按权限拆分管理与公开端点
- 03为 MCP 客户端提供交互式 Vue 组件
- 04排查工具发现或 OAuth 问题
MCP 相关
此技能帮助在 Nuxt 中通过 `@nuxtjs/mcp-toolkit` 构建 MCP 服务,向 MCP 客户端暴露工具、资源和提示词,并组织端点及处理中间件。
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ nuxt-modules/ mcp-toolkit --skill manage-mcp安装后在 Claude Code 中输入/manage-mcp确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/apps.mdhandlers.mdmiddleware.mdprompts.mdresources.mdsessions.mdtesting.mdtools.mdtroubleshooting.md详细介绍
管理 MCP
使用 @nuxtjs/mcp-toolkit 在 Nuxt 中管理 Model Context Protocol(MCP)服务的完整技能。涵盖设置、创建工具/资源/提示词、使用多处理器文件夹约定进行组织、构建交互式 UI 小组件(MCP Apps)、接入会话与可观测性、审查和排错。
适用场景
- 设置:“在我的 Nuxt 应用中设置 MCP 服务”,“将 MCP 添加到 Nuxt”
- 创建:“添加一个计算 BMI 的工具”,“将 README 暴露为资源”,“创建代码审查提示词”
- 组织:“将我的 MCP 拆分为 admin + public 端点”,“按用户筛选工具”
- 自定义:“为我的 MCP 添加身份验证”,“将每次工具调用记录到 Axiom”,“向客户端传输进度”
- 构建应用:“添加可从 ChatGPT/Cursor 调用的交互式 Vue 小组件”
- 持久化状态:“在一个会话的多次工具调用中记住用户”
- 审查:“审查我的 MCP 实现”,“检查是否符合最佳实践”
- 排错:“我的工具未被发现”,“自动导入无法使用”,“OAuth 流程不断触发”
- 测试:“评估模型会为这些提示词选择哪些工具”
设置 MCP 服务
安装
::code-group
pnpm add @nuxtjs/mcp-toolkit zod
npm install @nuxtjs/mcp-toolkit zod
yarn add @nuxtjs/mcp-toolkit zod
bun add @nuxtjs/mcp-toolkit zod
::
也可以通过 Nuxt CLI 一次性完成:
npx nuxt add mcp
添加到 nuxt.config.ts:
export default defineNuxtConfig({
modules: ['@nuxtjs/mcp-toolkit'],
mcp: {
name: 'My MCP Server',
description: 'Read and update todos for the current user.',
},
nitro: {
experimental: { asyncContext: true }, // required for useEvent / useMcpServer / useMcpLogger
},
})
目录结构
server/mcp/
├── tools/ # Actions the AI can perform
│ └── admin/ # Subdirectory → group: 'admin'
├── resources/ # Data the AI can read
├── prompts/ # Reusable message templates
└── handlers/ # Optional: named handlers mounted at /mcp/<name>
└── admin/
├── index.ts # defineMcpHandler({ middleware: requireAdmin })
├── tools/ # auto-attached to /mcp/admin
└── resources/
app/mcp/ # Optional: MCP Apps (interactive Vue widgets)
└── palette.vue # → app `palette`, callable from ChatGPT / Cursor
验证
- 启动开发服务:
pnpm dev - 访问端点:
curl http://localhost:3000/mcp(响应 MCP JSON-RPC) - 打开 Nuxt DevTools(Shift+Alt+D)→ MCP Inspector 标签页 → 启动 Inspector(会在新的浏览器标签页中打开)。
创建工具
工具是 AI 助手可以调用的函数。会自动发现 server/mcp/tools/ 下任意 .ts/.js 文件中的工具。
基本工具
import { z } from 'zod'
export default defineMcpTool({
description: 'Echo a message back to the user',
inputSchema: {
message: z.string().describe('Text to echo'),
},
handler: async ({ message }) => {
return `Echo: ${message}` // string is auto-wrapped into a text content item
},
})
name 和 title 会根据文件名自动生成(echo.ts → name: 'echo',title: 'Echo')。显式设置即可覆盖其中任一值。
返回值
处理器可以返回以下任意一种值,工具包会对其进行规范化:
| 返回值 | 封装为 |
|---|---|
string / number / boolean | { content: [{ type: 'text', text: String(v) }] } |
| 普通对象 / 数组 | 序列化为 JSON,并放入文本内容项 |
imageResult(base64, mime) | { content: [{ type: 'image', data, mimeType }] } |
audioResult(base64, mime) | { content: [{ type: 'audio', data, mimeType }] } |
完整的 CallToolResult | 原样传递(用于 structuredContent、嵌入资源、多内容) |
| 抛出的错误 | 捕获后转换为 isError: true。h3 的 createError({ statusCode, message }) 会在响应中包含状态码。 |
import { z } from 'zod'
export default defineMcpTool({
description: 'Get a user by ID',
inputSchema: { id: z.string().describe('User ID') },
handler: async ({ id }) => {
const user = await getUser(id)
if (!user) throw createError({ statusCode: 404, message: 'User not found' })
return user // plain object — auto-stringified
},
})
如需类型化的结构化输出,可将 outputSchema 与返回 structuredContent 配合使用:
import { z } from 'zod'
export default defineMcpTool({
description: 'Calculate Body Mass Index',
inputSchema: {
height: z.number().describe('Height in meters'),
weight: z.number().describe('Weight in kilograms'),
},
outputSchema: {
bmi: z.number(),
category: z.string(),
},
handler: async ({ height, weight }) => {
const bmi = weight / (height * height)
const category = bmi < 18.5 ? 'underweight' : bmi < 25 ? 'normal' : bmi < 30 ? 'overweight' : 'obese'
return { structuredContent: { bmi, category } }
},
})
注解与输入示例
行为提示可帮助客户端决定是否需要请求确认:
export default defineMcpTool({
description: 'Delete a user account',
annotations: {
readOnlyHint: false,
destructiveHint: true,
idempotentHint: true,
openWorldHint: false,
},
inputSchema: { id: z.string() },
inputExamples: [{ id: 'usr_42' }, { id: 'admin' }],
handler: async ({ id }) => {
await deleteUser(id)
return `Deleted ${id}.`
},
})
常见模式:只读 → readOnlyHint: true;创建 → idempotentHint: false;更新 → idempotentHint: true;删除 → destructiveHint: true, idempotentHint: true。
分组、标签与文件夹推断
group(单个)和 tags(自由格式)可帮助组织工具,并为 listMcpTools({ group, tags }) 筛选器提供依据。group 会根据父文件夹自动推断——server/mcp/tools/admin/delete-user.ts → group: 'admin'。显式设置的 group 优先。
export default defineMcpTool({
group: 'admin',
tags: ['destructive', 'user-management'],
description: 'Delete a user account',
// ...
})
缓存
export default defineMcpTool({
cache: '5m', // also accepts `Number` of ms or full Nitro cache options
description: 'Fetch weather (cached)',
inputSchema: { city: z.string() },
handler: async ({ city }) => $fetch(`/api/weather?city=${city}`),
})
条件可见性(enabled)
按请求隐藏工具——此操作在中间件之后运行,因此 event.context 已完成填充:
export default defineMcpTool({
enabled: event => Boolean(event.context.user),
description: 'List the current user’s todos',
handler: async () => listTodos(useEvent().context.user.id),
})
参见工具示例 →。
创建资源
资源通过 URI 提供只读数据。会自动发现 server/mcp/resources/ 下的资源。
文件简写(无需处理器)
export default defineMcpResource({
description: 'Project README file',
file: 'README.md', // URI, MIME type, and handler auto-generated
})
标准资源(自定义处理器)
export default defineMcpResource({
description: 'Application config',
uri: 'config:///app',
metadata: { mimeType: 'application/json' },
handler: async (uri) => ({
contents: [{
uri: uri.toString(),
mimeType: 'application/json',
text: JSON.stringify({ env: process.env.NODE_ENV }, null, 2),
}],
}),
})
模板资源(URI 变量)
将 SDK 中的 ResourceTemplate 作为 uri 传入:
import { ResourceTemplate } from '@modelcontextprotocol/sdk/server/mcp.js'
export default defineMcpResource({
description: 'Fetch a user by ID',
uri: new ResourceTemplate('user:///{id}', { list: undefined }),
handler: async (uri, { id }) => {
const user = await getUser(String(id))
return {
contents: [{
uri: uri.toString(),
mimeType: 'application/json',
text: JSON.stringify(user, null, 2),
}],
}
},
})
元数据与注释
使用顶层 metadata 字段指定 MIME 类型,并使用 annotations 字段指定受众、优先级和最后修改时间:
export default defineMcpResource({
description: 'Project README',
file: 'README.md',
metadata: {
mimeType: 'text/markdown',
annotations: {
audience: ['user', 'assistant'],
priority: 0.8,
lastModified: new Date().toISOString(),
},
},
})
参见资源示例 →。
创建提示词
提示词是可重复使用的消息模板。自动从 server/mcp/prompts/ 发现。
简单字符串提示词
export default defineMcpPrompt({
description: 'Code-review assistant',
handler: async () => 'You are a senior reviewer. Be concise and actionable.',
})
字符串会包装成一条用户消息。设置 role: 'assistant' 可将其包装成助手消息。
带参数的提示词
import { z } from 'zod'
export default defineMcpPrompt({
description: 'Generate a focused code review',
inputSchema: {
language: z.string().describe('Programming language'),
focus: z.enum(['performance', 'security', 'maintainability']).describe('Review focus'),
},
handler: async ({ language, focus }) =>
`Review my ${language} code. Focus on ${focus}.`,
})
使用 completable() 自动补全参数
在支持 prompts/complete 的客户端中提供动态建议:
import { z } from 'zod'
export default defineMcpPrompt({
description: 'Open an issue for a project',
inputSchema: {
project: completable(z.string(), async (value) => {
const projects = await listProjects()
return projects.filter(p => p.startsWith(value)).slice(0, 5)
}).describe('Project name'),
},
handler: async ({ project }) => `Open an issue for ${project}.`,
})
完整的 GetPromptResult(多条消息)
export default defineMcpPrompt({
description: 'Structured debugging session',
inputSchema: { issue: z.string(), env: z.enum(['dev', 'staging', 'prod']) },
handler: async ({ issue, env }) => ({
messages: [
{ role: 'user', content: { type: 'text', text: `Debug in ${env}:\n${issue}` } },
{ role: 'assistant', content: { type: 'text', text: 'Let me analyze step by step:' } },
],
}),
})
::callout{icon="i-lucide-info" color="info"}
MCP 规范只允许 user 和 assistant 角色。请将系统指令放在用户消息文本中。
::
参见提示词示例 →。
多处理器组织方式
默认情况下,server/mcp/{tools,resources,prompts}/ 下的所有内容都会在 /mcp 上公开。若要拆分为使用不同中间件的多个端点,请使用文件夹约定:
server/mcp/
├── tools/ # → /mcp (default handler)
└── handlers/
├── admin/
│ ├── index.ts # defineMcpHandler({ middleware: requireAdmin })
│ ├── tools/ # → /mcp/admin (auto)
│ └── prompts/
└── public/
├── index.ts
└── tools/ # → /mcp/public (auto)
export default defineMcpHandler({
description: 'Admin tools — destructive operations gated by Bearer auth.',
middleware: async (event) => {
const apiKey = getHeader(event, 'authorization')?.replace('Bearer ', '')
const user = apiKey ? await verifyAdmin(apiKey) : null
if (user) event.context.user = user
// No throw — let `enabled` guards on individual tools hide them when there's no user
},
// tools / resources / prompts omitted → folder convention auto-attaches them
})
当存在具名处理器时,mcp.defaultHandlerStrategy 控制 /mcp 公开的内容:
'orphans'(默认值)— 仅包含未关联到具名处理器的定义。'all'— 包含所有发现的定义(包罗万象的路由)。
跨领域筛选使用函数形式:
import { defineMcpHandler, getMcpTools } from '@nuxtjs/mcp-toolkit/server'
export default defineMcpHandler({
// Every tool tagged 'searchable', regardless of folder
tools: event => getMcpTools({ event, tags: ['searchable'] }),
})
客户端还可以通过发送 X-MCP-Tools(以逗号分隔、与 tools/list 中的名称匹配),进一步筛选此端点原本会提供的内容。未知名称会返回 HTTP 400。此筛选会在 enabled() 和 mcp:config:resolved 之后自动应用,无需自定义处理器。
参见处理器参考 →。
中间件
中间件定义在 defineMcpHandler 上(不存在单独的 defineMcpMiddleware)。它会在 MCP 请求前后运行,可以填充 event.context,并支持自动调用 next():
// Override the default `/mcp` handler with custom middleware
export default defineMcpHandler({
middleware: async (event) => {
// Soft auth: set context on success, never throw 401 from middleware
// (throwing 401 triggers OAuth discovery in MCP clients)
const apiKey = getHeader(event, 'authorization')?.replace('Bearer ', '')
if (apiKey) {
const user = await verifyApiKey(apiKey).catch(() => null)
if (user) event.context.user = user
}
},
})
::callout{icon="i-lucide-triangle-alert" color="warning"}
不要在中间件中调用 throw createError({ statusCode: 401 }) — 大多数客户端会将 MCP 路由上的 401 解读为“此服务需要 OAuth”,并停止常规流程。对于明确拒绝的请求,请返回 403;或者采用软性身份验证,并通过每个工具的 enabled 守卫隐藏用户无权访问的内容。
::
日志中间件(使用 next() 计时)
import { extractToolNames } from '@nuxtjs/mcp-toolkit/server'
export default defineMcpHandler({
middleware: async (event, next) => {
const start = Date.now()
const response = await next()
const tools = await extractToolNames(event)
console.log(`[mcp] ${tools.join(',') || 'rpc'} took ${Date.now() - start}ms`)
return response
},
})
如需为每个 MCP 请求记录结构化的宽泛事件,请使用 useMcpLogger(),而不是 console.log — 它会自动为 mcp.tool、mcp.session_id、user.id 等字段添加标签。
参见中间件模式 →。
Nitro 运行时钩子
在 MCP 请求生命周期中,会触发两个逐请求 Nitro 钩子。从 server/plugins/*.ts 插件中订阅它们,即可修改已解析的配置,或从任何位置访问 SDK 的 McpServer 实例 — 无需自行实现 defineMcpHandler。如果监听器抛出异常,异常会被记录,且请求会继续执行。
defineMcpHandler middleware → mcp:config:resolved → X-MCP-Tools allowlist → createMcpServer → mcp:server:created → transport
mcp:config:resolved — 按请求修改工具、资源和提示词
此钩子在动态解析器和 enabled(event) 守卫运行之后、X-MCP-Tools 允许列表处理之前触发,并且此时尚未构建逐请求的 McpServer。直接修改 ctx.config。
export default defineNitroPlugin((nitroApp) => {
nitroApp.hooks.hook('mcp:config:resolved', ({ config, event }) => {
if (!event.context.user) {
config.tools = config.tools.filter(t => !t.tags?.includes('admin'))
}
})
})
mcp:server:created — 访问 SDK 服务
此钩子在注册所有工具、资源和提示词之后、服务连接到传输之前触发。可使用 SDK API 延迟注册定义,或调用 getSdkServer(server) 获取低级处理器。
export default defineNitroPlugin((nitroApp) => {
nitroApp.hooks.hook('mcp:server:created', ({ server, event }) => {
server.registerTool(
'whoami',
{ description: 'Return the current user id' },
async () => ({
content: [{ type: 'text', text: String(event.context.userId ?? 'anonymous') }],
}),
)
})
})
参见钩子参考 →。
会话
有状态 MCP — 服务会分配一个 Mcp-Session-Id,并在同一会话中的工具调用之间保留数据。
export default defineNuxtConfig({
mcp: {
sessions: true, // or { maxDuration: 30 * 60_000, maxSessions: 1000 }
},
})
import { z } from 'zod'
export default defineMcpTool({
description: 'Remember a fact for this session',
inputSchema: { key: z.string(), value: z.string() },
handler: async ({ key, value }) => {
const session = useMcpSession<{ facts: Record<string, string> }>()
const facts = (await session.get('facts')) ?? {}
facts[key] = value
await session.set('facts', facts)
return `Remembered ${key}.`
},
})
会话支持 SSE 流式传输、服务到客户端的通知和信息征询。参见会话参考 →。
Elicitation
在请求处理中向已连接的客户端请求结构化输入,或引导用户访问 URL(MCP 规范 2025-11-25)。
import { z } from 'zod'
export default defineMcpTool({
description: 'Create a release after asking for the channel',
inputSchema: { name: z.string() },
handler: async ({ name }) => {
const elicit = useMcpElicitation()
if (!elicit.supports('form')) {
return `Pass --channel via your client; "${name}" needs a release channel.`
}
const result = await elicit.form({
message: `Pick a channel for "${name}"`,
schema: {
channel: z.enum(['stable', 'beta']).describe('Release channel'),
notify: z.boolean().default(true),
},
})
if (result.action !== 'accept') return `Cancelled (${result.action}).`
return `Released ${name} on ${result.content.channel}.`
},
})
- 表单模式 — Zod 原始结构,经过验证并带有类型。Schema 必须是由基本类型、枚举或字符串枚举数组组成的扁平对象。
- URL 模式 —
elicit.url({ message, url }),根据规范需选择启用;通过elicit.supports('url')进行检查。 - 确认 —
await elicit.confirm('Continue?')返回boolean。 - 错误 — 捕获
McpElicitationError(code: 'unsupported' | 'invalid-schema' | 'invalid-response'),以便优雅地回退。
需要启用 nitro.experimental.asyncContext: true,且客户端已声明 elicitation 能力。
参见 elicitation 文档 →。
可观测性 — useMcpLogger()
分通道日志记录器。notify 会发送到已连接的客户端;启用可观测性后,set / event / evlog 会写入请求的宽事件。
import { z } from 'zod'
export default defineMcpTool({
description: 'Charge a payment method',
inputSchema: { userId: z.string(), amount: z.number().int().positive() },
annotations: { destructiveHint: true, idempotentHint: false },
handler: async ({ userId, amount }) => {
const log = useMcpLogger('billing')
log.set({ billing: { amount } })
await log.notify.info({ msg: 'starting charge', amount })
try {
const receipt = await chargeCard(userId, amount)
log.event('charge_completed', { receiptId: receipt.id })
return `Charged ${amount}. Receipt: ${receipt.id}`
}
catch (err) {
log.evlog.error('charge failed', err as Error)
throw err
}
},
})
- 客户端通道(
log.notify):notify(level, data, logger?)+.debug/.info/.warning/.error。始终会解析,不会抛出异常。 - 服务端通道:
set(fields)/event(name, fields?)/setUser({ id, email, name })/setSession({ id })/evlog。未启用时会抛出McpObservabilityNotEnabledError。 - 宽事件会自动添加标签:
mcp.method、mcp.tool、mcp.session_id、mcp.request_id、service: '<env.service>/mcp'。当中间件设置event.context.user/event.context.session时,user.*/session.*会自动传递。
设置
::code-group
pnpm add evlog
npm install evlog
::
export default defineNuxtConfig({
modules: ['evlog/nuxt', '@nuxtjs/mcp-toolkit'],
evlog: { env: { service: 'my-app' } },
})
mcp.logging:省略(自动检测)、true(断言已注册 evlog/nuxt)、false(选择退出)。
数据接收端
通过一个 Nitro 插件,将每个 MCP 宽事件发送到 Axiom、Sentry、OTLP、HyperDX、Datadog、Better Stack 或 PostHog:
import { createAxiomDrain } from 'evlog/adapters/axiom'
export default defineNitroPlugin((nitroApp) => {
nitroApp.hooks.hook('evlog:drain', createAxiomDrain())
})
参见日志记录文档 →和 evlog.dev →。
MCP Apps(交互式 UI 小组件)
在 app/mcp/ 中编写 Vue 单文件组件(SFC)——它们会作为交互式小组件发送到兼容 MCP-Apps 的主机(ChatGPT、Cursor),并由你的 MCP 工具处理程序提供支持。
<script setup lang="ts">
import { z } from 'zod'
defineMcpApp({
description: 'Pick a colour and preview a palette.',
inputSchema: { base: z.string().describe('Hex colour, e.g. #2563eb') },
handler: async ({ base }) => ({
structuredContent: await $fetch('/api/palette', { query: { base } }),
}),
})
const { data, sendPrompt } = useMcpApp<{ swatches: { name: string, hex: string }[] }>()
</script>
<template>
<div class="grid grid-cols-3 gap-2">
<button
v-for="s in data?.swatches"
:key="s.hex"
class="rounded-md p-3 text-white"
:style="{ background: s.hex }"
@click="sendPrompt(`Use ${s.name}`)"
>
{{ s.name }}
</button>
</div>
</template>
每个 SFC 都会成为一个工具、位于 ui://mcp-app/<name> 的 UI 资源,以及一个单文件 HTML 包。处理程序在服务端运行;structuredContent 会内联到 HTML 中,让 iframe 在首次渲染时就加载完整数据。
useMcpApp<T>() 会提供 data、loading、error、hostContext、callTool(name, params)、sendPrompt(prompt) 和 openLink(url)。
默认情况下 CSP 设置严格——可选择允许额外来源:
defineMcpApp({
csp: {
resourceDomains: ['https://images.example.com'], // <img>, <style>, fonts
connectDomains: ['https://api.example.com'], // fetch / XHR / WebSocket
},
// ...
})
参见 MCP Apps 参考 →。
服务端元数据
在客户端 UI 中标识服务端,并通过操作说明引导大模型:
export default defineNuxtConfig({
mcp: {
name: 'Todos MCP',
description: 'Read and update todos for the current user.', // shown in client UIs
instructions: 'Always call list-todos before create-todo. Group results by status.', // injected into the LLM system prompt
icons: [
{ src: 'https://example.com/icon.png', mimeType: 'image/png', sizes: ['64x64'] },
],
},
})
可针对单个处理程序覆盖这些设置,以便在端点需要不同标识时使用(例如 /mcp/admin 使用自己的描述和图标)。
Agent 发现(llms.txt)
当 nuxt-llms 与此模块一同注册时,工具包会在 /llms.txt 中追加一个 ## MCP Server 章节,指向该端点。这样,通过 llms.txt 发现网站的 Agent 无需手动配置 URL 即可连接。无需进行任何设置:
export default defineNuxtConfig({
modules: ['@nuxtjs/mcp-toolkit', 'nuxt-llms'],
llms: { domain: 'https://example.com', title: 'Example' },
mcp: { name: 'Example MCP', description: 'Query Example data from your agent.' },
})
该章节使用 llms.domain + mcp.route 生成 URL,使用 mcp.name 作为标签,使用 mcp.description 作为章节描述,并在设置了 mcp.browserRedirect 时将其添加为文档链接。设置 mcp.llms: false 可选择退出;如果你自行将某个章节标题设为 MCP Server,则始终以该章节为准。未安装 nuxt-llms 时不会执行任何操作。
这是一种便于发现的约定,不属于 MCP 规范的一部分——规范中定义的发现机制(AI Catalog / Server Cards)仍处于草案阶段。
列表定义(读取你的目录)
使用 listMcp* 暴露摘要(目录端点),并使用 getMcp* 获取原始定义,以提供给处理程序:
import { listMcpDefinitions } from '@nuxtjs/mcp-toolkit/server'
export default defineEventHandler(async (event) => {
const { tools, resources, prompts } = await listMcpDefinitions({ event })
return {
name: 'My MCP Server',
tools: tools.map(t => ({ name: t.name, description: t.description })),
resources: resources.map(r => ({ name: r.name, uri: r.uri })),
prompts: prompts.map(p => ({ name: p.name, description: p.description })),
}
})
筛选条件:event(应用 enabled 守卫)、group、tags、handler、orphansOnly。传入 event,即可与请求实际看到的内容完全匹配。
代码模式(实验性)
将处理程序暴露的每个工具封装到单个 code 工具中。LLM 编写 JavaScript,通过 codemode.* 调用工具,并在安全的 V8 隔离环境中通过 secure-exec 执行。
export default defineMcpHandler({
experimental_codeMode: {
progressive: true,
memoryLimit: 128, // MB
cpuTimeLimitMs: 5000,
maxToolCalls: 20,
},
})
npm install secure-exec
适用于让 LLM 在一次往返中编排多个工具调用。不支持 Cloudflare Workers(会返回明确的运行时错误)。
参见代码模式文档 →。
审查与最佳实践
审查或现代化改造 server/mcp/** 时,请逐项检查以下清单:
工具
✅ 直接返回(string、对象、数组),而不是完整的 CallToolResult
✅ 失败时使用 throw createError({ statusCode, message })(会捕获并作为 isError 返回)
✅ 每个 Zod 字段都调用 .describe()
✅ 如实设置 annotations(readOnlyHint、destructiveHint、idempotentHint、openWorldHint)
✅ 对非简单的 schema 设置 inputExamples
✅ 使用 enabled: event => Boolean(event.context.user) 按请求隐藏工具
✅ 对耗时的幂等操作设置 cache: '5m'(或完整的 Nitro 选项)
✅ 调用 useEvent() / useMcpServer() / useMcpSession() / useMcpLogger() / useMcpElicitation() 时设置 nitro.experimental.asyncContext: true
❌ 使用 textResult / jsonResult / errorResult(已弃用——请直接返回或抛出错误)
❌ 使用 defineMcpMiddleware(不存在——中间件是 defineMcpHandler 的字段)
❌ 在中间件中抛出 401(会触发客户端的 OAuth 发现——请返回 403,或使用软身份验证和 enabled 守卫)
❌ 描述过于笼统、缺少 await、未验证输入、暴露机密
资源
✅ 将 mimeType 放在 metadata 下(而不是顶层)
✅ 静态文件使用 file: 'README.md' 简写(URI、MIME 类型和处理程序会自动生成)
✅ 参数化 URI 使用 ResourceTemplate
✅ 使用 metadata.annotations 设置 audience / priority / lastModified
❌ 返回超大负载——通过模板分页 ❌ 省略 MIME 类型——客户端会依靠它正确呈现内容
提示词
✅ 只需一条用户消息时,返回普通 string(使用 role 字段,默认为 'user')
✅ 使用 completable(z.string(), async value => suggestions) 实现参数自动补全
✅ 每个提示词只专注于单一用途
❌ 将系统指令放在 user 消息之外(规范不接受 system 角色)
处理程序
✅ 优先采用文件夹约定(server/mcp/handlers/<name>/),而不是手动编写 tools: [...] 数组
✅ 每个处理程序只负责一项职责(admin / public / apps),并选择合适的 defaultHandlerStrategy
✅ 当端点具有独立身份时,为每个处理程序设置 description / instructions / icons
故障排除
未发现工具 / 资源 / 提示词
- 文件是否位于
server/mcp/{tools,resources,prompts}/下(或handlers/<name>/子目录中)? - 是否使用了
export default(而不是具名导出)? - 重启开发服务器并运行
pnpm nuxt prepare。
自动导入不起作用
nuxt.config.ts的modules中是否列出了'@nuxtjs/mcp-toolkit'?- 运行
pnpm dev:prepare(或pnpm nuxt prepare)以重新生成类型存根。 - 在 IDE 中重启 TypeScript 服务。
- 如果设置了
mcp.autoImports: false,请从@nuxtjs/mcp-toolkit/server显式导入。
无法访问端点
curl -X POST http://localhost:3000/mcp \
-H 'content-type: application/json' \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
应返回 JSON-RPC 响应。如果返回 404,请检查 mcp.enabled !== false 和 mcp.route。
useEvent() / useMcpServer() / useMcpSession() 抛出 “no async context”
在 nuxt.config.ts 中添加 nitro.experimental.asyncContext: true。这些组合式函数会从 Nitro 的异步本地存储中读取当前事件,只有启用 asyncContext 时才能正常工作(Nuxt 3.8+ 默认启用)。
如果 IDE / vue-tsc 将其中某个函数推断为 Promise<…>(这是已知的自动导入问题),可防御性地对调用使用 await;无论哪种方式,运行时都能正常工作:const mcp = await useMcpServer()。
客户端每次请求都会触发 OAuth 流程
你在中间件中抛出了 401。请改用软认证:认证成功时设置 event.context.user,硬性拒绝时返回 403,并使用 enabled: event => Boolean(event.context.user) 控制各个工具的启用状态。
Origin 检查拒绝浏览器客户端
设置 mcp.security.allowedOrigins:
mcp: {
security: {
allowedOrigins: ['https://my-app.vercel.app'],
// allowedOrigins: '*' // explicit opt-out — use with care
},
}
参见故障排除参考 →。
使用 Evals 进行测试
使用 Evalite 对“为提示词选择合适工具”的效果进行评测:
pnpm add -D evalite vitest @ai-sdk/mcp ai
{
"scripts": {
"eval": "evalite",
"eval:ui": "evalite watch"
}
}
import { experimental_createMCPClient as createMCPClient } from '@ai-sdk/mcp'
import { generateText } from 'ai'
import { evalite } from 'evalite'
import { toolCallAccuracy } from 'evalite/scorers'
evalite('MCP Tool Selection', {
data: async () => [
{
input: 'Calculate BMI for 70kg 1.75m',
expected: [{ toolName: 'bmi-calculator', input: { weight: 70, height: 1.75 } }],
},
],
task: async (input) => {
const mcp = await createMCPClient({
transport: { type: 'http', url: 'http://localhost:3000/mcp' },
})
try {
const result = await generateText({
model: 'openai/gpt-4o',
prompt: input,
tools: await mcp.tools(),
})
return result.toolCalls ?? []
}
finally {
await mcp.close()
}
},
scorers: [
({ output, expected }) => toolCallAccuracy({ actualCalls: output, expectedCalls: expected }),
],
})
运行:
pnpm dev # in one terminal
pnpm eval # in another, or `pnpm eval:ui` for the watcher UI
参见测试参考 →。
快速参考
配置
export default defineNuxtConfig({
modules: ['@nuxtjs/mcp-toolkit'],
mcp: {
name: 'My Server',
description: 'What this server does',
instructions: 'How the LLM should use it',
route: '/mcp',
sessions: true,
defaultHandlerStrategy: 'orphans', // or 'all'
security: { allowedOrigins: ['https://my-app.vercel.app'] },
logging: true, // requires evlog/nuxt
llms: true, // advertise in /llms.txt when nuxt-llms is registered
},
nitro: { experimental: { asyncContext: true } },
})
服务端 API(自动导入)
| 辅助函数 | 用途 |
|---|---|
defineMcpTool / defineMcpResource / defineMcpPrompt | 声明定义(自动发现)。 |
defineMcpHandler | 使用中间件 / 动态工具的自定义处理程序。 |
defineMcpApp(位于 app/mcp/*.vue) | 交互式 Vue 小组件。 |
imageResult / audioResult | 将二进制内容封装为工具响应。 |
completable | 为提示词参数提供自动补全。 |
extractToolNames | 从中间件中的 JSON-RPC 请求体解析工具名称。 |
useMcpServer() | 在会话期间注册 / 注销工具。 |
useMcpSession<T>() | 按会话存储数据(必须启用会话)。 |
useMcpLogger(name?) | 客户端通知 + 服务端范围事件。 |
useMcpElicitation() | 向客户端发送表单 / URL / 确认提示。 |
useMcpApp<T>()(位于 MCP App SFC 中) | 提供响应式 data + callTool / sendPrompt 桥接。 |
listMcpTools / listMcpResources / listMcpPrompts / listMcpDefinitions | 生成适用于 JSON 的摘要(目录端点)。 |
getMcpTools / getMcpResources / getMcpPrompts | 获取原始定义(传回处理程序)。 |
getSdkServer | 从 McpServer 获取底层 SDK Server(高级用法)。 |
Nitro 钩子
| 钩子 | 触发时机 |
|---|---|
mcp:config:resolved | 每个请求触发,在动态解析器运行后调用 — 可修改 config.tools / resources / prompts / instructions / icons / name。X-MCP-Tools 会在此钩子之后应用。 |
mcp:server:created | 每个请求触发,在注册完所有定义后调用 — 可调用 server.registerTool(...)、getSdkServer(server).setRequestHandler(...) 等。 |
调试
- DevTools:Shift+Alt+D → MCP Inspector 标签页 → Launch Inspector(在新的浏览器标签页中打开)。
- CLI Inspector:
npx @modelcontextprotocol/inspector http://localhost:3000/mcp - curl 冒烟测试:
curl -X POST … -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
对于需要认证的端点,请在 DevTools 启动器中传入请求头:
const token = process.env.MCP_TOKEN
export default defineNuxtConfig({
mcp: {
inspector: {
headers: { Authorization: token ? `Bearer ${token}` : undefined },
},
},
})