> ## Documentation Index
> Fetch the complete documentation index at: https://docs.chucky.cloud/llms.txt
> Use this file to discover all available pages before exploring further.

# Add custom tools

> Give Claude the ability to take actions in your application

# Add custom tools

Tools let Claude call functions that execute in your application - query databases, call APIs, interact with UI elements, or perform any action your app needs.

## How It Works

When you define a tool with a handler, the handler runs in **your application** (not on the server):

```
Your App                    Chucky Cloud                    Claude
   │                             │                            │
   │  createSession(mcpServers)  │                            │
   │────────────────────────────►│                            │
   │                             │                            │
   │  send("Get weather")        │     prompt + tools         │
   │────────────────────────────►│───────────────────────────►│
   │                             │                            │
   │                             │     tool_call: get_weather │
   │   tool_call: get_weather    │◄───────────────────────────│
   │◄────────────────────────────│                            │
   │                             │                            │
   │   [handler executes]        │                            │
   │   → returns "Sunny, 22°C"   │                            │
   │                             │                            │
   │   tool_result: "Sunny"      │     tool_result            │
   │────────────────────────────►│───────────────────────────►│
   │                             │                            │
   │                             │     "It's sunny and 22°C"  │
   │   assistant message         │◄───────────────────────────│
   │◄────────────────────────────│                            │
```

Your tools have access to everything in your app: databases, APIs, the DOM, local storage, etc. For type definitions, see [SDK Types Reference](/sdk/javascript/types).

***

## JavaScript

### Simple Object Syntax

```typescript theme={null}
import { ChuckyClient, getAssistantText } from '@chucky.cloud/sdk';

const weatherTool = {
  name: 'get_weather',
  description: 'Get current weather for a city',
  inputSchema: {
    type: 'object',
    properties: {
      city: { type: 'string', description: 'City name' },
      unit: { type: 'string', enum: ['celsius', 'fahrenheit'] },
    },
    required: ['city'],
  },
  handler: async ({ city, unit = 'celsius' }) => {
    const weather = await fetchWeatherAPI(city, unit);
    return { content: [{ type: 'text', text: `${city}: ${weather.temp}°` }] };
  }
};

const client = new ChuckyClient({ token });
const session = client.createSession({
  model: 'claude-sonnet-4-5-20250929',
  mcpServers: [{
    name: 'my-tools',
    version: '1.0.0',
    tools: [weatherTool]
  }],
});

await session.send('What is the weather in Paris?');

for await (const msg of session.stream()) {
  if (msg.type === 'assistant') {
    console.log(getAssistantText(msg));
  }
}

session.close();
```

### McpServerBuilder

For multiple related tools, use the builder pattern:

```typescript theme={null}
import { McpServerBuilder, textResult, errorResult } from '@chucky.cloud/sdk';

const fileServer = new McpServerBuilder('file-tools', '1.0.0')
  .addTool({
    name: 'read_file',
    description: 'Read contents of a file',
    inputSchema: {
      type: 'object',
      properties: {
        path: { type: 'string', description: 'File path' },
      },
      required: ['path'],
    },
    handler: async ({ path }) => {
      const content = await fs.readFile(path, 'utf-8');
      return textResult(content);
    },
  })
  .addTool({
    name: 'write_file',
    description: 'Write content to a file',
    inputSchema: {
      type: 'object',
      properties: {
        path: { type: 'string' },
        content: { type: 'string' },
      },
      required: ['path', 'content'],
    },
    handler: async ({ path, content }) => {
      await fs.writeFile(path, content);
      return textResult(`Wrote ${content.length} bytes`);
    },
  })
  .build();

const session = client.createSession({
  mcpServers: [fileServer],
});
```

### Result Helpers

```typescript theme={null}
import { textResult, errorResult, imageResult } from '@chucky.cloud/sdk';

// Success
return textResult('Operation completed');

// Error
return errorResult('File not found');

// Image
return imageResult(base64Data, 'image/png');
```

***

## Python

### Tool Decorator

The `@tool` decorator automatically infers the JSON schema from type hints:

```python theme={null}
from chucky import tool, text_result, error_result

@tool('get_weather', 'Get weather for a city')
async def get_weather(city: str, unit: str = 'celsius') -> ToolResult:
    """
    Args:
        city: City name
        unit: Temperature unit (celsius or fahrenheit)
    """
    weather = await fetch_weather(city, unit)
    return text_result(f'{city}: {weather}')
```

### With Pydantic Model

```python theme={null}
from pydantic import BaseModel, Field
from typing import Literal

class WeatherInput(BaseModel):
    city: str = Field(description='City name')
    unit: Literal['celsius', 'fahrenheit'] = Field(
        default='celsius',
        description='Temperature unit'
    )

@tool('get_weather', 'Get weather', schema=WeatherInput)
async def get_weather(args: dict) -> ToolResult:
    return text_result(f'{args["city"]}: sunny')
```

### MCP Server

