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):Copy
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 │◄───────────────────────────│
│◄────────────────────────────│ │
JavaScript
Simple Object Syntax
Copy
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:Copy
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
Copy
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:
Copy
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
Copy
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
Copy
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:Copy
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 aToolResult object:
Copy
// 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:Copy
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
Write clear descriptions
Write clear descriptions
Claude uses descriptions to decide when to use tools:
Copy
// Good
description: 'Search for products by name, category, or price range. Returns up to 10 matching products.'
// Bad
description: 'Search products'
Handle errors gracefully
Handle errors gracefully
Return informative errors so Claude can respond appropriately:
Copy
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}`);
}
}
Keep tools focused
Keep tools focused
Each tool should do one thing well:
Copy
// 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', ... };
Group related tools
Group related tools
Use MCP servers to bundle related tools:
Copy
const fileServer = new McpServerBuilder('file-tools', '1.0.0')
.addTool({ name: 'read_file', ... })
.addTool({ name: 'write_file', ... })
.addTool({ name: 'list_files', ... })
.build();