Core Concepts

Handlers

Create custom MCP endpoints with their own tools, resources, and prompts.

What are Handlers?

Handlers allow you to create multiple MCP endpoints in a single Nuxt application. Each handler has its own route, name, version, and can include its own set of tools, resources, and prompts.

This is useful when you want to:

  • Separate different MCP functionalities into different endpoints
  • Create versioned MCP APIs
  • Organize tools/resources by domain or feature

Default Handler

By default, the module creates a single MCP endpoint at /mcp (or your configured route) that includes all tools, resources, and prompts from the server/mcp/ directory.

Custom Handlers

Create custom handlers using defineMcpHandler:

server/mcp/migration.ts
import { z } from 'zod'

const migrationTool = defineMcpTool({
  name: 'migrate-v3-to-v4',
  title: 'Migrate v3 to v4',
  description: 'Migrate code from version 3 to version 4',
  inputSchema: {
    code: z.string().describe('The code to migrate'),
  },
  handler: async ({ code }) => {
    const migrated = code.replace(/v3/g, 'v4')
    return {
      content: [{
        type: 'text',
        text: migrated,
      }],
    }
  },
})

export default defineMcpHandler({
  name: 'migration',
  version: '0.1.0',
  route: '/mcp/migration',
  tools: [migrationTool],
  browserRedirect: '/',
})

Handler Structure

A handler definition consists of:

export default defineMcpHandler({
  name: 'handler-name',  // Unique identifier
})

Handler Options

name (required)

Unique identifier for the handler. The name determines where the handler will be mounted. By default, the handler will be accessible at /mcp/:name.

export default defineMcpHandler({
  name: 'migration', // Handler mounted at /mcp/migration
})

version (optional)

Version of the handler. Defaults to the module's configured version.

export default defineMcpHandler({
  name: 'migration',
  version: '2.0.0',
})

route (optional)

Custom route for the handler. Defaults to /mcp/:name.

export default defineMcpHandler({
  name: 'migration',
  route: '/api/mcp/migration', // Custom route
})

browserRedirect (optional)

URL to redirect browsers when they access the handler endpoint. Defaults to the module's configured browserRedirect.

export default defineMcpHandler({
  name: 'migration',
  browserRedirect: '/docs/migration',
})

tools (optional)

Array of tool definitions specific to this handler.

const tool1 = defineMcpTool({ ... })
const tool2 = defineMcpTool({ ... })

export default defineMcpHandler({
  name: 'custom',
  tools: [tool1, tool2],
})

resources (optional)

Array of resource definitions specific to this handler.

const resource1 = defineMcpResource({ ... })
const resource2 = defineMcpResource({ ... })

export default defineMcpHandler({
  name: 'custom',
  resources: [resource1, resource2],
})

prompts (optional)

Array of prompt definitions specific to this handler.

const prompt1 = defineMcpPrompt({ ... })
const prompt2 = defineMcpPrompt({ ... })

export default defineMcpHandler({
  name: 'custom',
  prompts: [prompt1, prompt2],
})

Complete Example

Here's a complete example of a custom handler:

server/mcp/api-handler.ts
import { z } from 'zod'

// Define tools for this handler
const getUserTool = defineMcpTool({
  name: 'get-user',
  description: 'Get user information',
  inputSchema: {
    userId: z.string(),
  },
  handler: async ({ userId }) => {
    const user = await db.users.find(userId)
    return {
      content: [{
        type: 'text',
        text: JSON.stringify(user),
      }],
    }
  },
})

const createUserTool = defineMcpTool({
  name: 'create-user',
  description: 'Create a new user',
  inputSchema: {
    name: z.string(),
    email: z.string().email(),
  },
  handler: async ({ name, email }) => {
    const user = await db.users.create({ name, email })
    return {
      content: [{
        type: 'text',
        text: `User created: ${user.id}`,
      }],
    }
  },
})

// Define resources for this handler
const userResource = defineMcpResource({
  name: 'user',
  uri: 'api://users/{id}',
  handler: async (uri, variables) => {
    const id = variables.id as string
    const user = await db.users.find(id)
    return {
      contents: [{
        uri: uri.toString(),
        mimeType: 'application/json',
        text: JSON.stringify(user),
      }],
    }
  },
})

// Define prompts for this handler
const userPrompt = defineMcpPrompt({
  name: 'user-help',
  description: 'Get help with user operations',
  handler: async () => {
    return {
      messages: [{
        role: 'user',
        content: {
          type: 'text',
          text: 'How can I manage users?',
        },
      }],
    }
  },
})

