进阶主题
Add middleware to MCP handlers
拦截 MCP 请求以添加身份验证、日志记录、分析等功能。
什么是中间件?
中间件允许你在 MCP 请求被处理之前(以及可选的之后)运行代码。
添加 MCP 服务器中间件和身份验证
这适用于以下场景:
- 身份验证 - 验证令牌并设置用户上下文
- 日志记录 - 跟踪请求耗时和分析数据
- 上下文 - 通过
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 } from 'h3'
import { defineMcpHandler } from '@nuxtjs/mcp-toolkit/server'
export default defineMcpHandler({
middleware: async (event) => {
const apiKey = getHeader(event, 'x-api-key')
if (!apiKey) return
const user = await validateApiKey(apiKey).catch(() => null)
if (!user) return
event.context.apiKey = apiKey
event.context.user = user
},
})
不要在 MCP 中间件中抛出
401。 许多 MCP 客户端会将 401 视为启动 OAuth 发现(查找 .well-known/oauth-* 端点)的信号。请以“软”方式设置上下文,并使用enabled 守卫或按工具检查来控制功能。完整模式请参见身份验证。你的工具随后可以访问这些上下文:
server/mcp/tools/my-tool.ts
import { useEvent } from 'h3'
import { defineMcpTool } from '@nuxtjs/mcp-toolkit/server'
export default defineMcpTool({
name: 'my-tool',
description: '使用中间件上下文的工具',
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] 请求开始:', event.path)
// 调用处理器
const response = await next()
// 处理器执行后的代码
const duration = Date.now() - startTime
console.log(`[MCP] 请求在 ${duration}ms 内完成`)
return response
},
})
何时使用 next()
| 使用场景 | 需要 next()? |
|---|---|
| 在处理器之前设置上下文 | 否 |
| 在处理器之前验证身份 | 否 |
| 记录请求耗时 | 是 |
| 修改响应 | 是 |
| 捕获错误 | 是 |
身份验证示例
当令牌有效时,将用户设置到上下文中;否则不执行任何操作。需要身份验证的工具可以检查 event.context.user,并使用enabled 守卫对匿名调用者隐藏自身:
server/mcp/index.ts
import { getHeader } from 'h3'
import { defineMcpHandler } from '@nuxtjs/mcp-toolkit/server'
export default defineMcpHandler({
middleware: async (event) => {
const authHeader = getHeader(event, 'authorization')
if (!authHeader?.startsWith('Bearer ')) return
const token = authHeader.slice(7)
const user = await verifyToken(token).catch(() => null)
if (!user) return
event.context.user = user
event.context.userId = user.id
},
})
请参阅身份验证指南获取完整演练(Better Auth API 密钥、自定义验证、客户端配置)。
日志和分析示例
对于结构化的广泛事件、汇聚器(Axiom、Sentry、OTLP、Datadog 等)以及按工具的上下文,请使用
useMcpLogger()而不是下面的手动 console.log 模式。此处的中间件示例保持最小化,因此它不依赖 evlog。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] 正在调用工具:${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: '此工具需要管理员权限' })
}
},
})
对于特定的进行中工具调用,抛出
403 是安全的——当 tools/call 到达时,客户端已经完成初始化,不会进入 OAuth 发现流程。“不抛出”规则适用于在传输层缺少/无效认证时抛出 401。extractToolNames 在服务器上下文中是自动导入的 —— 在 server/ 目录中使用时无需手动导入。自定义处理器的中间件
Middleware works the same way with custom handlers. For an admin-only endpoint mounted at a non-discovery route (e.g. /mcp/admin), throwing 403 is safe — the client targeted this route explicitly:
server/mcp/admin.ts
import { createError } from 'h3'
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: '需要管理员权限',
})
}
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 会对此进行验证
}
},
})
最佳实践
- 保持中间件专注 - 做好一件事
- 不需要时不要调用
next()- 让它自动调用 - 始终返回
next()的结果 - 如果你调用了next(),请返回它的结果 - 优雅地处理错误 - 使用
createError处理 HTTP 错误 - 为上下文添加类型 - 扩展 H3EventContext 以确保类型安全
下一步
- Handlers - 了解自定义处理器
- TypeScript - 类型安全的定义
- Tools - 创建使用中间件上下文的工具