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:
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.
Create custom handlers using defineMcpHandler:
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: '/',
})
A handler definition consists of:
export default defineMcpHandler({
name: 'handler-name', // Unique identifier
})
export default defineMcpHandler({
name: 'handler-name',
version: '1.0.0', // Handler version
route: '/mcp/custom', // Custom route
browserRedirect: '/', // Browser redirect URL
tools: [ ... ], // Array of tools
resources: [ ... ], // Array of resources
prompts: [ ... ], // Array of prompts
})
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],
})
Here's a complete example of a custom handler:
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',
})
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:
export default defineMcpHandler({
name: 'migration',
tools: [ ... ],
})
export default defineMcpHandler({
name: 'api',
tools: [ ... ],
})
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:
name: 'migration' → mounted at /mcp/migrationname: 'api' → mounted at /mcp/apiname: 'admin' → mounted at /mcp/adminYou 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
})
| Feature | Default Handler | Custom Handler |
|---|---|---|
| Route | /mcp (configurable) | /mcp/:name (or custom route) |
| Tools | From server/mcp/tools/ | Defined in handler |
| Resources | From server/mcp/resources/ | Defined in handler |
| Prompts | From server/mcp/prompts/ | Defined in handler |
| Name | From config | Handler name |
| Version | From config | Handler version |
Separate different features into different handlers:
export default defineMcpHandler({
name: 'users',
tools: [getUserTool, createUserTool, updateUserTool],
})
export default defineMcpHandler({
name: 'content',
tools: [createPostTool, updatePostTool, deletePostTool],
})
Create versioned handlers:
export default defineMcpHandler({
name: 'api-v1',
version: '1.0.0',
route: '/api/v1/mcp',
tools: [ ... ],
})
export default defineMcpHandler({
name: 'api-v2',
version: '2.0.0',
route: '/api/v2/mcp',
tools: [ ... ],
})
Organize by domain:
export default defineMcpHandler({
name: 'ecommerce',
tools: [addToCartTool, checkoutTool, getProductsTool],
})
export default defineMcpHandler({
name: 'analytics',
tools: [getStatsTool, generateReportTool],
})
You can share tool definitions between handlers by exporting them from a separate file:
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}`,
}],
}
},
})
import { sharedTool } from './shared-tools'
export default defineMcpHandler({
name: 'handler1',
tools: [sharedTool],
})
import { sharedTool } from './shared-tools'
export default defineMcpHandler({
name: 'handler2',
tools: [sharedTool],
})
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