> ## 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.

# tool() Function

> Create type-safe tools with Zod schema validation

The `tool()` function creates tool definitions for SDK-embedded MCP servers with automatic TypeScript type inference from Zod schemas.

## Import

```typescript theme={null}
import { tool } from '@qwen-code/sdk';
import { z } from 'zod';
import type { SdkMcpToolDefinition } from '@qwen-code/sdk';
```

## Signature

```typescript theme={null}
function tool<Schema extends ZodRawShape>(
  name: string,
  description: string,
  inputSchema: Schema,
  handler: (
    args: z.infer<ZodObject<Schema>>,
    extra: unknown
  ) => Promise<CallToolResult>
): SdkMcpToolDefinition<Schema>
```

## Parameters

<ParamField path="name" type="string" required>
  Tool name (1-64 characters). Must start with a letter and contain only letters, numbers, and underscores.

  ```typescript theme={null}
  tool('calculate_sum', /* ... */)  // ✅ Valid
  tool('get_weather', /* ... */)    // ✅ Valid
  tool('123invalid', /* ... */)     // ❌ Invalid - starts with number
  tool('my-tool', /* ... */)        // ❌ Invalid - contains hyphen
  ```
</ParamField>

<ParamField path="description" type="string" required>
  Human-readable description of what the tool does. This helps the AI understand when to use the tool.

  ```typescript theme={null}
  tool(
    'search_docs',
    'Search technical documentation using semantic search',
    /* ... */
  )
  ```

  <Note>
    Write clear, concise descriptions. The AI uses these to decide when to invoke the tool.
  </Note>
</ParamField>

<ParamField path="inputSchema" type="ZodRawShape" required>
  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)

  ```typescript theme={null}
  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 }
    }
  )
  ```
</ParamField>

<ParamField path="handler" type="function" required>
  Async function that executes the tool and returns the result.

  **Signature:**

  ```typescript theme={null}
  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
</ParamField>

## Return Value

<ResponseField name="SdkMcpToolDefinition" type="object">
  A tool definition object that can be passed to `createSdkMcpServer()`.

  ```typescript theme={null}
  {
    name: string;
    description: string;
    inputSchema: Schema;
    handler: Function;
  }
  ```
</ResponseField>

## CallToolResult Format

The handler must return a `CallToolResult` object:

```typescript theme={null}
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:

```typescript theme={null}
return {
  content: [{ type: 'text', text: 'Operation completed successfully' }],
};
```

### Image Content

Return base64-encoded images:

```typescript theme={null}
return {
  content: [{
    type: 'image',
    data: 'iVBORw0KGgoAAAANSUhEUgAA...', // base64 data
    mimeType: 'image/png',
  }],
};
```

### Multiple Content Blocks

Combine text and images:

```typescript theme={null}
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`:

```typescript theme={null}
return {
  content: [{ type: 'text', text: 'Failed to connect to database' }],
  isError: true,
};
```

## Examples

### Simple Calculator

```typescript theme={null}
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

```typescript theme={null}
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

```typescript theme={null}
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

```typescript theme={null}
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

```typescript theme={null}
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)}`,
      }],
    };
  }
);
```

### Tool with No Input

```typescript theme={null}
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:

```typescript theme={null}
{
  // 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:

```typescript theme={null}
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

### Tool Name Validation

The tool name must:

* Be 1-64 characters long
* Start with a letter (a-z, A-Z)
* Contain only letters, numbers, and underscores

```typescript theme={null}
// ✅ 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
```

### Input Validation

Zod validates inputs at runtime:

```typescript theme={null}
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:

```typescript theme={null}
// Tool receives: { age: 'not a number' }
// Zod validation fails
// Tool returns error to AI automatically
```

### Handler Errors

Catch and format errors in your handler:

```typescript theme={null}
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:

```typescript theme={null}
// Configure timeout in query options
options: {
  mcpServers: { myServer: server },
  timeout: {
    mcpRequest: 600000, // 10 minutes
  },
}
```

## Type Safety

The tool function provides full type inference:

```typescript theme={null}
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

```typescript theme={null}
// ❌ Bad
tool('search', 'Searches stuff', /* ... */)

// ✅ Good
tool(
  'search',
  'Search the product catalog using keywords. Returns up to 50 matching products.',
  /* ... */
)
```

### 2. Validate and Constrain Inputs

```typescript theme={null}
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

```typescript theme={null}
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

```typescript theme={null}
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

```typescript theme={null}
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

* [createSdkMcpServer()](/sdk/mcp/create-server) - Create MCP server instances
* [MCP Overview](/sdk/mcp/overview) - MCP integration guide
* [MCP Examples](/sdk/mcp/examples) - Complete examples
