进阶主题

中间件

拦截 MCP 请求以添加身份验证、日志记录、分析等功能。

什么是中间件?

中间件允许你在 MCP 请求被处理之前(以及可选的之后)运行代码。

Prompt
为我的 Nuxt MCP 服务器 (@nuxtjs/mcp-toolkit) 添加身份验证中间件。

- 创建或编辑 server/mcp/index.ts,使用 defineMcpHandler 和一个中间件函数
- 在中间件中,验证 auth 请求头(例如从 getHeader(event, 'authorization') 获取 Bearer token 或 API key)
- 将已认证的用户设置到 event.context.user 以便工具访问
- 对于缺少认证的情况,不要抛出 401 错误 —— 采用“软”处理方式,仅在认证成功时设置上下文
- 在工具中通过 useEvent().context.user 访问用户上下文(需要 nitro.experimental.asyncContext: true)
- 使用 extractToolNames(event) 将特定工具限制给特定角色
- 在 server/types.ts 中扩展 H3EventContext 以实现类型安全的上下文
- 对于处理器之后的逻辑(日志记录、计时),显式调用 next() 并返回其结果

文档: https://mcp-toolkit.nuxt.dev/advanced/middleware

这适用于以下场景:

  • 身份验证 - 验证令牌并设置用户上下文
  • 日志记录 - 跟踪请求耗时和分析数据
  • 上下文 - 通过 event.context 将数据传递给工具
  • 速率限制 - 控制请求频率
  • 错误处理 - 使用 try/catch 包装处理器

基本用法

使用 middleware 选项将中间件添加到你的处理器中:

server/mcp/index.ts
import { defineMcpHandler } from '@nuxtjs/mcp-toolkit/server'

export default defineMcpHandler({
  middleware: async (event) => {
    // 设置工具可访问的上下文
    event.context.userId = 'user-123'
    event.context.startTime = Date.now()
  },
})
如果你不调用 next(),处理器将在你的中间件运行后自动被调用。这使得简单的用例更加直观。

简单中间件

在大多数情况下,你只需要在处理器运行之前设置上下文或验证某些内容:

server/mcp/index.ts
import { getHeader, createError } from 'h3'
import { defineMcpHandler } from '@nuxtjs/mcp-toolkit/server'

export default defineMcpHandler({
  middleware: async (event) => {
    // 验证 API 密钥
    const apiKey = getHeader(event, 'x-api-key')
    if (!apiKey) {
      throw createError({ statusCode: 401, message: 'API key required' })
    }

    // 设置用户上下文供工具访问
    event.context.apiKey = apiKey
    event.context.user = await validateApiKey(apiKey)
  },
})

随后,你的工具可以访问此上下文:

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

export default defineMcpTool({
  name: 'my-tool',
  description: 'A tool that uses middleware context',
  inputSchema: {},
  handler: async () => {
    const event = useEvent()
    const user = event.context.user
    return `Hello, ${user.name}!`
  },
})
要在你的工具中使用 useEvent(),请在 Nuxt 配置中启用 asyncContext
nuxt.config.ts
export default defineNuxtConfig({
  nitro: {
    experimental: {
      asyncContext: true,
    },
  },
})

使用 next() 的高级中间件

为了获得更精细的控制,可以显式调用 next() 以在处理器之前和之后运行代码:

server/mcp/index.ts
import { defineMcpHandler } from '@nuxtjs/mcp-toolkit/server'

export default defineMcpHandler({
  middleware: async (event, next) => {
    const startTime = Date.now()
    console.log('[MCP] Request started:', event.path)

    // 调用处理器
    const response = await next()

    // 处理器执行后的代码
    const duration = Date.now() - startTime
    console.log(`[MCP] Request completed in ${duration}ms`)

    return response
  },
})

何时使用 next()

使用场景需要 next()
在处理器之前设置上下文
在处理器之前验证身份
记录请求耗时
修改响应
捕获错误

身份验证示例

server/mcp/index.ts
import { getHeader, createError } from 'h3'
import { defineMcpHandler } from '@nuxtjs/mcp-toolkit/server'

