核心概念

工具

使用 Zod 验证和类型安全创建 MCP 工具。

什么是工具?

工具是 AI 助手可以调用以执行操作或检索信息的函数。它们接受经过验证的输入参数并返回结构化的结果。

Prompt
使用 @nuxtjs/mcp-toolkit 在我的 Nuxt 应用中创建一个新的 MCP 工具。

- 在 server/mcp/tools/ 中创建一个文件(例如 server/mcp/tools/my-tool.ts)
- 使用 defineMcpTool(自动导入)并配置 description 和 handler
- 使用 import { z } from 'zod' 导入 Zod,并在 inputSchema 中定义输入参数(例如 z.string().describe('...'))
- handler 接收经过验证的输入,并返回字符串、数字、布尔值、对象或完整的 CallToolResult
- 在错误情况下,使用 h3 的 createError({ statusCode, message }) 抛出错误
- name 和 title 会根据文件名自动生成(例如 my-tool.ts → name: 'my-tool', title: 'My Tool')
- 添加 annotations 以提供行为提示(readOnlyHint, destructiveHint, idempotentHint, openWorldHint)
- 使用子目录自动推断分组(例如 tools/admin/delete-user.ts → group: 'admin')

文档:https://mcp-toolkit.nuxt.dev/core-concepts/tools

基础工具定义

以下是一个简单的回显消息的工具:

server/mcp/tools/echo.ts
import { z } from 'zod'
import { defineMcpTool } from '@nuxtjs/mcp-toolkit/server'

export default defineMcpTool({
  name: 'echo',
  description: 'Echo back a message',
  inputSchema: {
    message: z.string().describe('The message to echo back'),
  },
  handler: async ({ message }) => `Echo: ${message}`,
})

自动生成的名称和标题

你可以省略 nametitle —— 它们将根据文件名自动生成:

server/mcp/tools/list-documentation.ts
import { defineMcpTool } from '@nuxtjs/mcp-toolkit/server'

export default defineMcpTool({
  // name 和 title 将根据文件名自动生成:
  // name: 'list-documentation'
  // title: 'List Documentation'
  description: 'List all documentation files',
  handler: async () => {
    // ...
  },
})

文件名 list-documentation.ts 将自动转换为:

  • namelist-documentation(短横线命名法)
  • titleList Documentation(标题大小写)

你仍然可以显式提供 nametitle 以覆盖自动生成的值。

工具结构

工具定义包含以下内容:

export default defineMcpTool({
  name: 'tool-name',        // 唯一标识符(可选 - 根据文件名自动生成)
  inputSchema: { ... },      // 用于输入验证的 Zod 模式
  handler: async (args) => {
    return 'result'          // 字符串、数字、布尔值、对象或 CallToolResult
  },
})

输入模式

inputSchema 是可选的,它使用 Zod 来定义和验证输入参数。如果提供,每个字段都必须是一个 Zod 模式。没有参数的工具可以完全省略 inputSchema

server/mcp/tools/echo.ts
import { defineMcpTool } from '@nuxtjs/mcp-toolkit/server'

export default defineMcpTool({
  name: 'echo',
  description: 'Echo back a message',
  handler: async () => 'Echo: test',
})

对于带参数的工具,请使用 Zod 模式进行定义:

server/mcp/tools/calculator.ts
import { z } from 'zod'
import { defineMcpTool } from '@nuxtjs/mcp-toolkit/server'

export default defineMcpTool({
  name: 'calculator',
  inputSchema: {
    // 字符串输入
    operation: z.string().describe('Operation to perform'),

    // 数字输入
    a: z.number().describe('First number'),
    b: z.number().describe('Second number'),

    // 可选字段
    precision: z.number().optional().describe('Decimal precision'),

    // 枚举输入
    format: z.enum(['decimal', 'fraction']).describe('Output format'),

    // 数组输入
    numbers: z.array(z.number()).describe('List of numbers'),
  },
  handler: async ({ operation, a, b, precision, format, numbers }) => {
    // 处理器实现
  },
})

常用 Zod 类型

