工具
什么是工具?
工具是 AI 助手可以调用以执行操作或检索信息的函数。它们接受经过验证的输入参数并返回结构化的结果。
使用 @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
基础工具定义
以下是一个简单的回显消息的工具:
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}`,
})
自动生成的名称和标题
你可以省略 name 和 title —— 它们将根据文件名自动生成:
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 将自动转换为:
name:list-documentation(短横线命名法)title:List Documentation(标题大小写)
你仍然可以显式提供 name 或 title 以覆盖自动生成的值。
工具结构
工具定义包含以下内容:
export default defineMcpTool({
name: 'tool-name', // 唯一标识符(可选 - 根据文件名自动生成)
inputSchema: { ... }, // 用于输入验证的 Zod 模式
handler: async (args) => {
return 'result' // 字符串、数字、布尔值、对象或 CallToolResult
},
})
export default defineMcpTool({
name: 'tool-name', // 可选 - 根据文件名自动生成
title: 'Tool Title', // 可选 - 根据文件名自动生成
description: 'Tool description', // 工具的功能描述
inputSchema: { ... }, // 可选 - 用于输入验证的 Zod 模式
outputSchema: { ... }, // 用于结构化输出的 Zod 模式
annotations: { ... }, // 面向客户端的行为提示
inputExamples: [{ ... }], // 具体的使用示例
handler: async (args) => { ... },
})
输入模式
inputSchema 是可选的,它使用 Zod 来定义和验证输入参数。如果提供,每个字段都必须是一个 Zod 模式。没有参数的工具可以完全省略 inputSchema:
import { defineMcpTool } from '@nuxtjs/mcp-toolkit/server'
export default defineMcpTool({
name: 'echo',
description: 'Echo back a message',
handler: async () => 'Echo: test',
})
对于带参数的工具,请使用 Zod 模式进行定义:
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 定义结构化输出:
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 返回 string、number、boolean、object 或 array:
handler: async ({ name }) => `Hello ${name}`
// → { content: [{ type: 'text', text: 'Hello World' }] }
handler: async ({ a, b }) => a + b
// → { content: [{ type: 'text', text: '10' }] }
handler: async ({ id }) => {
const user = await getUser(id)
return user
}
// → { content: [{ type: 'text', text: '{ "id": ... }' }] }
handler: async ({ id }) => await exists(id)
// → { content: [{ type: 'text', text: 'true' }] }
当你需要更多控制权时(例如图像、多个内容项、structuredContent),也可以返回完整的 CallToolResult 格式。
内容类型
对于高级用例,返回带有类型化内容的完整 CallToolResult:
return {
content: [{
type: 'image',
data: base64ImageData,
mimeType: 'image/png',
}],
}
return {
structuredContent: {
bmi: 25.5,
category: 'Normal',
},
}
// 文本内容将自动生成,作为旧版客户端的后备方案
return {
content: [{
type: 'resource',
resource: {
uri: 'file:///path/to/file',
text: 'File content',
mimeType: 'text/plain',
},
}],
}
结果辅助函数
该模块提供了 imageResult 和 audioResult 辅助函数,用于处理工具响应中的二进制媒体(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')
},
})
工具注解
注解是行为提示,用于告知 MCP 客户端工具的行为方式。客户端可以使用它们来决定何时提示用户进行确认(人在回路)。
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 }) => {
// ...
},
})
注解参考
| 注解 | 类型 | 默认值 | 描述 |
|---|---|---|---|
readOnlyHint | boolean | false | 如果为 true,工具仅读取数据而不修改任何状态(可安全重试)。 |
destructiveHint | boolean | true | 如果为 true,工具可能会执行删除数据等破坏性操作。仅在 readOnlyHint 为 false 时有意义。 |
idempotentHint | boolean | false | 如果为 true,使用相同参数多次调用工具不会产生超出第一次调用的额外效果。仅在 readOnlyHint 为 false 时有意义。 |
openWorldHint | boolean | true | 如果为 true,工具可能会与外部世界交互(外部 API、互联网)。如果为 false,它仅操作本地/内部数据。 |
以下是典型工具的常见注解模式:
// 搜索、列表、查找、计算...
annotations: {
readOnlyHint: true,
destructiveHint: false,
openWorldHint: false,
}
// 每次创建一条新记录
annotations: {
readOnlyHint: false,
destructiveHint: false,
idempotentHint: false,
openWorldHint: false,
}
// 更新是幂等的(相同输入 → 相同结果)
annotations: {
readOnlyHint: false,
destructiveHint: false,
idempotentHint: true,
openWorldHint: false,
}
// 破坏性且幂等(删除两次效果相同)
annotations: {
readOnlyHint: false,
destructiveHint: true,
idempotentHint: true,
openWorldHint: false,
}
输入示例
你可以使用 inputExamples 为你的工具提供具体的使用示例。这些示例是类型安全的(与你的 inputSchema 匹配),并通过 _meta.inputExamples 传输给客户端。
输入示例有助于 AI 模型理解如何正确填写工具参数,特别是对于包含可选字段或复杂输入的工具。
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() 来处理带有状态码的错误:
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 实例同样适用:
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 解析)或毫秒数:
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 缓存选项的对象:
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 }) => {
// ...
},
})
缓存选项参考
| 选项 | 类型 | 是否必需 | 描述 |
|---|---|---|---|
maxAge | string | number | 是 | 缓存时长(例如 '1h'、3600000) |
getKey | (args) => string | 否 | 自定义缓存键生成器 |
staleMaxAge | number | 否 | stale-while-revalidate 的时长 |
swr | boolean | 否 | 启用 stale-while-revalidate |
name | string | 否 | 缓存名称(根据工具名称自动生成) |
group | string | 否 | 缓存组(默认:'mcp') |
高级示例
集成 API 的工具
以下是一个典型的由 API 支持的工具示例:
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
},
})
分组与标签
使用 group 和 tags 来组织你的工具,以便进行过滤和渐进式发现。分组和标签会在 _meta 中公开,并在被采纳后映射到 SEP-1300。
显式分组与标签
直接在定义上设置 group 和 tags:
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 中使用这些值来过滤、排序或分组工具。
group 和 tags 会存储在定义对象上,但尚未在协议响应(resources/list、prompts/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 守卫来控制工具是否对客户端可见:
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 中隐藏且无法被调用。