---
title: "Dynamic Definitions"
description: "Conditionally register tools, resources, and prompts based on authentication, roles, or request context."
canonical_url: "https://mcp-toolkit.nuxt.dev/advanced/dynamic-definitions"
---
# Dynamic Definitions

> Conditionally register tools, resources, and prompts based on authentication, roles, or request context.

## Overview

By default, every tool, resource, and prompt defined in `server/mcp/` is registered for all clients. Dynamic definitions let you control **which definitions are visible** based on request context — for example, showing admin-only tools to authenticated admins while hiding them from regular users.

There are two complementary mechanisms:

1. **enabled guard** — A per-definition callback that controls visibility
2. **Dynamic handler definitions** — A function in `defineMcpHandler` that returns definitions based on context

Both mechanisms run **after middleware**, so `event.context` (e.g. authentication data) is available.

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

If you only need to change tool/prompt **behavior** (not visibility), you can already do that by reading `event.context` inside your handler via `useEvent()`. Dynamic definitions are for controlling which definitions appear in `tools/list`, `prompts/list`, and `resources/list`.

</callout>

## The `enabled` Guard

Add an `enabled` callback to any tool, resource, or prompt definition. When the callback returns `false`, the definition is hidden from the client.

### Tools

```typescript [server/mcp/tools/delete-all.ts]
import { z } from 'zod'
import { defineMcpTool } from '@nuxtjs/mcp-toolkit/server'

export default defineMcpTool({
  name: 'delete-all',
  description: 'Delete all records (admin only)',
  inputSchema: {
    confirm: z.boolean().describe('Confirm deletion'),
  },
  enabled: event => event.context.user?.role === 'admin',
  handler: async ({ confirm }) => {
    if (!confirm) return 'Deletion cancelled'
    await deleteAllRecords()
    return 'All records deleted'
  },
})
```

### Resources

```typescript [server/mcp/resources/internal-logs.ts]
import { defineMcpResource } from '@nuxtjs/mcp-toolkit/server'

export default defineMcpResource({
  name: 'internal-logs',
  description: 'Application logs (admin only)',
  uri: 'app://logs',
  enabled: event => event.context.user?.role === 'admin',
  handler: async (uri) => ({
    contents: [{ uri: uri.toString(), text: await readLogs() }],
  }),
})
```

### Prompts

```typescript [server/mcp/prompts/onboarding.ts]
import { useEvent } from 'h3'
import { defineMcpPrompt } from '@nuxtjs/mcp-toolkit/server'

export default defineMcpPrompt({
  name: 'onboarding',
  description: 'Personalized onboarding (authenticated users only)',
  enabled: event => !!event.context.user,
  handler: async () => {
    const event = useEvent()
    return {
      messages: [{
        role: 'user',
        content: {
          type: 'text',
          text: `Welcome ${event.context.user.name}! Here's how to get started...`,
        },
      }],
    }
  },
})
```

### Middleware Setup

The `enabled` guard runs after middleware, so set up your auth context in middleware:

```typescript [server/mcp/index.ts]
import { getHeader } from 'h3'
import { defineMcpHandler } from '@nuxtjs/mcp-toolkit/server'

export default defineMcpHandler({
  middleware: async (event) => {
    const token = getHeader(event, 'authorization')?.replace('Bearer ', '')
    if (token) {
      event.context.user = await verifyToken(token)
    }
  },
})
```

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

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

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

</callout>

## Dynamic Handler Definitions

For more control, pass a **function** as `tools`, `resources`, or `prompts` in `defineMcpHandler`. The function receives the H3 event and returns an array of definitions.

```typescript [server/mcp/index.ts]
import { defineMcpHandler } from '@nuxtjs/mcp-toolkit/server'
import { adminTools } from './admin-tools'
import { publicTools } from './public-tools'

export default defineMcpHandler({
  middleware: async (event) => {
    event.context.user = await getUser(event)
  },
  tools: async (event) => {
    const base = [...publicTools]
    if (event.context.user?.role === 'admin') {
      base.push(...adminTools)
    }
    return base
  },
  prompts: async (event) => {
    if (event.context.user) {
      return [authenticatedPrompt, dashboardPrompt]
    }
    return [guestPrompt]
  },
})
```

This is useful when you need to:

- Build the tool list programmatically from a database or config
- Compose definitions from multiple modules
- Apply complex filtering logic

## Combining Both Approaches

The `enabled` guard and dynamic handler definitions work together. When you use dynamic handler definitions, each returned definition's `enabled` guard is still evaluated:

```typescript [server/mcp/index.ts]
import { defineMcpHandler } from '@nuxtjs/mcp-toolkit/server'

export default defineMcpHandler({
  middleware: async (event) => {
    event.context.user = await getUser(event)
  },
  tools: async (event) => {
    const allTools = await loadToolsFromConfig()
    return allTools
  },
})
```

```typescript [server/mcp/tools/admin-delete.ts]
import { defineMcpTool } from '@nuxtjs/mcp-toolkit/server'