Zod 类型示例描述
z.string()z.string().min(1).max(100)带验证的字符串
z.number()z.number().min(0).max(100)带验证的数字
z.boolean()z.boolean()布尔值
z.array()z.array(z.string())值数组
z.object()z.object({ ... })嵌套对象
z.enum()z.enum(['a', 'b'])枚举
z.optional()z.string().optional()可选字段
z.default()z.string().default('value')带默认值的字段

输出模式

使用 outputSchema 定义结构化输出:

server/mcp/tools/bmi.ts
import { z } from 'zod'
import { defineMcpTool } from '@nuxtjs/mcp-toolkit/server'

export default defineMcpTool({
  name: 'calculate-bmi',
  description: 'Calculate Body Mass Index',
  inputSchema: {
    weightKg: z.number().describe('Weight in kilograms'),
    heightM: z.number().describe('Height in meters'),
  },
  outputSchema: {
    bmi: z.number(),
    category: z.string(),
  },
  handler: async ({ weightKg, heightM }) => {
    const bmi = weightKg / (heightM * heightM)
    let category = 'Normal'
    if (bmi < 18.5) category = 'Underweight'
    else if (bmi >= 25) category = 'Overweight'
    else if (bmi >= 30) category = 'Obese'

    return {
      structuredContent: {
        bmi: Math.round(bmi * 100) / 100,
        category,
      },
    }
  },
})

structuredContent 字段提供与你的 outputSchema 匹配的结构化数据,使 AI 助手更容易处理结果。

处理器函数

handler 是一个异步函数,它接收经过验证的输入并返回结果。你可以直接返回简化的值 —— 它们会被自动包装为 MCP 的 CallToolResult 格式。

简化返回

直接从你的 handler 返回 stringnumberbooleanobjectarray

handler: async ({ name }) => `Hello ${name}`
// → { content: [{ type: 'text', text: 'Hello World' }] }

当你需要更多控制权时(例如图像、多个内容项、structuredContent),也可以返回完整的 CallToolResult 格式。

内容类型

对于高级用例,返回带有类型化内容的完整 CallToolResult

return {
  content: [{
    type: 'image',
    data: base64ImageData,
    mimeType: 'image/png',
  }],
}

结果辅助函数

该模块提供了 imageResultaudioResult 辅助函数,用于处理工具响应中的二进制媒体(Base64 编码数据加上 MIME 类型):

import { z } from 'zod'
import { defineMcpTool, imageResult, audioResult } from '@nuxtjs/mcp-toolkit/server'

export default defineMcpTool({
  description: 'Generate chart',
  inputSchema: { data: z.array(z.number()) },
  handler: async ({ data }) => {
    const base64 = await generateChart(data)
    return imageResult(base64, 'image/png')
  },
})
import { z } from 'zod'
import { defineMcpTool, audioResult } from '@nuxtjs/mcp-toolkit/server'

export default defineMcpTool({
  description: 'Text to speech',
  inputSchema: { text: z.string() },
  handler: async ({ text }) => {
    const base64 = await synthesizeSpeech(text)
    return audioResult(base64, 'audio/mp3')
  },
})
textResultjsonResulterrorResult 辅助函数已弃用。请直接从你的 handler 返回值,并在错误情况下抛出错误(参见 错误处理)。

工具注解

注解是行为提示,用于告知 MCP 客户端工具的行为方式。客户端可以使用它们来决定何时提示用户进行确认(人在回路)。

server/mcp/tools/delete-user.ts
import { z } from 'zod'
import { defineMcpTool } from '@nuxtjs/mcp-toolkit/server'

export default defineMcpTool({
  name: 'delete-user',
  description: 'Delete a user account',
  inputSchema: {
    userId: z.string(),
  },
  annotations: {
    readOnlyHint: false,    // 工具会修改状态
    destructiveHint: true,  // 工具执行破坏性更新
    idempotentHint: true,   // 删除同一用户两次不会产生额外影响
    openWorldHint: false,   // 工具不与外部系统交互
  },
  handler: async ({ userId }) => {
    // ...
  },
})

注解参考

