Advanced Topics

Extend the module with hooks

Use Nuxt and Nitro hooks to extend and customize the MCP module.

The toolkit exposes two flavours of hooks:

  • Build-time hooks on NuxtHooks — fire during nuxt build / nuxt prepare, useful to register additional definition directories.
  • Runtime hooks on NitroRuntimeHooks — fire per request, inside Nitro plugins, to mutate the resolved config or reach the SDK McpServer instance.

User listeners are best-effort: a hook that throws is logged via consola and the MCP request continues.

Build-time hooks

mcp:definitions:paths

Add additional directories to scan for tool / resource / prompt / handler definitions. Useful for sharing definitions across Nuxt layers or shipping them from a custom module.

Hook Signature

nuxt.hook('mcp:definitions:paths', (paths: {
  tools: string[]
  resources: string[]
  prompts: string[]
  handlers: string[]
}) => {
  // Mutate paths in place
})

Usage in nuxt.config.ts

nuxt.config.ts
export default defineNuxtConfig({
  modules: ['@nuxtjs/mcp-toolkit'],
  hooks: {
    'mcp:definitions:paths'(paths) {
      paths.tools.push('shared/tools')
      paths.tools.push('legacy/tools')
      paths.resources.push('shared/resources')
      paths.prompts.push('shared/prompts')
      paths.handlers.push('custom/handlers')
    },
  },
})

Usage in a Custom Module

my-module.ts
export default defineNuxtModule({
  setup(options, nuxt) {
    nuxt.hook('mcp:definitions:paths', (paths) => {
      paths.tools.push('my-module/tools')
      paths.resources.push('my-module/resources')
      paths.prompts.push('my-module/prompts')
    })
  },
})

Path Structure

{
  tools: string[]      // Directories to scan for tools
  resources: string[]  // Directories to scan for resources
  prompts: string[]    // Directories to scan for prompts
  handlers: string[]   // Directories to scan for handlers
}

All paths are relative to the server/ directory of each Nuxt layer:

  1. Relative paths like 'tools' resolve to server/tools/.
  2. Absolute paths starting with / resolve from project root.
  3. Layer-specific — each Nuxt layer resolves paths relative to its own server/ directory.

Runtime hooks

Runtime hooks fire per MCP request, from inside the Nitro server. Subscribe to them from a Nitro plugin at server/plugins/.

server/plugins/mcp.ts
export default defineNitroPlugin((nitroApp) => {
  nitroApp.hooks.hook('mcp:server:created', ({ server, event }) => {
    // ...
  })
})

The hooks run in this order:

H3 Event
   │
   ▼
defineMcpHandler middleware (if any)
   │
   ▼
resolveDynamicDefinitions  ──►  mcp:config:resolved
   │
   ▼
createMcpServer            ──►  mcp:server:created
   │
   ▼
transport.handleRequest

mcp:config:resolved

Fired after dynamic tools / resources / prompts resolvers and enabled(event) guards have run, before the per-request McpServer is built. Mutate ctx.config in place to add, remove or transform definitions for this request only.

Hook signature

import type { McpResolvedConfig } from '@nuxtjs/mcp-toolkit/server'
import type { H3Event } from 'h3'

nitroApp.hooks.hook('mcp:config:resolved', (ctx: {
  config: McpResolvedConfig
  event: H3Event
}) => {
  // Mutate ctx.config in place
})

Example: hide admin tools from anonymous clients

server/plugins/mcp-auth.ts
export default defineNitroPlugin((nitroApp) => {
  nitroApp.hooks.hook('mcp:config:resolved', ({ config, event }) => {
    if (!event.context.user) {
      config.tools = config.tools.filter(
        tool => !tool.tags?.includes('admin'),
      )
    }
  })
})

Example: rebrand the server per request

server/plugins/mcp-rebrand.ts
export default defineNitroPlugin((nitroApp) => {
  nitroApp.hooks.hook('mcp:config:resolved', ({ config, event }) => {
    const tenant = event.context.tenant?.name
    if (tenant) {
      config.name = `${tenant} MCP`
      config.instructions = `You are connected to ${tenant}'s MCP server.`
    }
  })
})

mcp:server:created

Fired after createMcpServer has registered every tool / resource / prompt and before the server is connected to the transport. Receives the SDK McpServer instance.

Use it to register definitions late or reach the underlying low-level Server via getSdkServer(ctx.server) to install custom JSON-RPC handlers.

Hook signature

import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
import type { H3Event } from 'h3'

nitroApp.hooks.hook('mcp:server:created', (ctx: {
  server: McpServer
  event: H3Event
}) => {
  // Reach into the SDK
})

Example: register a tool dynamically

server/plugins/mcp-whoami.ts
export default defineNitroPlugin((nitroApp) => {
  nitroApp.hooks.hook('mcp:server:created', ({ server, event }) => {
    server.registerTool(
      'whoami',
      { description: 'Return the current user id' },
      async () => ({
        content: [{
          type: 'text',
          text: String(event.context.userId ?? 'anonymous'),
        }],
      }),
    )
  })
})

Example: instrument the low-level SDK server

server/plugins/mcp-instrument.ts
import { getSdkServer } from '@nuxtjs/mcp-toolkit/server'

export default defineNitroPlugin((nitroApp) => {
  nitroApp.hooks.hook('mcp:server:created', ({ server }) => {
    const sdkServer = getSdkServer(server)
    sdkServer.oninitialized = () => {
      console.log('Client initialized')
    }
  })
})
Need request- or session-scoped state? Pull it from event.context inside the hook — middleware (and any auth layer that runs before the MCP handler) has already populated it.

Error handling

Listeners that throw are caught and reported via consola (tag mcp-toolkit):

[error] [mcp-toolkit] Hook "mcp:server:created" threw — request continues

The MCP request always proceeds. If you need an error to surface to the client, throw inside the corresponding tool / resource / prompt handler instead — the toolkit converts thrown errors (including H3 errors) into MCP-compliant isError results.

Next Steps

  • Middleware - Run code per MCP request, including next() interception.
  • Dynamic Definitions - Pick tools / resources / prompts dynamically from defineMcpHandler.
  • Custom Paths - Customize where definitions are scanned from.
  • Handlers - Create multiple MCP endpoints.
Copyright © 2026