Tools

Errors & caching

H3 and plain errors in tool handlers, and Nitro-backed response caching.

Error Handling

Throw errors directly from your handlers — just like in Nitro event handlers. Thrown errors are automatically caught and converted into MCP-compliant isError results.

Use createError() from H3 for errors with status codes:

server/mcp/tools/get-user.ts
import { z } from 'zod'
import { createError } from 'h3'
import { defineMcpTool } from '@nuxtjs/mcp-toolkit/server'

export default defineMcpTool({
  name: 'get-user',
  description: 'Get a user by ID',
  inputSchema: {
    id: z.string(),
  },
  handler: async ({ id }) => {
    const user = await findUser(id)
    if (!user) {
      throw createError({ statusCode: 404, message: 'User not found' })
    }
    return user
  },
})
// Error result: { isError: true, content: [{ type: 'text', text: '[404] User not found' }] }

H3 errors can also include structured data:

throw createError({
  statusCode: 400,
  message: 'Validation failed',
  data: { fields: ['name', 'email'] },
})
// Error text: '[400] Validation failed\n{ "fields": ["name", "email"] }'

Plain Errors

Regular Error instances work too:

server/mcp/tools/safe-divide.ts
import { z } from 'zod'
import { defineMcpTool } from '@nuxtjs/mcp-toolkit/server'

export default defineMcpTool({
  name: 'safe-divide',
  inputSchema: {
    a: z.number(),
    b: z.number(),
  },
  handler: async ({ a, b }) => {
    if (b === 0) throw new Error('Division by zero')
    return a / b
  },
})

Response Caching

You can cache tool responses using Nitro's caching system. The cache option accepts three formats:

Simple Duration

Use a string duration (parsed by ms) or a number in milliseconds:

server/mcp/tools/cached-data.ts
import { z } from 'zod'
import { defineMcpTool } from '@nuxtjs/mcp-toolkit/server'

export default defineMcpTool({
  description: 'Fetch data with 1 hour cache',
  inputSchema: {
    id: z.string(),
  },
  cache: '1h', // or '30m', '2 days', 3600000, etc.
  handler: async ({ id }) => {
    return await fetchExpensiveData(id)
  },
})

Full Cache Options

For more control, use an object with all Nitro cache options:

server/mcp/tools/cached-pages.ts
import { z } from 'zod'
import { defineMcpTool } from '@nuxtjs/mcp-toolkit/server'

export default defineMcpTool({
  description: 'Get page with custom cache key',
  inputSchema: {
    path: z.string(),
  },
  cache: {
    maxAge: '1h',
    getKey: args => `page-${args.path}`,
    swr: true, // stale-while-revalidate
  },
  handler: async ({ path }) => {
    // ...
  },
})

Cache Options Reference

OptionTypeRequiredDescription
maxAgestring | numberYesCache duration (e.g., '1h', 3600000)
getKey(args) => stringNoCustom cache key generator
staleMaxAgenumberNoDuration for stale-while-revalidate
swrbooleanNoEnable stale-while-revalidate
namestringNoCache name (auto-generated from tool name)
groupstringNoCache group (default: 'mcp')
See the Nitro Cache documentation for all available options.
Copyright © 2026