// Export the handler
export default defineMcpHandler({
  name: 'api',
  version: '1.0.0',
  route: '/mcp/api',
  tools: [getUserTool, createUserTool],
  resources: [userResource],
  prompts: [userPrompt],
  browserRedirect: '/docs/api',
})

Multiple Handlers

You can create multiple handlers in your application:

server/
└── mcp/
    ├── migration.ts          # Migration handler
    ├── api-handler.ts        # API handler
    ├── admin-handler.ts      # Admin handler
    ├── tools/
    │   └── echo.ts           # Default handler tools
    ├── resources/
    │   └── readme.ts         # Default handler resources
    └── prompts/
        └── greeting.ts       # Default handler prompts

Each handler file should export a default handler definition:

server/mcp/migration.ts
export default defineMcpHandler({
  name: 'migration',
  tools: [ ... ],
})
server/mcp/api-handler.ts
export default defineMcpHandler({
  name: 'api',
  tools: [ ... ],
})

Handler Routes

The handler's name determines where it will be mounted. By default, handlers are accessible at /mcp/:name where :name is the handler's name:

  • Handler with name: 'migration' → mounted at /mcp/migration
  • Handler with name: 'api' → mounted at /mcp/api
  • Handler with name: 'admin' → mounted at /mcp/admin

You can also specify a custom route to override the default:

export default defineMcpHandler({
  name: 'api',
  route: '/api/mcp/v1', // Custom route instead of /mcp/api
})

Default vs Custom Handlers

FeatureDefault HandlerCustom Handler
Route/mcp (configurable)/mcp/:name (or custom route)
ToolsFrom server/mcp/tools/Defined in handler
ResourcesFrom server/mcp/resources/Defined in handler
PromptsFrom server/mcp/prompts/Defined in handler
NameFrom configHandler name
VersionFrom configHandler version

Use Cases

1. Feature Separation

Separate different features into different handlers:

server/mcp/user-management.ts
export default defineMcpHandler({
  name: 'users',
  tools: [getUserTool, createUserTool, updateUserTool],
})
server/mcp/content-management.ts
export default defineMcpHandler({
  name: 'content',
  tools: [createPostTool, updatePostTool, deletePostTool],
})

2. Versioned APIs

Create versioned handlers:

server/mcp/api-v1.ts
export default defineMcpHandler({
  name: 'api-v1',
  version: '1.0.0',
  route: '/api/v1/mcp',
  tools: [ ... ],
})
server/mcp/api-v2.ts
export default defineMcpHandler({
  name: 'api-v2',
  version: '2.0.0',
  route: '/api/v2/mcp',
  tools: [ ... ],
})

3. Domain-Specific Handlers

Organize by domain:

server/mcp/ecommerce.ts
export default defineMcpHandler({
  name: 'ecommerce',
  tools: [addToCartTool, checkoutTool, getProductsTool],
})
server/mcp/analytics.ts
export default defineMcpHandler({
  name: 'analytics',
  tools: [getStatsTool, generateReportTool],
})

Sharing Tools Between Handlers

You can share tool definitions between handlers by exporting them from a separate file:

server/mcp/shared-tools.ts
import { z } from 'zod'

export const sharedTool = defineMcpTool({
  name: 'shared-tool',
  description: 'A shared tool',
  inputSchema: {
    input: z.string(),
  },
  handler: async ({ input }) => {
    return {
      content: [{
        type: 'text',
        text: `Shared: ${input}`,
      }],
    }
  },
})
server/mcp/handler1.ts
import { sharedTool } from './shared-tools'

export default defineMcpHandler({
  name: 'handler1',
  tools: [sharedTool],
})
server/mcp/handler2.ts
import { sharedTool } from './shared-tools'

export default defineMcpHandler({
  name: 'handler2',
  tools: [sharedTool],
})

File Organization

Organize handlers in your server/mcp/ directory:

server/
└── mcp/
    ├── migration.ts          # Custom handler
    ├── api-handler.ts        # Custom handler
    ├── admin.ts              # Custom handler
    ├── shared-tools.ts       # Shared tool definitions
    ├── tools/
    │   └── # Tools for default handler
    ├── resources/
    │   └── # Resources for default handler
    └── prompts/
        └── # Prompts for default handler

Best Practices

  1. Use descriptive names: Make handler names clear and specific
  2. Group related functionality: Put related tools/resources together
  3. Version your handlers: Use semantic versioning for handler versions
  4. Document your handlers: Add comments explaining what each handler does
  5. Keep handlers focused: Each handler should have a clear, single purpose

Next Steps