Tools
What are Tools?
Tools are functions that AI assistants can call to perform actions or retrieve information. They accept validated input parameters and return structured results.
Basic Tool Definition
Here's a simple tool that echoes back a message:
import { z } from 'zod'
import { defineMcpTool } from '@nuxtjs/mcp-toolkit/server' // optional
export default defineMcpTool({
name: 'echo',
description: 'Echo back a message',
inputSchema: {
message: z.string().describe('The message to echo back'),
},
handler: async ({ message }) => {
return {
content: [{
type: 'text',
text: `Echo: ${message}`,
}],
}
},
})
Auto-Generated Name and Title
You can omit name and title - they will be automatically generated from the filename:
import { z } from 'zod'
import { defineMcpTool } from '@nuxtjs/mcp-toolkit/server' // optional
export default defineMcpTool({
// name and title are auto-generated from filename:
// name: 'list-documentation'
// title: 'List Documentation'
description: 'List all documentation files',
handler: async () => {
// ...
},
})
The filename list-documentation.ts automatically becomes:
name:list-documentation(kebab-case)title:List Documentation(title case)
You can still provide name or title explicitly to override the auto-generated values.
Tool Structure
A tool definition consists of:
import { defineMcpTool } from '@nuxtjs/mcp-toolkit/server' // optional
export default defineMcpTool({
name: 'tool-name', // Unique identifier (optional - auto-generated from filename)
inputSchema: { ... }, // Zod schema for input validation
handler: async (args) => { // Handler function
return { content: [...] }
},
})
import { defineMcpTool } from '@nuxtjs/mcp-toolkit/server' // optional
export default defineMcpTool({
name: 'tool-name', // Optional - auto-generated from filename
title: 'Tool Title', // Optional - auto-generated from filename
description: 'Tool description', // What the tool does
inputSchema: { ... }, // Optional - Zod schema for input validation
outputSchema: { ... }, // Zod schema for structured output
annotations: { ... }, // Behavioral hints for clients
inputExamples: [{ ... }], // Concrete usage examples
handler: async (args) => { ... },
})
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:
import { defineMcpTool } from '@nuxtjs/mcp-toolkit/server' // optional
export default defineMcpTool({
name: 'echo',
description: 'Echo back a message',
handler: async () => {
return {
content: [{
type: 'text',
text: 'Echo: test',
}],
}
},
})
For tools with parameters, define them using Zod schemas:
import { z } from 'zod'
import { defineMcpTool } from '@nuxtjs/mcp-toolkit/server' // optional
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
| Zod Type | Example | Description |
|---|---|---|
z.string() | z.string().min(1).max(100) | String with validation |
z.number() | z.number().min(0).max(100) | Number with validation |
z.boolean() | z.boolean() | Boolean value |
z.array() | z.array(z.string()) | Array of values |
z.object() | z.object({ ... }) | Nested object |
z.enum() | z.enum(['a', 'b']) | Enumeration |
z.optional() | z.string().optional() | Optional field |
z.default() | z.string().default('value') | Field with default |
Output Schema
Define structured output using outputSchema:
import { z } from 'zod'
import { defineMcpTool } from '@nuxtjs/mcp-toolkit/server' // optional
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 {
content: [{
type: 'text',
text: `BMI: ${bmi.toFixed(2)} (${category})`,
}],
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:
handler: async (args, extra) => {
// args: Validated input matching inputSchema
// extra: Request handler extra information
return {
content: [{
type: 'text',
text: 'Result text',
}],
structuredContent: { ... }, // Optional structured output
}
}
Content Types
Tools can return different content types:
return {
content: [{
type: 'text',
text: 'Hello, world!',
}],
}
return {
content: [{
type: 'image',
data: base64ImageData,
mimeType: 'image/png',
}],
}
return {
content: [{
type: 'resource',
resource: {
uri: 'file:///path/to/file',
text: 'File content',
mimeType: 'text/plain',
},
}],
}
Result Helpers
To simplify creating tool responses, the module provides auto-imported helper functions:
import { defineMcpTool, textResult } from '@nuxtjs/mcp-toolkit/server' // optional
// Simple text response
export default defineMcpTool({
description: 'Echo a message',
inputSchema: { message: z.string() },
handler: async ({ message }) => textResult(`Echo: ${message}`),
})
import { defineMcpTool, jsonResult } from '@nuxtjs/mcp-toolkit/server' // optional
// JSON response (auto-stringified)
export default defineMcpTool({
description: 'Get user data',
inputSchema: { id: z.string() },
handler: async ({ id }) => {
const user = await getUser(id)
return jsonResult(user)
// or jsonResult(user, false) for compact JSON
},
})
import { defineMcpTool, jsonResult, errorResult } from '@nuxtjs/mcp-toolkit/server' // optional
// Error response
export default defineMcpTool({
description: 'Find resource',
inputSchema: { id: z.string() },
handler: async ({ id }) => {
const resource = await findResource(id)
if (!resource) return errorResult('Resource not found')
return jsonResult(resource)
},
})
import { defineMcpTool, imageResult } from '@nuxtjs/mcp-toolkit/server' // optional
// Image response (base64)
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')
},
})
| Helper | Description | Parameters |
|---|---|---|
textResult(text) | Simple text response | text: string |
jsonResult(data, pretty?) | JSON response (auto-stringify) | data: unknown, pretty?: boolean (default: true) |
errorResult(message) | Error response with isError: true | message: string |
imageResult(data, mimeType) | Base64 image response | data: string, mimeType: string |
Tool Annotations
Annotations are behavioral hints that tell MCP clients how a tool behaves. Clients can use them to decide when to prompt users for confirmation (human-in-the-loop).
import { z } from 'zod'
import { defineMcpTool } from '@nuxtjs/mcp-toolkit/server' // optional
export default defineMcpTool({
name: 'delete-user',
description: 'Delete a user account',
inputSchema: {
userId: z.string(),
},
annotations: {
readOnlyHint: false, // Tool modifies state
destructiveHint: true, // Tool performs destructive updates
idempotentHint: true, // Deleting the same user twice has no additional effect
openWorldHint: false, // Tool does not interact with external systems
},
handler: async ({ userId }) => {
// ...
},
})
Annotation Reference
| Annotation | Type | Default | Description |
|---|---|---|---|
readOnlyHint | boolean | false | If true, the tool only reads data without modifying any state (safe to retry). |
destructiveHint | boolean | true | If true, the tool may perform destructive operations like deleting data. Only meaningful when readOnlyHint is false. |
idempotentHint | boolean | false | If true, calling the tool multiple times with the same arguments has no additional effect beyond the first call. Only meaningful when readOnlyHint is false. |
openWorldHint | boolean | true | If true, the tool may interact with the outside world (external APIs, internet). If false, it only operates on local/internal data. |
Here are common annotation patterns for typical tools:
// Search, list, lookup, calculate...
annotations: {
readOnlyHint: true,
destructiveHint: false,
openWorldHint: false,
}
// Creates a new record each time
annotations: {
readOnlyHint: false,
destructiveHint: false,
idempotentHint: false,
openWorldHint: false,
}
// Updates are idempotent (same input → same result)
annotations: {
readOnlyHint: false,
destructiveHint: false,
idempotentHint: true,
openWorldHint: false,
}
// Destructive and idempotent (deleting twice is the same)
annotations: {
readOnlyHint: false,
destructiveHint: true,
idempotentHint: true,
openWorldHint: false,
}
Input Examples
You can provide concrete usage examples for your tools using inputExamples. These examples are type-safe (matching your inputSchema) and are transmitted to clients via _meta.inputExamples.
Input examples help AI models understand how to correctly fill in tool parameters, especially for tools with optional fields or complex inputs.
import { z } from 'zod'
import { defineMcpTool } from '@nuxtjs/mcp-toolkit/server' // optional
export default defineMcpTool({
description: 'Create a new todo',
inputSchema: {
title: z.string().describe('The title of the todo'),
content: z.string().optional().describe('Optional description'),
},
inputExamples: [
{ title: 'Buy groceries', content: 'Milk, eggs, bread' },
{ title: 'Fix login bug' }, // content is optional
],
handler: async ({ title, content }) => {
// ...
},
})
inputExamples are particularly useful for tools with optional parameters, enums, or complex nested inputs where showing concrete values helps models pick the right format.Error Handling
Handle errors gracefully in your handlers:
import { z } from 'zod'
import { defineMcpTool } from '@nuxtjs/mcp-toolkit/server' // optional
export default defineMcpTool({
name: 'safe-divide',
inputSchema: {
a: z.number(),
b: z.number(),
},
handler: async ({ a, b }) => {
if (b === 0) {
return {
content: [{
type: 'text',
text: 'Error: Division by zero',
}],
isError: true,
}
}
const result = a / b
return {
content: [{
type: 'text',
text: `Result: ${result}`,
}],
}
},
})
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:
import { z } from 'zod'
import { defineMcpTool } from '@nuxtjs/mcp-toolkit/server' // optional
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 }) => {
const data = await fetchExpensiveData(id)
return {
content: [{ type: 'text', text: JSON.stringify(data) }],
}
},
})
Full Cache Options
For more control, use an object with all Nitro cache options:
import { z } from 'zod'
import { defineMcpTool } from '@nuxtjs/mcp-toolkit/server' // optional
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
| Option | Type | Required | Description |
|---|---|---|---|
maxAge | string | number | Yes | Cache duration (e.g., '1h', 3600000) |
getKey | (args) => string | No | Custom cache key generator |
staleMaxAge | number | No | Duration for stale-while-revalidate |
swr | boolean | No | Enable stale-while-revalidate |
name | string | No | Cache name (auto-generated from tool name) |
group | string | No | Cache group (default: 'mcp') |
Advanced Examples
Tool with Error Handling
Here's an example showing proper error handling:
import { z } from 'zod'
import { defineMcpTool } from '@nuxtjs/mcp-toolkit/server' // optional
export default defineMcpTool({
name: 'safe-operation',
description: 'Perform an operation with error handling',
inputSchema: {
value: z.string().describe('Input value'),
},
handler: async ({ value }) => {
try {
// Your operation here
const result = value.toUpperCase()
return {
content: [{
type: 'text',
text: `Result: ${result}`,
}],
}
}
catch (error) {
return {
content: [{
type: 'text',
text: `Error: ${error instanceof Error ? error.message : String(error)}`,
}],
isError: true,
}
}
},
})
File Organization
Organize your tools in the server/mcp/tools/ directory:
server/
└── mcp/
└── tools/
├── echo.ts
├── calculator.ts
├── bmi.ts
└── text-processor.ts
Each file should export a default tool definition.
Type Safety
The module provides full TypeScript type inference:
// Input types are inferred from inputSchema
handler: async ({ message }) => {
// message is typed as string
}
// Output types are inferred from outputSchema
const result = {
structuredContent: {
bmi: 25.5, // number
category: '...', // string
},
}