Middleware allows you to run code before (and optionally after) MCP requests are processed. This is useful for:
event.contextAdd middleware to your handler using the middleware option:
export default defineMcpHandler({
middleware: async (event) => {
// Set context that tools can access
event.context.userId = 'user-123'
event.context.startTime = Date.now()
},
})
next(), the handler is called automatically after your middleware runs. This makes simple use cases straightforward.For most cases, you just need to set context or validate something before the handler runs:
export default defineMcpHandler({
middleware: async (event) => {
// Validate API key
const apiKey = getHeader(event, 'x-api-key')
if (!apiKey) {
throw createError({ statusCode: 401, message: 'API key required' })
}
// Set user context for tools to access
event.context.apiKey = apiKey
event.context.user = await validateApiKey(apiKey)
},
})
Your tools can then access this context:
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 {
content: [{
type: 'text',
text: `Hello, ${user.name}!`,
}],
}
},
})
useEvent() in your tools, enable asyncContext in your Nuxt config:export default defineNuxtConfig({
nitro: {
experimental: {
asyncContext: true,
},
},
})
next()For more control, call next() explicitly to run code before and after the handler:
export default defineMcpHandler({
middleware: async (event, next) => {
const startTime = Date.now()
console.log('[MCP] Request started:', event.path)
// Call the handler
const response = await next()
// Code after the handler
const duration = Date.now() - startTime
console.log(`[MCP] Request completed in ${duration}ms`)
return response
},
})
next()| Use Case | Need next()? |
|---|---|
| Set context before handler | No |
| Validate auth before handler | No |
| Log request timing | Yes |
| Modify response | Yes |
| Catch errors | Yes |
import { getHeader, createError } from 'h3'
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',
})
}
},
})
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
},
})
Middleware works the same way with custom handlers:
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],
})
For type-safe context, extend the H3 context:
declare module 'h3' {
interface H3EventContext {
user?: {
id: string
name: string
role: 'user' | 'admin'
}
requestId?: string
startTime?: number
}
}
Now your middleware and tools will have typed context:
export default defineMcpHandler({
middleware: async (event) => {
event.context.user = {
id: 'user-123',
name: 'John',
role: 'admin', // TypeScript will validate this
}
},
})
next() if you don't need it - Let it be called automaticallynext() result - If you call next(), return its resultcreateError for HTTP errors