进阶主题

动态定义

根据身份验证、角色或请求上下文有条件地注册工具、资源和提示词。

概述

默认情况下,server/mcp/ 中定义的每个工具、资源和提示词都会为所有客户端注册。动态定义允许你根据请求上下文控制哪些定义是可见的——例如,仅向已认证的管理员显示仅限管理员使用的工具,而对普通用户隐藏它们。

有两种互补的机制:

  1. enabled 守卫 — 每个定义的回调函数,用于控制可见性
  2. 动态处理器定义defineMcpHandler 中的一个函数,根据上下文返回定义

这两种机制都在中间件之后运行,因此 event.context(例如身份验证数据)是可用的。

如果你只需要更改工具/提示词的行为(而不是可见性),你完全可以通过在处理器中使用 useEvent() 读取 event.context 来实现。动态定义用于控制哪些定义会出现在 tools/listprompts/listresources/list 中。

enabled 守卫

向任何工具、资源或提示词定义添加 enabled 回调。当回调返回 false 时,该定义将对客户端隐藏。

工具

server/mcp/tools/delete-all.ts
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'
  },
})

资源

server/mcp/resources/internal-logs.ts
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() }],
  }),
})

提示词

server/mcp/prompts/onboarding.ts
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 守卫在中间件之后运行,因此请在中间件中设置你的身份验证上下文:

server/mcp/index.ts
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
nuxt.config.ts
export default defineNuxtConfig({
  nitro: {
    experimental: {
      asyncContext: true,
    },
  },
})

动态处理器定义

为了获得更高的控制权,可以在 defineMcpHandler 中将 toolsresourcesprompts 传递为一个函数。该函数接收 H3 事件并返回一个定义数组。

server/mcp/index.ts
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 守卫仍然会被评估:

server/mcp/index.ts
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
  },
})
server/mcp/tools/admin-delete.ts
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 操作。

在会话期间注册工具

server/mcp/tools/create-shortcut.ts
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) 按名称移除工具——无需存储句柄:

server/mcp/tools/manage-agents.ts
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 必须为 trueuseMcpServer() 访问请求上下文所必需)
  • 会话期间的变更在启用会话时最有用,因为服务器实例会在请求之间持久存在。如果不使用会话,每次请求都会创建一个新的服务器,因此变更仅对单个请求有效。
并非所有 MCP 客户端都支持 notifications/tools/list_changed。如果客户端不支持,用户可能需要重新连接才能看到更新的定义。请查阅客户端文档以确认兼容性。

TypeScript

为实现类型安全的上下文,请扩展 H3 事件上下文:

server/types.ts
declare module 'h3' {
  interface H3EventContext {
    user?: {
      id: string
      name: string
      role: 'user' | 'admin'
    }
  }
}

后续步骤