mcpField
M
manage-mcp

manage-mcp

github.com/nuxt-modules开发181213 次安装1 天前更新
代码仓库

关于 manage-mcp

面向 Nuxt 开发者的 MCP 管理技能,指导使用 `@nuxtjs/mcp-toolkit` 配置服务,并创建工具、资源和提示词。它还涵盖多处理器组织、认证、中间件、MCP Apps 和故障排查。

主要功能

  1. 01配置 Nuxt MCP 服务
  2. 02创建工具、资源和提示词
  3. 03按处理器组织多个端点
  4. 04使用中间件与条件可见性
  5. 05构建 MCP Apps 交互组件
  6. 06提供测试与故障排查指导

适用场景

  1. 01在 Nuxt 应用中添加 MCP 服务
  2. 02按权限拆分管理与公开端点
  3. 03为 MCP 客户端提供交互式 Vue 组件
  4. 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.md
handlers.md
middleware.md
prompts.md
resources.md
sessions.md
testing.md
tools.md
troubleshooting.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
验证
  1. 启动开发服务:pnpm dev
  2. 访问端点:curl http://localhost:3000/mcp(响应 MCP JSON-RPC)
  3. 打开 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


故障排除

未发现工具 / 资源 / 提示词
  1. 文件是否位于 server/mcp/{tools,resources,prompts}/ 下(或 handlers/<name>/ 子目录中)?
  2. 是否使用了 export default(而不是具名导出)?
  3. 重启开发服务器并运行 pnpm nuxt prepare。
自动导入不起作用
  1. nuxt.config.ts 的 modules 中是否列出了 '@nuxtjs/mcp-toolkit'?
  2. 运行 pnpm dev:prepare(或 pnpm nuxt prepare)以重新生成类型存根。
  3. 在 IDE 中重启 TypeScript 服务。
  4. 如果设置了 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 },
    },
  },
})

了解更多

评论

支持 Markdown