动态定义
概述
默认情况下,server/mcp/ 中定义的每个工具、资源和提示词都会为所有客户端注册。动态定义允许你根据请求上下文控制哪些定义是可见的——例如,仅向已认证的管理员显示仅限管理员使用的工具,而对普通用户隐藏它们。
有两种互补的机制:
enabled守卫 — 每个定义的回调函数,用于控制可见性- 动态处理器定义 —
defineMcpHandler中的一个函数,根据上下文返回定义
这两种机制都在中间件之后运行,因此 event.context(例如身份验证数据)是可用的。
useEvent() 读取 event.context 来实现。动态定义用于控制哪些定义会出现在 tools/list、prompts/list 和 resources/list 中。enabled 守卫
向任何工具、资源或提示词定义添加 enabled 回调。当回调返回 false 时,该定义将对客户端隐藏。
工具
import { z } from 'zod'
import { defineMcpTool } from '@nuxtjs/mcp-toolkit/server'
export default defineMcpTool({
name: 'delete-all',
description: 'Delete all records (admin only)',
inputSchema: {
confirm: z.boolean().describe('Confirm deletion'),
},
enabled: event => event.context.user?.role === 'admin',
handler: async ({ confirm }) => {
if (!confirm) return 'Deletion cancelled'
await deleteAllRecords()
return 'All records deleted'
},
})
资源
import { defineMcpResource } from '@nuxtjs/mcp-toolkit/server'
export default defineMcpResource({
name: 'internal-logs',
description: 'Application logs (admin only)',
uri: 'app://logs',
enabled: event => event.context.user?.role === 'admin',
handler: async (uri) => ({
contents: [{ uri: uri.toString(), text: await readLogs() }],
}),
})
提示词
import { useEvent } from 'h3'
import { defineMcpPrompt } from '@nuxtjs/mcp-toolkit/server'
export default defineMcpPrompt({
name: 'onboarding',
description: 'Personalized onboarding (authenticated users only)',
enabled: event => !!event.context.user,
handler: async () => {
const event = useEvent()
return {
messages: [{
role: 'user',
content: {
type: 'text',
text: `Welcome ${event.context.user.name}! Here's how to get started...`,
},
}],
}
},
})
中间件设置
enabled 守卫在中间件之后运行,因此请在中间件中设置你的身份验证上下文:
import { getHeader } from 'h3'
import { defineMcpHandler } from '@nuxtjs/mcp-toolkit/server'
export default defineMcpHandler({
middleware: async (event) => {
const token = getHeader(event, 'authorization')?.replace('Bearer ', '')
if (token) {
event.context.user = await verifyToken(token)
}
},
})
useEvent(),请在 Nuxt 配置中启用 asyncContext:export default defineNuxtConfig({
nitro: {
experimental: {
asyncContext: true,
},
},
})
动态处理器定义
为了获得更高的控制权,可以在 defineMcpHandler 中将 tools、resources 或 prompts 传递为一个函数。该函数接收 H3 事件并返回一个定义数组。
import { defineMcpHandler } from '@nuxtjs/mcp-toolkit/server'
import { adminTools } from './admin-tools'
import { publicTools } from './public-tools'
export default defineMcpHandler({
middleware: async (event) => {
event.context.user = await getUser(event)
},
tools: async (event) => {
const base = [...publicTools]
if (event.context.user?.role === 'admin') {
base.push(...adminTools)
}
return base
},
prompts: async (event) => {
if (event.context.user) {
return [authenticatedPrompt, dashboardPrompt]
}
return [guestPrompt]
},
})
这在以下场景中非常有用:
- 以编程方式从数据库或配置构建工具列表
- 从多个模块组合定义
- 应用复杂的过滤逻辑
结合两种方法
enabled 守卫和动态处理器定义可以协同工作。当你使用动态处理器定义时,返回的每个定义的 enabled 守卫仍然会被评估:
import { defineMcpHandler } from '@nuxtjs/mcp-toolkit/server'
export default defineMcpHandler({
middleware: async (event) => {
event.context.user = await getUser(event)
},
tools: async (event) => {
const allTools = await loadToolsFromConfig()
return allTools
},
})
import { defineMcpTool } from '@nuxtjs/mcp-toolkit/server'
export default defineMcpTool({
name: 'admin-delete',
enabled: event => event.context.user?.role === 'admin',
handler: async () => { /* ... */ },
})
在这种情况下,admin-delete 通过自动发现(或动态方式)加载,并且会经过其 enabled 守卫的过滤。
会话行为
当启用会话时,MCP 服务器会在会话的第一次请求时创建。动态定义会在此时解析,并且相同的工具集将在整个会话生命周期内保持不变。
这意味着:
- 连接的管理员将在整个会话期间获得管理员工具
- 连接的普通用户永远看不到管理员工具,即使他们在会话中途获得了管理员权限
- 不同的会话可以拥有不同的工具集
如果不使用会话,则每次请求都会创建一个新的服务器,因此定义可能会因请求而异。
会话期间的变更
enabled 守卫和动态处理器定义控制会话启动时注册哪些定义。对于需要在活跃会话期间添加、移除或更新定义的情况,请使用 useMcpServer() 组合式函数。
useMcpServer() 返回一个辅助对象,其中包含用于注册、移除和管理工具、提示词和资源的方法。SDK 会自动向客户端发送 notifications/tools/list_changed(或提示词/资源的等效通知),提示其重新获取列表。
API
| 方法 | 描述 |
|---|---|
registerTool(name, config, handler) | 注册一个新工具。返回 RegisteredTool 句柄。 |
registerPrompt(name, config, handler) | 注册一个新提示词。返回 RegisteredPrompt 句柄。 |
registerResource(name, uri, config, handler) | 注册一个新资源。返回 RegisteredResource 句柄。 |
removeTool(name) | 按名称移除工具。如果找到则返回 true。 |
removePrompt(name) | 按名称移除提示词。如果找到则返回 true。 |
removeResource(name) | 按名称移除资源。如果找到则返回 true。 |
server | 底层的 McpServer 实例,用于高级 SDK 操作。 |
在会话期间注册工具
import { z } from 'zod'
import { defineMcpTool } from '@nuxtjs/mcp-toolkit/server'
export default defineMcpTool({
description: 'Create a shortcut tool for a specific query',
inputSchema: {
name: z.string().describe('Name for the shortcut tool'),
query: z.string().describe('The query this shortcut runs'),
},
handler: async ({ name, query }) => {
const mcp = useMcpServer()
mcp.registerTool(name, {
description: `Shortcut: ${query}`,
}, async () => {
const result = await runQuery(query)
return { content: [{ type: 'text', text: JSON.stringify(result) }] }
})
return `Shortcut "${name}" created`
},
})
调用此工具后,客户端的工具列表将刷新并包含新的快捷方式。
在会话期间移除工具
使用 removeTool(name) 按名称移除工具——无需存储句柄:
import { z } from 'zod'
import { createError } from 'h3'
import { defineMcpTool } from '@nuxtjs/mcp-toolkit/server'
export const registerAgent = defineMcpTool({
name: 'register-agent',
inputSchema: {
agentId: z.string(),
agentName: z.string(),
},
handler: async ({ agentId, agentName }) => {
const mcp = useMcpServer()
mcp.registerTool(`call-${agentId}`, {
description: `Call agent: ${agentName}`,
}, async () => {
const response = await callAgent(agentId)
return { content: [{ type: 'text', text: response }] }
})
return `Agent "${agentName}" registered`
},
})
export const unregisterAgent = defineMcpTool({
name: 'unregister-agent',
inputSchema: {
agentId: z.string(),
},
handler: async ({ agentId }) => {
const mcp = useMcpServer()
const removed = mcp.removeTool(`call-${agentId}`)
if (!removed) throw createError({ statusCode: 404, message: `Agent "${agentId}" not found` })
return `Agent "${agentId}" unregistered`
},
})
更新和切换定义
对于就地更新,请使用 registerTool() 返回的 RegisteredTool 句柄:
const mcp = useMcpServer()
const registered = mcp.registerTool('my-tool', { description: 'v1' }, handler)
// Update the description and handler
registered.update({ description: 'v2', callback: newHandler })
// Temporarily hide from the client
registered.disable()
// Re-enable
registered.enable()
相同的模式也适用于 registerPrompt() 和 registerResource(),它们会返回具有相同方法的句柄。
要求
- Nuxt 配置中的
nitro.experimental.asyncContext必须为true(useMcpServer()访问请求上下文所必需) - 会话期间的变更在启用会话时最有用,因为服务器实例会在请求之间持久存在。如果不使用会话,每次请求都会创建一个新的服务器,因此变更仅对单个请求有效。
notifications/tools/list_changed。如果客户端不支持,用户可能需要重新连接才能看到更新的定义。请查阅客户端文档以确认兼容性。TypeScript
为实现类型安全的上下文,请扩展 H3 事件上下文:
declare module 'h3' {
interface H3EventContext {
user?: {
id: string
name: string
role: 'user' | 'admin'
}
}
}