Skip to main content

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.

JavaScript

Simple Object Syntax

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:
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

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:
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

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

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 TypeJSON Schema
strstring
intinteger
floatnumber
boolboolean
List[T]array
Dict[str, T]object
Optional[T]Nullable
Literal['a', 'b']enum

Input Schema

Use JSON Schema to define parameters:
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:
// 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:
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

Claude uses descriptions to decide when to use tools:
// Good
description: 'Search for products by name, category, or price range. Returns up to 10 matching products.'

// Bad
description: 'Search products'
Return informative errors so Claude can respond appropriately:
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}`);
  }
}
Each tool should do one thing well:
// 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', ... };