---
title: "Schema, handler & returns"
description: "Define tool inputs and outputs with Zod, and return text, structured data, or media from handlers."
canonical_url: "https://mcp-toolkit.nuxt.dev/tools/schema-handler"
---
# Schema, handler & returns

> Define tool inputs and outputs with Zod, and return text, structured data, or media from handlers.

## Input Schema

The `inputSchema` is optional and uses Zod to define and validate input parameters. When provided, each field must be a Zod schema. Tools without parameters can omit `inputSchema` entirely:

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

export default defineMcpTool({
  name: 'echo',
  description: 'Echo back a message',
  handler: async () => 'Echo: test',
})
```

For tools with parameters, define them using Zod schemas:

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

export default defineMcpTool({
  name: 'calculator',
  inputSchema: {
    // String input
    operation: z.string().describe('Operation to perform'),

    // Number input
    a: z.number().describe('First number'),
    b: z.number().describe('Second number'),

    // Optional field
    precision: z.number().optional().describe('Decimal precision'),

    // Enum input
    format: z.enum(['decimal', 'fraction']).describe('Output format'),

    // Array input
    numbers: z.array(z.number()).describe('List of numbers'),
  },
  handler: async ({ operation, a, b, precision, format, numbers }) => {
    // Handler implementation
  },
})
```

### Common Zod Types

<table>
<thead>
  <tr>
    <th>
      Zod Type
    </th>
    
    <th>
      Example
    </th>
    
    <th>
      Description
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        z.string()
      </code>
    </td>
    
    <td>
      <code>
        z.string().min(1).max(100)
      </code>
    </td>
    
    <td>
      String with validation
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        z.number()
      </code>
    </td>
    
    <td>
      <code>
        z.number().min(0).max(100)
      </code>
    </td>
    
    <td>
      Number with validation
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        z.boolean()
      </code>
    </td>
    
    <td>
      <code>
        z.boolean()
      </code>
    </td>
    
    <td>
      Boolean value
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        z.array()
      </code>
    </td>
    
    <td>
      <code>
        z.array(z.string())
      </code>
    </td>
    
    <td>
      Array of values
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        z.object()
      </code>
    </td>
    
    <td>
      <code>
        z.object({ ... })
      </code>
    </td>
    
    <td>
      Nested object
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        z.enum()
      </code>
    </td>
    
    <td>
      <code>
        z.enum(['a', 'b'])
      </code>
    </td>
    
    <td>
      Enumeration
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        z.optional()
      </code>
    </td>
    
    <td>
      <code>
        z.string().optional()
      </code>
    </td>
    
    <td>
      Optional field
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        z.default()
      </code>
    </td>
    
    <td>
      <code>
        z.string().default('value')
      </code>
    </td>
    
    <td>
      Field with default
    </td>
  </tr>
</tbody>
</table>

## Output Schema

Define structured output using `outputSchema`:

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

export default defineMcpTool({
  name: 'calculate-bmi',
  description: 'Calculate Body Mass Index',
  inputSchema: {
    weightKg: z.number().describe('Weight in kilograms'),
    heightM: z.number().describe('Height in meters'),
  },
  outputSchema: {
    bmi: z.number(),
    category: z.string(),
  },
  handler: async ({ weightKg, heightM }) => {
    const bmi = weightKg / (heightM * heightM)
    let category = 'Normal'
    if (bmi < 18.5) category = 'Underweight'
    else if (bmi >= 25) category = 'Overweight'
    else if (bmi >= 30) category = 'Obese'

    return {
      structuredContent: {
        bmi: Math.round(bmi * 100) / 100,
        category,
      },
    }
  },
})
```

The `structuredContent` field provides structured data that matches your `outputSchema`, making it easier for AI assistants to work with the results.

## Handler Function

The handler is an async function that receives validated input and returns results. You can return simplified values directly — they are automatically wrapped into the MCP `CallToolResult` format.

### Simplified Returns

Return a `string`, `number`, `boolean`, object, or array directly from your handler:

<code-group>

```typescript [String]
handler: async ({ name }) => `Hello ${name}`
// → { content: [{ type: 'text', text: 'Hello World' }] }
```

```typescript [Number]
handler: async ({ a, b }) => a + b
// → { content: [{ type: 'text', text: '10' }] }
```

```typescript [Object / Array]
handler: async ({ id }) => {
  const user = await getUser(id)
  return user
}
// → { content: [{ type: 'text', text: '{ "id": ... }' }] }
```

```typescript [Boolean]
handler: async ({ id }) => await exists(id)
// → { content: [{ type: 'text', text: 'true' }] }
```

</code-group>

You can also return the full `CallToolResult` format when you need more control (e.g., images, multiple content items, `structuredContent`).

### Content Types

For advanced use cases, return a full `CallToolResult` with typed content:

<code-group>

```typescript [Image Content]
return {
  content: [{
    type: 'image',
    data: base64ImageData,
    mimeType: 'image/png',
  }],
}
```

```typescript [Structured Content]
return {
  structuredContent: {
    bmi: 25.5,
    category: 'Normal',
  },
}
// text content is auto-generated as fallback for older clients
```

```typescript [Resource Reference]
return {
  content: [{
    type: 'resource',
    resource: {
      uri: 'file:///path/to/file',
      text: 'File content',
      mimeType: 'text/plain',
    },
  }],
}
```

</code-group>

### Result Helpers

The module provides `imageResult` and `audioResult` helpers for binary media in tool responses (base64-encoded data plus MIME type):

```typescript
import { z } from 'zod'
import { defineMcpTool, imageResult, audioResult } from '@nuxtjs/mcp-toolkit/server'

export default defineMcpTool({
  description: 'Generate chart',
  inputSchema: { data: z.array(z.number()) },
  handler: async ({ data }) => {
    const base64 = await generateChart(data)
    return imageResult(base64, 'image/png')
  },
})
```

```typescript
import { z } from 'zod'
import { defineMcpTool, audioResult } from '@nuxtjs/mcp-toolkit/server'

export default defineMcpTool({
  description: 'Text to speech',
  inputSchema: { text: z.string() },
  handler: async ({ text }) => {
    const base64 = await synthesizeSpeech(text)
    return audioResult(base64, 'audio/mp3')
  },
})
```

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

The `textResult`, `jsonResult`, and `errorResult` helpers are deprecated. Return values directly from your handler instead, and throw errors for error cases (see [Errors & caching](https://mcp-toolkit.nuxt.dev/tools/errors-caching)).

</callout>


## Sitemap

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