注解类型默认值描述
readOnlyHintbooleanfalse如果为 true,工具仅读取数据而不修改任何状态(可安全重试)。
destructiveHintbooleantrue如果为 true,工具可能会执行删除数据等破坏性操作。仅在 readOnlyHintfalse 时有意义。
idempotentHintbooleanfalse如果为 true,使用相同参数多次调用工具不会产生超出第一次调用的额外效果。仅在 readOnlyHintfalse 时有意义。
openWorldHintbooleantrue如果为 true,工具可能会与外部世界交互(外部 API、互联网)。如果为 false,它仅操作本地/内部数据。

以下是典型工具的常见注解模式:

// 搜索、列表、查找、计算...
annotations: {
  readOnlyHint: true,
  destructiveHint: false,
  openWorldHint: false,
}
所有注解均为提示 —— 不保证每个 MCP 客户端都会遵守。客户端绝不应基于来自不受信任服务器的注解做出安全关键决策。

输入示例

你可以使用 inputExamples 为你的工具提供具体的使用示例。这些示例是类型安全的(与你的 inputSchema 匹配),并通过 _meta.inputExamples 传输给客户端。

输入示例有助于 AI 模型理解如何正确填写工具参数,特别是对于包含可选字段或复杂输入的工具。

server/mcp/tools/create-todo.ts
import { z } from 'zod'
import { defineMcpTool } from '@nuxtjs/mcp-toolkit/server'

export default defineMcpTool({
  description: 'Create a new todo',
  inputSchema: {
    title: z.string().describe('The title of the todo'),
    content: z.string().optional().describe('Optional description'),
  },
  inputExamples: [
    { title: 'Buy groceries', content: 'Milk, eggs, bread' },
    { title: 'Fix login bug' },  // content 是可选的
  ],
  handler: async ({ title, content }) => {
    // ...
  },
})
inputExamples 对于包含可选参数、枚举或复杂嵌套输入的工具特别有用,展示具体值有助于模型选择正确的格式。

错误处理

直接从你的处理程序中抛出错误——就像在 Nitro 事件处理程序中一样。抛出的错误会被自动捕获并转换为符合 MCP 规范的 isError 结果。

H3 错误(推荐)

使用 H3 的 createError() 来处理带有状态码的错误:

server/mcp/tools/get-user.ts
import { z } from 'zod'
import { createError } from 'h3'
import { defineMcpTool } from '@nuxtjs/mcp-toolkit/server'

export default defineMcpTool({
  name: 'get-user',
  description: 'Get a user by ID',
  inputSchema: {
    id: z.string(),
  },
  handler: async ({ id }) => {
    const user = await findUser(id)
    if (!user) {
      throw createError({ statusCode: 404, message: 'User not found' })
    }
    return user
  },
})
// 错误结果:{ isError: true, content: [{ type: 'text', text: '[404] User not found' }] }

H3 错误还可以包含结构化数据:

throw createError({
  statusCode: 400,
  message: 'Validation failed',
  data: { fields: ['name', 'email'] },
})
// 错误文本:'[400] Validation failed\n{ "fields": ["name", "email"] }'

普通错误

普通的 Error 实例同样适用:

server/mcp/tools/safe-divide.ts
import { z } from 'zod'
import { defineMcpTool } from '@nuxtjs/mcp-toolkit/server'

export default defineMcpTool({
  name: 'safe-divide',
  inputSchema: {
    a: z.number(),
    b: z.number(),
  },
  handler: async ({ a, b }) => {
    if (b === 0) throw new Error('Division by zero')
    return a / b
  },
})

响应缓存

你可以使用 Nitro 的缓存系统来缓存工具响应。cache 选项接受三种格式:

简单时长

使用字符串时长(由 ms 解析)或毫秒数:

server/mcp/tools/cached-data.ts
import { z } from 'zod'
import { defineMcpTool } from '@nuxtjs/mcp-toolkit/server'

export default defineMcpTool({
  description: 'Fetch data with 1 hour cache',
  inputSchema: {
    id: z.string(),
  },
  cache: '1h', // 或 '30m'、'2 days'、3600000 等
  handler: async ({ id }) => {
    return await fetchExpensiveData(id)
  },
})

完整缓存选项

如需更多控制,请使用包含所有 Nitro 缓存选项的对象:

server/mcp/tools/cached-pages.ts
import { z } from 'zod'
import { defineMcpTool } from '@nuxtjs/mcp-toolkit/server'

