---
title: "Errors & caching"
description: "H3 and plain errors in tool handlers, and Nitro-backed response caching."
canonical_url: "https://mcp-toolkit.nuxt.dev/tools/errors-caching"
---
# 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.

### H3 Errors (Recommended)

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

```typescript [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:

```typescript
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:

```typescript [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`](https://www.npmjs.com/package/ms)) or a number in milliseconds:

```typescript [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:

```typescript [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

<table>
<thead>
  <tr>
    <th>
      Option
    </th>
    
    <th>
      Type
    </th>
    
    <th>
      Required
    </th>
    
    <th>
      Description
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        maxAge
      </code>
    </td>
    
    <td>
      <code>
        string | number
      </code>
    </td>
    
    <td>
      Yes
    </td>
    
    <td>
      Cache duration (e.g., <code>
        '1h'
      </code>
      
      , <code>
        3600000
      </code>
      
      )
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        getKey
      </code>
    </td>
    
    <td>
      <code>
        (args) => string
      </code>
    </td>
    
    <td>
      No
    </td>
    
    <td>
      Custom cache key generator
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        staleMaxAge
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      No
    </td>
    
    <td>
      Duration for stale-while-revalidate
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        swr
      </code>
    </td>
    
    <td>
      <code>
        boolean
      </code>
    </td>
    
    <td>
      No
    </td>
    
    <td>
      Enable stale-while-revalidate (defaults to <code>
        false
      </code>
      
      , see warning below)
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        name
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      No
    </td>
    
    <td>
      Cache name (auto-generated from tool name)
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        group
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      No
    </td>
    
    <td>
      Cache group (default: <code>
        'mcp'
      </code>
      
      )
    </td>
  </tr>
</tbody>
</table>

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

See the [Nitro Cache documentation](https://nitro.build/guide/cache#options) for all available options.

</callout>

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

`swr` defaults to `false` (Nitro itself defaults to `true`). With `swr: true`, stale hits return immediately and the handler refreshes in the background after the request is answered, so request-scoped writes (structured logs, traces) may be dropped. Opt in only when you accept that trade-off.

</callout>


## Sitemap

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