Documentation Index
Fetch the complete documentation index at: https://mintlify.com/QwenLM/qwen-code/llms.txt
Use this file to discover all available pages before exploring further.
The tool() function creates tool definitions for SDK-embedded MCP servers with automatic TypeScript type inference from Zod schemas.
Import
import { tool } from '@qwen-code/sdk';
import { z } from 'zod';
import type { SdkMcpToolDefinition } from '@qwen-code/sdk';
Signature
function tool<Schema extends ZodRawShape>(
name: string,
description: string,
inputSchema: Schema,
handler: (
args: z.infer<ZodObject<Schema>>,
extra: unknown
) => Promise<CallToolResult>
): SdkMcpToolDefinition<Schema>
Parameters
Tool name (1-64 characters). Must start with a letter and contain only letters, numbers, and underscores.tool('calculate_sum', /* ... */) // ✅ Valid
tool('get_weather', /* ... */) // ✅ Valid
tool('123invalid', /* ... */) // ❌ Invalid - starts with number
tool('my-tool', /* ... */) // ❌ Invalid - contains hyphen
Human-readable description of what the tool does. This helps the AI understand when to use the tool.tool(
'search_docs',
'Search technical documentation using semantic search',
/* ... */
)
Write clear, concise descriptions. The AI uses these to decide when to invoke the tool.
Zod schema object defining the tool’s input parameters. The schema is used for:
- Type inference (TypeScript types for handler args)
- Runtime validation (invalid inputs are rejected)
- AI guidance (schema sent to AI as JSON Schema)
tool(
'create_user',
'Create a new user account',
{
email: z.string().email().describe('User email address'),
name: z.string().min(1).describe('User full name'),
age: z.number().int().min(0).max(120).optional(),
},
async (args) => {
// args is typed as:
// { email: string; name: string; age?: number }
}
)
Async function that executes the tool and returns the result.Signature:async (args: InferredType, extra: unknown) => Promise<CallToolResult>
args: Tool input, typed according to inputSchema
extra: Reserved for future use (currently unused)
- Returns:
CallToolResult with content blocks
Return Value
A tool definition object that can be passed to createSdkMcpServer().{
name: string;
description: string;
inputSchema: Schema;
handler: Function;
}
The handler must return a CallToolResult object:
type CallToolResult = {
content: Array<
| { type: 'text'; text: string }
| { type: 'image'; data: string; mimeType: string }
| { type: 'resource'; uri: string; mimeType?: string; text?: string }
>;
isError?: boolean;
};
Text Content
Most common response type:
return {
content: [{ type: 'text', text: 'Operation completed successfully' }],
};
Image Content
Return base64-encoded images:
return {
content: [{
type: 'image',
data: 'iVBORw0KGgoAAAANSUhEUgAA...', // base64 data
mimeType: 'image/png',
}],
};
Multiple Content Blocks
Combine text and images:
return {
content: [
{ type: 'text', text: 'Here is the visualization:' },
{ type: 'image', data: chartImageData, mimeType: 'image/png' },
{ type: 'text', text: 'Analysis complete.' },
],
};
Error Response
Indicate errors with isError: true:
return {
content: [{ type: 'text', text: 'Failed to connect to database' }],
isError: true,
};
Examples
Simple Calculator
import { z } from 'zod';
import { tool } from '@qwen-code/sdk';
const addTool = tool(
'add',
'Add two numbers together',
{
a: z.number().describe('First number'),
b: z.number().describe('Second number'),
},
async (args) => {
const result = args.a + args.b;
return {
content: [{ type: 'text', text: String(result) }],
};
}
);
File Operations
import { z } from 'zod';
import { tool } from '@qwen-code/sdk';
import * as fs from 'fs/promises';
const writeFileTool = tool(
'write_custom_file',
'Write content to a file in the custom directory',
{
filename: z.string().describe('Name of the file'),
content: z.string().describe('Content to write'),
},
async (args) => {
try {
const path = `./custom-dir/${args.filename}`;
await fs.writeFile(path, args.content, 'utf-8');
return {
content: [{
type: 'text',
text: `Successfully wrote ${args.content.length} bytes to ${path}`,
}],
};
} catch (error) {
return {
content: [{
type: 'text',
text: `Error writing file: ${error.message}`,
}],
isError: true,
};
}
}
);
API Integration
import { z } from 'zod';
import { tool } from '@qwen-code/sdk';
const weatherTool = tool(
'get_weather',
'Get current weather for a city using OpenWeatherMap API',
{
city: z.string().describe('City name'),
units: z.enum(['metric', 'imperial']).default('metric').describe('Temperature units'),
},
async (args) => {
try {
const apiKey = process.env.OPENWEATHER_API_KEY;
const url = `https://api.openweathermap.org/data/2.5/weather?q=${args.city}&units=${args.units}&appid=${apiKey}`;
const response = await fetch(url);
const data = await response.json();
if (!response.ok) {
return {
content: [{ type: 'text', text: `Error: ${data.message}` }],
isError: true,
};
}
const temp = data.main.temp;
const description = data.weather[0].description;
const unit = args.units === 'metric' ? '°C' : '°F';
return {
content: [{
type: 'text',
text: `Weather in ${args.city}: ${description}, ${temp}${unit}`,
}],
};
} catch (error) {
return {
content: [{ type: 'text', text: `Failed to fetch weather: ${error.message}` }],
isError: true,
};
}
}
);
Database Query
import { z } from 'zod';
import { tool } from '@qwen-code/sdk';
import { db } from './database';
const queryUsersTool = tool(
'query_users',
'Search users in the database',
{
email: z.string().email().optional().describe('Filter by email'),
minAge: z.number().int().min(0).optional().describe('Minimum age'),
limit: z.number().int().min(1).max(100).default(10).describe('Max results'),
},
async (args) => {
try {
let query = db.users.select();
if (args.email) {
query = query.where('email', '=', args.email);
}
if (args.minAge !== undefined) {
query = query.where('age', '>=', args.minAge);
}
const users = await query.limit(args.limit).execute();
return {
content: [{
type: 'text',
text: `Found ${users.length} users:\n${JSON.stringify(users, null, 2)}`,
}],
};
} catch (error) {
return {
content: [{ type: 'text', text: `Database error: ${error.message}` }],
isError: true,
};
}
}
);
Complex Schema with Nested Objects
import { z } from 'zod';
import { tool } from '@qwen-code/sdk';
const createProjectTool = tool(
'create_project',
'Create a new project with configuration',
{
name: z.string().min(1).describe('Project name'),
config: z.object({
language: z.enum(['typescript', 'javascript', 'python']),
framework: z.string().optional(),
dependencies: z.array(z.string()).default([]),
}).describe('Project configuration'),
metadata: z.record(z.string()).optional().describe('Additional metadata'),
},
async (args) => {
// args is fully typed:
// {
// name: string;
// config: {
// language: 'typescript' | 'javascript' | 'python';
// framework?: string;
// dependencies: string[];
// };
// metadata?: Record<string, string>;
// }
const project = {
id: Math.random().toString(36),
...args,
createdAt: new Date().toISOString(),
};
return {
content: [{
type: 'text',
text: `Created project: ${JSON.stringify(project, null, 2)}`,
}],
};
}
);
import { z } from 'zod';
import { tool } from '@qwen-code/sdk';
const getTimeTool = tool(
'get_current_time',
'Get the current server time',
{}, // Empty schema for no parameters
async () => {
return {
content: [{
type: 'text',
text: new Date().toISOString(),
}],
};
}
);
Schema Validation
Supported Zod Types
The tool function supports all Zod schema types:
{
// Primitives
str: z.string(),
num: z.number(),
bool: z.boolean(),
// Constrained
email: z.string().email(),
url: z.string().url(),
positiveInt: z.number().int().positive(),
// Enums
status: z.enum(['active', 'inactive', 'pending']),
// Arrays
tags: z.array(z.string()),
// Objects
user: z.object({
name: z.string(),
age: z.number(),
}),
// Optional/Default
optional: z.string().optional(),
withDefault: z.number().default(42),
// Union types
idOrName: z.union([z.number(), z.string()]),
// Records/Dictionaries
metadata: z.record(z.string()),
}
Descriptions
Add descriptions to help the AI understand parameters:
tool(
'search',
'Search for items',
{
query: z.string()
.min(1)
.describe('Search query (minimum 1 character)'),
filters: z.object({
category: z.string().optional()
.describe('Filter by category name'),
priceRange: z.object({
min: z.number().optional()
.describe('Minimum price in USD'),
max: z.number().optional()
.describe('Maximum price in USD'),
}).optional(),
}).optional().describe('Optional search filters'),
},
async (args) => { /* ... */ }
)
Validation Rules
The tool name must:
- Be 1-64 characters long
- Start with a letter (a-z, A-Z)
- Contain only letters, numbers, and underscores
// ✅ Valid names
tool('calculate', /* ... */)
tool('get_user', /* ... */)
tool('searchDocs', /* ... */)
tool('tool_123', /* ... */)
// ❌ Invalid names
tool('123tool', /* ... */) // Starts with number
tool('my-tool', /* ... */) // Contains hyphen
tool('user@search', /* ... */) // Contains @
tool('', /* ... */) // Empty string
Zod validates inputs at runtime:
const emailTool = tool(
'send_email',
'Send an email',
{
to: z.string().email(),
subject: z.string().min(1),
},
async (args) => {
// args.to is guaranteed to be a valid email
// args.subject is guaranteed to be non-empty
}
);
// If AI passes invalid data:
// { to: 'not-an-email', subject: '' }
// The tool will return a validation error automatically
Error Handling
Validation Errors
Invalid inputs are automatically rejected:
// Tool receives: { age: 'not a number' }
// Zod validation fails
// Tool returns error to AI automatically
Handler Errors
Catch and format errors in your handler:
const tool = tool(
'risky_operation',
'Perform a risky operation',
{ input: z.string() },
async (args) => {
try {
const result = await performRiskyOperation(args.input);
return {
content: [{ type: 'text', text: result }],
};
} catch (error) {
// Return formatted error
return {
content: [{
type: 'text',
text: `Operation failed: ${error.message}`,
}],
isError: true,
};
}
}
);
Timeout Errors
Tools that take too long will timeout:
// Configure timeout in query options
options: {
mcpServers: { myServer: server },
timeout: {
mcpRequest: 600000, // 10 minutes
},
}
Type Safety
The tool function provides full type inference:
import { z } from 'zod';
import { tool } from '@qwen-code/sdk';
const myTool = tool(
'process_data',
'Process some data',
{
id: z.number(),
name: z.string(),
tags: z.array(z.string()),
metadata: z.record(z.string()).optional(),
},
async (args) => {
// TypeScript knows:
args.id // number
args.name // string
args.tags // string[]
args.metadata // Record<string, string> | undefined
// This would be a TypeScript error:
// args.id.toUpperCase() // Error: number has no toUpperCase
return {
content: [{ type: 'text', text: 'Done' }],
};
}
);
Best Practices
1. Write Clear Descriptions
// ❌ Bad
tool('search', 'Searches stuff', /* ... */)
// ✅ Good
tool(
'search',
'Search the product catalog using keywords. Returns up to 50 matching products.',
/* ... */
)
tool(
'create_user',
'Create a new user',
{
email: z.string().email(),
age: z.number().int().min(0).max(120),
username: z.string().min(3).max(20).regex(/^[a-zA-Z0-9_]+$/),
},
/* ... */
)
3. Use Optional and Default Values
tool(
'search',
'Search items',
{
query: z.string(),
limit: z.number().int().min(1).max(100).default(10),
offset: z.number().int().min(0).default(0).optional(),
},
/* ... */
)
4. Handle Errors Gracefully
const tool = tool(
'fetch_data',
'Fetch data from API',
{ url: z.string().url() },
async (args) => {
try {
const response = await fetch(args.url);
const data = await response.text();
return { content: [{ type: 'text', text: data }] };
} catch (error) {
return {
content: [{ type: 'text', text: `Fetch failed: ${error.message}` }],
isError: true,
};
}
}
);
5. Return Structured Data
const tool = tool(
'get_stats',
'Get usage statistics',
{},
async () => {
const stats = await getStats();
// Format as readable text or JSON
return {
content: [{
type: 'text',
text: JSON.stringify(stats, null, 2),
}],
};
}
);
See Also