```python theme={null}
from chucky import Chucky, create_mcp_server, tool, text_result

@tool('read_file', 'Read a file')
async def read_file(path: str) -> ToolResult:
    with open(path) as f:
        return text_result(f.read())

@tool('write_file', 'Write a file')
async def write_file(path: str, content: str) -> ToolResult:
    with open(path, 'w') as f:
        f.write(content)
    return text_result('File written')

file_server = create_mcp_server('file-tools', [read_file, write_file])

client = Chucky(token='...')
async with client.session(mcp_servers={'files': file_server}) as session:
    result = await session.send('Read the config.json file')
```

### Type Mapping

| Python Type         | JSON Schema |
| ------------------- | ----------- |
| `str`               | `string`    |
| `int`               | `integer`   |
| `float`             | `number`    |
| `bool`              | `boolean`   |
| `List[T]`           | `array`     |
| `Dict[str, T]`      | `object`    |
| `Optional[T]`       | Nullable    |
| `Literal['a', 'b']` | `enum`      |

***

## Input Schema

Use JSON Schema to define parameters:

```typescript theme={null}
const searchTool = {
  name: 'search_products',
  description: 'Search for products in the catalog',
  inputSchema: {
    type: 'object',
    properties: {
      // Required string
      query: {
        type: 'string',
        description: 'Search query',
      },
      // Optional number with constraints
      limit: {
        type: 'number',
        description: 'Max results (1-50)',
        minimum: 1,
        maximum: 50,
      },
      // Enum (fixed options)
      sort: {
        type: 'string',
        enum: ['relevance', 'price', 'rating'],
        description: 'Sort order',
      },
      // Array of strings
      tags: {
        type: 'array',
        items: { type: 'string' },
        description: 'Filter by tags',
      },
    },
    required: ['query'],
  },
  handler: async ({ query, limit = 10, sort = 'relevance', tags = [] }) => {
    const results = await db.products.search({ query, limit, sort, tags });
    return textResult(JSON.stringify(results));
  }
};
```

***

## Tool Results

Handlers return a `ToolResult` object:

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

// Error
return {
  content: [{ type: 'text', text: 'Error: File not found' }],
  isError: true
};

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

// Multiple content blocks
return {
  content: [
    { type: 'text', text: 'Here is the chart:' },
    { type: 'image', data: chartBase64, mimeType: 'image/png' },
  ]
};
```

***

## Browser Tools

In the browser, tools have access to the DOM:

```typescript theme={null}
const highlightTool = {
  name: 'highlight_element',
  description: 'Highlight an element on the page',
  inputSchema: {
    type: 'object',
    properties: {
      selector: { type: 'string', description: 'CSS selector' },
      color: { type: 'string', description: 'Highlight color' },
    },
    required: ['selector'],
  },
  handler: async ({ selector, color = 'yellow' }) => {
    const element = document.querySelector(selector);
    if (!element) {
      return errorResult('Element not found');
    }
    element.style.backgroundColor = color;
    return textResult(`Highlighted ${selector}`);
  }
};

const showModalTool = {
  name: 'show_modal',
  description: 'Show a modal dialog',
  inputSchema: {
    type: 'object',
    properties: {
      title: { type: 'string' },
      message: { type: 'string' },
    },
    required: ['message'],
  },
  handler: async ({ title, message }) => {
    await showModal({ title, message });
    return textResult('Modal shown');
  }
};
```

***

## Best Practices

<AccordionGroup>
  <Accordion title="Write clear descriptions">
    Claude uses descriptions to decide when to use tools:

    ```typescript theme={null}
    // Good
    description: 'Search for products by name, category, or price range. Returns up to 10 matching products.'

    // Bad
    description: 'Search products'
    ```
  </Accordion>

  <Accordion title="Handle errors gracefully">
    Return informative errors so Claude can respond appropriately:

    ```typescript theme={null}
    handler: async ({ id }) => {
      try {
        const item = await database.find(id);
        if (!item) {
          return errorResult(`Item ${id} not found`);
        }
        return textResult(JSON.stringify(item));
      } catch (err) {
        return errorResult(`Database error: ${err.message}`);
      }
    }
    ```
  </Accordion>

  <Accordion title="Keep tools focused">
    Each tool should do one thing well:

    ```typescript theme={null}
    // Good: Separate tools
    const searchProducts = { name: 'search_products', ... };
    const getProductDetails = { name: 'get_product', ... };
    const addToCart = { name: 'add_to_cart', ... };

    // Bad: One mega-tool
    const productOperations = { name: 'product_ops', ... };
    ```
  </Accordion>

  <Accordion title="Group related tools">
    Use MCP servers to bundle related tools:

    ```typescript theme={null}
    const fileServer = new McpServerBuilder('file-tools', '1.0.0')
      .addTool({ name: 'read_file', ... })
      .addTool({ name: 'write_file', ... })
      .addTool({ name: 'list_files', ... })
      .build();
    ```
  </Accordion>
</AccordionGroup>
