进阶主题
中间件
拦截 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 将对此进行验证
}
},
})
最佳实践
- 保持中间件专注 - 做好一件事
- 不需要时不要调用
next()- 让它自动调用 - 始终返回
next()的结果 - 如果你调用了next(),请返回它的结果 - 优雅地处理错误 - 使用
createError处理 HTTP 错误 - 为上下文添加类型 - 扩展 H3EventContext 以确保类型安全
下一步
- 处理器 - 了解自定义处理器
- TypeScript - 类型安全的定义
- 工具 - 创建使用中间件上下文的工具