export default defineMcpTool({
  name: 'admin-delete',
  enabled: event => event.context.user?.role === 'admin',
  handler: async () => { /* ... */ },
})
```

In this case, `admin-delete` is loaded by auto-discovery (or dynamically) **and** filtered by its `enabled` guard.

## Session Behavior

When [sessions](https://mcp-toolkit.nuxt.dev/advanced/sessions) are enabled, the MCP server is created on the **first request** of a session. Dynamic definitions are resolved at that point, and the same tool set persists for the session's lifetime.

This means:

- An admin who connects gets admin tools for the entire session
- A regular user who connects never sees admin tools, even if they gain admin access mid-session
- Different sessions can have different tool sets

Without sessions, a new server is created per request, so definitions can vary per request.

## Mid-Session Mutations

The `enabled` guard and dynamic handler definitions control which definitions are registered **when the session starts**. For cases where you need to add, remove, or update definitions **during** an active session, use the `useMcpServer()` composable.

`useMcpServer()` returns a helper with methods to register, remove, and manage tools, prompts, and resources. The SDK automatically sends `notifications/tools/list_changed` (or the equivalent for prompts/resources) to the client, prompting it to re-fetch the list.

### API

<table>
<thead>
  <tr>
    <th>
      Method
    </th>
    
    <th>
      Description
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        registerTool(name, config, handler)
      </code>
    </td>
    
    <td>
      Register a new tool. Returns a <code>
        RegisteredTool
      </code>
      
       handle.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        registerPrompt(name, config, handler)
      </code>
    </td>
    
    <td>
      Register a new prompt. Returns a <code>
        RegisteredPrompt
      </code>
      
       handle.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        registerResource(name, uri, config, handler)
      </code>
    </td>
    
    <td>
      Register a new resource. Returns a <code>
        RegisteredResource
      </code>
      
       handle.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        removeTool(name)
      </code>
    </td>
    
    <td>
      Remove a tool by name. Returns <code>
        true
      </code>
      
       if found.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        removePrompt(name)
      </code>
    </td>
    
    <td>
      Remove a prompt by name. Returns <code>
        true
      </code>
      
       if found.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        removeResource(name)
      </code>
    </td>
    
    <td>
      Remove a resource by name. Returns <code>
        true
      </code>
      
       if found.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        server
      </code>
    </td>
    
    <td>
      The underlying <code>
        McpServer
      </code>
      
       instance for advanced SDK operations.
    </td>
  </tr>
</tbody>
</table>

### Registering a Tool Mid-Session

```typescript [server/mcp/tools/create-shortcut.ts]
import { z } from 'zod'
import { defineMcpTool } from '@nuxtjs/mcp-toolkit/server'

export default defineMcpTool({
  description: 'Create a shortcut tool for a specific query',
  inputSchema: {
    name: z.string().describe('Name for the shortcut tool'),
    query: z.string().describe('The query this shortcut runs'),
  },
  handler: async ({ name, query }) => {
    const mcp = useMcpServer()
    mcp.registerTool(name, {
      description: `Shortcut: ${query}`,
    }, async () => {
      const result = await runQuery(query)
      return { content: [{ type: 'text', text: JSON.stringify(result) }] }
    })
    return `Shortcut "${name}" created`
  },
})
```

After calling this tool, the client's tool list refreshes and includes the new shortcut.

### Removing a Tool Mid-Session

Use `removeTool(name)` to remove a tool by name -- no need to store handles:

```typescript [server/mcp/tools/manage-agents.ts]
import { z } from 'zod'
import { createError } from 'h3'
import { defineMcpTool } from '@nuxtjs/mcp-toolkit/server'

export const registerAgent = defineMcpTool({
  name: 'register-agent',
  inputSchema: {
    agentId: z.string(),
    agentName: z.string(),
  },
  handler: async ({ agentId, agentName }) => {
    const mcp = useMcpServer()
    mcp.registerTool(`call-${agentId}`, {
      description: `Call agent: ${agentName}`,
    }, async () => {
      const response = await callAgent(agentId)
      return { content: [{ type: 'text', text: response }] }
    })
    return `Agent "${agentName}" registered`
  },
})

export const unregisterAgent = defineMcpTool({
  name: 'unregister-agent',
  inputSchema: {
    agentId: z.string(),
  },
  handler: async ({ agentId }) => {
    const mcp = useMcpServer()
    const removed = mcp.removeTool(`call-${agentId}`)
    if (!removed) throw createError({ statusCode: 404, message: `Agent "${agentId}" not found` })
    return `Agent "${agentId}" unregistered`
  },
})
```

### Updating and Toggling Definitions

For in-place updates, use the `RegisteredTool` handle returned by `registerTool()`:

```typescript
const mcp = useMcpServer()
const registered = mcp.registerTool('my-tool', { description: 'v1' }, handler)

// Update the description and handler
registered.update({ description: 'v2', callback: newHandler })

// Temporarily hide from the client
registered.disable()

// Re-enable
registered.enable()
```

The same pattern applies to `registerPrompt()` and `registerResource()`, which return handles with the same methods.

### Requirements

- **nitro.experimental.asyncContext** must be `true` in your Nuxt config (required for `useMcpServer()` to access the request context)
- Mid-session mutations are most useful with **sessions enabled**, since the server instance persists across requests. Without sessions, each request creates a fresh server, so mutations only last for a single request.

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

Not all MCP clients support `notifications/tools/list_changed`. If a client doesn't support it, the user may need to reconnect to see updated definitions. Check your client's documentation for compatibility.

</callout>

## TypeScript

For type-safe context, extend the H3 event context:

```typescript [server/types.ts]
declare module 'h3' {
  interface H3EventContext {
    user?: {
      id: string
      name: string
      role: 'user' | 'admin'
    }
  }
}
```

## Next Steps

- [Middleware](https://mcp-toolkit.nuxt.dev/advanced/middleware) — Set up authentication context
- [Sessions](https://mcp-toolkit.nuxt.dev/advanced/sessions) — Enable per-session state
- [Handlers](https://mcp-toolkit.nuxt.dev/handlers/overview) — Create custom MCP endpoints


## Sitemap

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