Advanced Topics
Middleware
Intercept MCP requests to add authentication, logging, analytics, and more.
What is Middleware?
Middleware allows you to run code before (and optionally after) MCP requests are processed.
Prompt
Add authentication middleware to my Nuxt MCP server (@nuxtjs/mcp-toolkit).
- Create or edit server/mcp/index.ts with defineMcpHandler and a middleware function
- In the middleware, validate the auth header (e.g. Bearer token or API key from getHeader(event, 'authorization'))
- Set the authenticated user on event.context.user for tools to access
- Do NOT throw 401 errors for missing auth — use a "soft" approach that sets context when auth succeeds
- Access user context in tools via useEvent().context.user (requires nitro.experimental.asyncContext: true)
- Use extractToolNames(event) to restrict specific tools to certain roles
- Extend H3EventContext in server/types.ts for type-safe context
- For post-handler logic (logging, timing), call next() explicitly and return its result
Docs: https://mcp-toolkit.nuxt.dev/advanced/middleware
This is useful for:
- Authentication - Validate tokens and set user context
- Logging - Track request timing and analytics
- Context - Pass data to your tools via
event.context - Rate limiting - Control request frequency
- Error handling - Wrap handlers with try/catch
Basic Usage
Add middleware to your handler using the middleware option:
server/mcp/index.ts
import { defineMcpHandler } from '@nuxtjs/mcp-toolkit/server'
export default defineMcpHandler({
middleware: async (event) => {
// Set context that tools can access
event.context.userId = 'user-123'
event.context.startTime = Date.now()
},
})
If you don't call
next(), the handler is called automatically after your middleware runs. This makes simple use cases straightforward.Simple Middleware
For most cases, you just need to set context or validate something before the handler runs:
server/mcp/index.ts
import { getHeader, createError } from 'h3'
import { defineMcpHandler } from '@nuxtjs/mcp-toolkit/server'
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:
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}!`
},
})
To use
useEvent() in your tools, enable asyncContext in your Nuxt config:nuxt.config.ts
export default defineNuxtConfig({
nitro: {
experimental: {
asyncContext: true,
},
},
})
Advanced Middleware with next()
For more control, call next() explicitly to run code before and after the handler:
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)
// 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
},
})
When to use 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 |
Authentication Example
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',
})
}
},
})
Logging & Analytics Example
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
},
})
Extracting Tool Names
Use the extractToolNames utility to inspect which tools are being called in the current request. It parses the JSON-RPC body and returns the tool names from any tools/call messages.
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()
},
})
This is useful for:
- Logging which tools are called per request
- Monitoring tool usage and frequency
- Access control based on tool names (e.g. restricting certain tools to admin users)
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 is auto-imported in the server context — no import needed when using it in your server/ directory.Middleware with Custom Handlers
Middleware works the same way with custom handlers:
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
For type-safe context, extend the H3 context:
server/types.ts
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:
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 will validate this
}
},
})
Best Practices
- Keep middleware focused - Do one thing well
- Don't call
next()if you don't need it - Let it be called automatically - Always return
next()result - If you callnext(), return its result - Handle errors gracefully - Use
createErrorfor HTTP errors - Type your context - Extend H3EventContext for type safety
Next Steps
- Handlers - Learn about custom handlers
- TypeScript - Type-safe definitions
- Tools - Create tools that use middleware context