export default defineMcpHandler({
  middleware: async (event) => {
    const authHeader = getHeader(event, 'authorization')

    if (!authHeader?.startsWith('Bearer ')) {
      throw createError({
        statusCode: 401,
        message: 'Missing or invalid authorization header',
      })
    }

    const token = authHeader.slice(7)

    try {
      const user = await verifyToken(token)
      event.context.user = user
      event.context.userId = user.id
    }
    catch {
      throw createError({
        statusCode: 401,
        message: 'Invalid token',
      })
    }
  },
})

日志记录与分析示例

server/mcp/index.ts
import { defineMcpHandler } from '@nuxtjs/mcp-toolkit/server'

export default defineMcpHandler({
  middleware: async (event, next) => {
    const requestId = crypto.randomUUID()
    const startTime = Date.now()

    event.context.requestId = requestId

    console.log(JSON.stringify({
      type: 'mcp_request_start',
      requestId,
      path: event.path,
      method: event.method,
      timestamp: new Date().toISOString(),
    }))

    const response = await next()

    console.log(JSON.stringify({
      type: 'mcp_request_end',
      requestId,
      duration: Date.now() - startTime,
      timestamp: new Date().toISOString(),
    }))

    return response
  },
})

提取工具名称

使用 extractToolNames 工具来检查当前请求中调用了哪些工具。它会解析 JSON-RPC 请求体,并返回任何 tools/call 消息中的工具名称。

server/mcp/index.ts
import { defineMcpHandler, extractToolNames } from '@nuxtjs/mcp-toolkit/server'

export default defineMcpHandler({
  middleware: async (event, next) => {
    const toolNames = await extractToolNames(event)

    if (toolNames.length > 0) {
      console.log(`[MCP] Calling tools: ${toolNames.join(', ')}`)
    }

    return next()
  },
})

这适用于以下场景:

  • 日志记录 每次请求调用了哪些工具
  • 监控 工具的使用情况和频率
  • 访问控制 基于工具名称(例如,将某些工具限制为仅管理员可用)
server/mcp/index.ts
import { createError } from 'h3'
import { defineMcpHandler, extractToolNames } from '@nuxtjs/mcp-toolkit/server'

const ADMIN_TOOLS = ['delete-user', 'reset-database']

export default defineMcpHandler({
  middleware: async (event) => {
    const toolNames = await extractToolNames(event)
    const user = event.context.user

    if (toolNames.some(name => ADMIN_TOOLS.includes(name)) && user?.role !== 'admin') {
      throw createError({ statusCode: 403, message: 'Admin access required for this tool' })
    }
  },
})
extractToolNames 在服务器上下文中是自动导入的 —— 在 server/ 目录中使用时无需手动导入。

自定义处理器的中间件

中间件在自定义处理器中的工作方式相同:

server/mcp/admin.ts
import { defineMcpHandler } from '@nuxtjs/mcp-toolkit/server'

export default defineMcpHandler({
  name: 'admin',
  middleware: async (event) => {
    const user = await getUser(event)

    if (user?.role !== 'admin') {
      throw createError({
        statusCode: 403,
        message: 'Admin access required',
      })
    }

    event.context.user = user
  },
  tools: [adminTool1, adminTool2],
})

TypeScript

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

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

现在你的中间件和工具将拥有带类型的上下文:

server/mcp/index.ts
import { defineMcpHandler } from '@nuxtjs/mcp-toolkit/server'

export default defineMcpHandler({
  middleware: async (event) => {
    event.context.user = {
      id: 'user-123',
      name: 'John',
      role: 'admin', // TypeScript 将对此进行验证
    }
  },
})

最佳实践

  1. 保持中间件专注 - 做好一件事
  2. 不需要时不要调用 next() - 让它自动调用
  3. 始终返回 next() 的结果 - 如果你调用了 next(),请返回它的结果
  4. 优雅地处理错误 - 使用 createError 处理 HTTP 错误
  5. 为上下文添加类型 - 扩展 H3EventContext 以确保类型安全

下一步