export default defineMcpTool({
  description: 'Get page with custom cache key',
  inputSchema: {
    path: z.string(),
  },
  cache: {
    maxAge: '1h',
    getKey: args => `page-${args.path}`,
    swr: true, // stale-while-revalidate
  },
  handler: async ({ path }) => {
    // ...
  },
})

缓存选项参考

选项类型是否必需描述
maxAgestring | number缓存时长(例如 '1h'3600000
getKey(args) => string自定义缓存键生成器
staleMaxAgenumberstale-while-revalidate 的时长
swrboolean启用 stale-while-revalidate
namestring缓存名称(根据工具名称自动生成)
groupstring缓存组(默认:'mcp'
有关所有可用选项,请参阅 Nitro 缓存文档

高级示例

集成 API 的工具

以下是一个典型的由 API 支持的工具示例:

server/mcp/tools/get-weather.ts
import { z } from 'zod'
import { createError } from 'h3'
import { defineMcpTool } from '@nuxtjs/mcp-toolkit/server'

export default defineMcpTool({
  name: 'get-weather',
  description: 'Get current weather for a city',
  inputSchema: {
    city: z.string().describe('City name'),
  },
  handler: async ({ city }) => {
    const data = await $fetch(`/api/weather/${city}`)
    if (!data) throw createError({ statusCode: 404, message: `City "${city}" not found` })
    return data
  },
})

分组与标签

使用 grouptags 来组织你的工具,以便进行过滤和渐进式发现。分组和标签会在 _meta 中公开,并在被采纳后映射到 SEP-1300

显式分组与标签

直接在定义上设置 grouptags

server/mcp/tools/delete-user.ts
import { z } from 'zod'
import { defineMcpTool } from '@nuxtjs/mcp-toolkit/server'

export default defineMcpTool({
  group: 'admin',
  tags: ['destructive', 'user-management'],
  description: 'Delete a user account',
  inputSchema: {
    userId: z.string(),
  },
  handler: async ({ userId }) => {
    // ...
  },
})

从目录自动推断分组

将工具放在子目录中,分组将被自动推断:

server/mcp/tools/
├── admin/
│   ├── delete-user.ts    → group: 'admin'
│   └── stats.ts          → group: 'admin'
├── content/
│   └── list-pages.ts     → group: 'content'
└── search.ts             → no group

定义上显式设置的 group 始终优先于目录推断的值。

客户端如何查看分组与标签

分组和标签会包含在 tools/list 响应的 _meta 字段中:

{
  "name": "delete-user",
  "_meta": {
    "group": "admin",
    "tags": ["destructive", "user-management"]
  }
}

MCP 客户端可以在其 UI 中使用这些值来过滤、排序或分组工具。

对于 资源(resources)提示词(prompts)grouptags 会存储在定义对象上,但尚未在协议响应(resources/listprompts/list)中公开。当 MCP SDK 采纳 SEP-1300 后,将支持此功能。

文件组织

将你的工具组织在 server/mcp/tools/ 目录中。支持扁平和嵌套布局:

server/
└── mcp/
    └── tools/
        ├── echo.ts
        ├── calculator.ts
        ├── admin/
        │   ├── delete-user.ts
        │   └── stats.ts
        └── content/
            └── list-pages.ts

每个文件都应导出一个默认的工具定义。子目录会自动为其中的所有工具设置 group

类型安全

该模块提供完整的 TypeScript 类型推断:

// 输入类型从 inputSchema 推断
handler: async ({ message }) => {
  // message 的类型为 string
}

// 输出类型从 outputSchema 推断
const result = {
  structuredContent: {
    bmi: 25.5,      // number
    category: '...', // string
  },
}

条件注册

你可以使用 enabled 守卫来控制工具是否对客户端可见:

server/mcp/tools/admin-tool.ts
import { defineMcpTool } from '@nuxtjs/mcp-toolkit/server'

export default defineMcpTool({
  name: 'admin-tool',
  description: 'Admin-only tool',
  enabled: event => event.context.user?.role === 'admin',
  handler: async () => {
    // ...
  },
})

enabled 返回 false 时,该工具将从 tools/list 中隐藏且无法被调用。

有关基于身份验证过滤的详细文档,请参阅 动态定义 指南。

后续步骤