---
title: "Add middleware to MCP handlers"
description: "Intercept MCP requests to add authentication, logging, analytics, and more."
canonical_url: "https://mcp-toolkit.nuxt.dev/advanced/middleware"
---
# Add middleware to MCP handlers

> 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 :actions="["copy","cursor","windsurf"]" description="Add MCP server middleware and auth" icon="i-lucide-shield">

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](https://mcp-toolkit.nuxt.dev/advanced/middleware)

</prompt>

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:

```typescript [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()
  },
})
```

<callout color="primary" icon="i-lucide-lightbulb">

If you don't call `next()`, the handler is called **automatically** after your middleware runs. This makes simple use cases straightforward.

</callout>

## Simple Middleware

For most cases, you just need to set context before the handler runs. Use a **soft** approach — set context only when authentication succeeds, and let unauthenticated requests continue:

```typescript [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
  },
})
```

<callout color="warning" icon="i-lucide-triangle-alert">

**Do not throw 401 from MCP middleware.** Many MCP clients treat a 401 as a signal to start OAuth discovery (looking for `.well-known/oauth-*` endpoints). Set context softly and use [`enabled` guards](https://mcp-toolkit.nuxt.dev/advanced/dynamic-definitions) or per-tool checks to gate functionality. See [Authentication](https://mcp-toolkit.nuxt.dev/examples/authentication) for the full pattern.

</callout>

Your tools can then access this context:

```typescript [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}!`
  },
})
```

<callout color="info" icon="i-lucide-info">

To use `useEvent()` in your tools, enable `asyncContext` in your Nuxt config:

```typescript [nuxt.config.ts]
export default defineNuxtConfig({
  nitro: {
    experimental: {
      asyncContext: true,
    },
  },
})
```

</callout>

## Advanced Middleware with `next()`

For more control, call `next()` explicitly to run code **before and after** the handler:

```typescript [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()`

<table>
<thead>
  <tr>
    <th>
      Use Case
    </th>
    
    <th>
      Need <code>
        next()
      </code>
      
      ?
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      Set context before handler
    </td>
    
    <td>
      No
    </td>
  </tr>
  
  <tr>
    <td>
      Validate auth before handler
    </td>
    
    <td>
      No
    </td>
  </tr>
  
  <tr>
    <td>
      Log request timing
    </td>
    
    <td>
      Yes
    </td>
  </tr>
  
  <tr>
    <td>
      Modify response
    </td>
    
    <td>
      Yes
    </td>
  </tr>
  
  <tr>
    <td>
      Catch errors
    </td>
    
    <td>
      Yes
    </td>
  </tr>
</tbody>
</table>

## Authentication Example

Set the user on context when the token is valid; do nothing otherwise. Tools that require auth can check `event.context.user` and use [`enabled` guards](https://mcp-toolkit.nuxt.dev/advanced/dynamic-definitions) to hide themselves from anonymous callers:

```typescript [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
  },
})
```

See the [Authentication guide](https://mcp-toolkit.nuxt.dev/examples/authentication) for a complete walkthrough (Better Auth API keys, custom validation, configuring clients).

## Logging & Analytics Example

<callout color="primary" icon="i-lucide-arrow-right" to="https://mcp-toolkit.nuxt.dev/advanced/logging">

For structured wide events, drains (Axiom, Sentry, OTLP, Datadog…), and per-tool context, use [`useMcpLogger()`](https://mcp-toolkit.nuxt.dev/advanced/logging) instead of the manual `console.log` pattern below. The middleware example here is kept minimal so it doesn't depend on `evlog`.

</callout>

```typescript [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.

```typescript [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)

```typescript [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' })
    }
  },
})
```

<callout color="info" icon="i-lucide-info">

Throwing `403` for a specific in-flight tool call is safe — by the time `tools/call` arrives, the client has already initialized and won't enter OAuth discovery. The "no throw" rule applies to **401 on missing/invalid auth at the transport level**.

</callout>

<callout color="primary" icon="i-lucide-lightbulb">

`extractToolNames` is auto-imported in the server context — no import needed when using it in your `server/` directory.

</callout>

## Middleware with Custom Handlers

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:

```typescript [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: 'Admin access required',
      })
    }

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

## TypeScript

For type-safe context, extend the H3 context:

```typescript [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:

```typescript [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

1. **Keep middleware focused** - Do one thing well
2. **Don't call next() if you don't need it** - Let it be called automatically
3. **Always return next() result** - If you call `next()`, return its result
4. **Handle errors gracefully** - Use `createError` for HTTP errors
5. **Type your context** - Extend H3EventContext for type safety

## Next Steps

- [Handlers](https://mcp-toolkit.nuxt.dev/handlers/overview) - Learn about custom handlers
- [TypeScript](https://mcp-toolkit.nuxt.dev/advanced/typescript) - Type-safe definitions
- [Tools](https://mcp-toolkit.nuxt.dev/tools/overview) - Create tools that use middleware context


## Sitemap

See the full [sitemap](https://mcp-toolkit.nuxt.dev/sitemap.md) for all pages.
