170 lines
6.2 KiB
Markdown
170 lines
6.2 KiB
Markdown
---
|
|
title: "Custom Tools in the Agent SDK"
|
|
aliases: [custom-tools, sdk-custom-tools, mcp-custom-tools]
|
|
tags: [agent-sdk, mcp, tools, python, typescript]
|
|
sources: [raw/Give Claude custom tools.md]
|
|
created: 2026-04-17
|
|
updated: 2026-04-17
|
|
---
|
|
|
|
# Custom Tools in the Agent SDK
|
|
|
|
Extend Claude's capabilities by defining your own async functions — wrapped in an in-process MCP server — that Claude can call during any `query()` session.
|
|
|
|
## Tool Anatomy
|
|
|
|
Every tool has four required parts:
|
|
|
|
| Part | Purpose |
|
|
|------|---------|
|
|
| **Name** | Unique ID Claude uses to call the tool |
|
|
| **Description** | What the tool does — Claude reads this to decide when to call it |
|
|
| **Input schema** | Arguments Claude must supply (Zod in TS, dict or JSON Schema in Python) |
|
|
| **Handler** | `async` function that runs when Claude calls the tool; returns `{ content, isError? }` |
|
|
|
|
The `content` array accepts blocks of type `"text"`, `"image"`, or `"resource"`.
|
|
|
|
## Defining a Tool
|
|
|
|
**Python** — use the `@tool` decorator:
|
|
|
|
```python
|
|
from claude_agent_sdk import tool, create_sdk_mcp_server
|
|
|
|
@tool(
|
|
"get_temperature",
|
|
"Get the current temperature at a location",
|
|
{"latitude": float, "longitude": float},
|
|
)
|
|
async def get_temperature(args):
|
|
# ... fetch data ...
|
|
return {"content": [{"type": "text", "text": "72°F"}]}
|
|
|
|
weather_server = create_sdk_mcp_server(
|
|
name="weather", version="1.0.0", tools=[get_temperature]
|
|
)
|
|
```
|
|
|
|
**TypeScript** — use the `tool()` helper with a Zod schema (args are auto-typed).
|
|
|
|
## Registering & Calling Tools
|
|
|
|
Pass the server to `query()` via `mcpServers`. The tool's fully-qualified name follows:
|
|
|
|
```
|
|
mcp__{server_name}__{tool_name}
|
|
```
|
|
|
|
List it in `allowedTools` to skip the permission prompt:
|
|
|
|
```python
|
|
options = ClaudeAgentOptions(
|
|
mcp_servers={"weather": weather_server},
|
|
allowed_tools=["mcp__weather__get_temperature"],
|
|
)
|
|
async for msg in query(prompt="Temp in SF?", options=options):
|
|
...
|
|
```
|
|
|
|
Use wildcard `mcp__weather__*` to approve all tools on a server at once.
|
|
|
|
## Optional Parameters
|
|
|
|
- **TypeScript:** add `.default()` to a Zod field.
|
|
- **Python:** omit the key from the schema dict, mention it in the description, read with `args.get("key", default)`.
|
|
|
|
## Tool Annotations
|
|
|
|
Pass via `annotations=ToolAnnotations(...)` to give Claude hints about side effects:
|
|
|
|
| Field | Default | Effect |
|
|
|-------|---------|--------|
|
|
| `readOnlyHint` | `false` | Allows parallel batching with other read-only tools |
|
|
| `destructiveHint` | `true` | Informational — flags potentially destructive ops |
|
|
| `idempotentHint` | `false` | Informational — repeated calls are safe |
|
|
| `openWorldHint` | `true` | Informational — tool reaches external systems |
|
|
|
|
Annotations are metadata only — they do not enforce behavior.
|
|
|
|
## Controlling Tool Access
|
|
|
|
Two separate layers govern tool availability:
|
|
|
|
| Option | Layer | Effect |
|
|
|--------|-------|--------|
|
|
| `tools: ["Read", "Grep"]` | Availability | Restricts which built-ins appear in Claude's context |
|
|
| `tools: []` | Availability | Removes all built-ins; Claude only uses MCP tools |
|
|
| `allowedTools` | Permission | Listed tools run without a prompt |
|
|
| `disallowedTools` | Permission | Calls denied, but tool stays visible (wastes turns) |
|
|
|
|
Prefer `tools` over `disallowedTools` to restrict built-ins — omitting removes it from context entirely.
|
|
|
|
## Error Handling
|
|
|
|
| Handler behavior | Agent loop |
|
|
|-----------------|-----------|
|
|
| Throws uncaught exception | **Stops** — `query()` fails, Claude never sees the error |
|
|
| Returns `is_error: True` in result | **Continues** — Claude sees error as data, can retry or explain |
|
|
|
|
Always `try/except` (Python) or `try/catch` (TS) inside handlers and return `is_error: True` for recoverable failures.
|
|
|
|
## Returning Images & Resources
|
|
|
|
**Images** — base64-encode raw bytes inline (no URL field, no `data:image/...` prefix):
|
|
|
|
```python
|
|
return {"content": [{"type": "image", "data": base64_str, "mimeType": "image/png"}]}
|
|
```
|
|
|
|
**Resources** — embed content identified by a URI label:
|
|
|
|
```python
|
|
return {"content": [{"type": "resource", "resource": {
|
|
"uri": "file:///tmp/report.md",
|
|
"mimeType": "text/markdown",
|
|
"text": "# Report\n..."
|
|
}}]}
|
|
```
|
|
|
|
The URI is a label for Claude — the SDK does not read from that path.
|
|
|
|
## Enum / Complex Schemas (Python)
|
|
|
|
The dict schema doesn't support enums. Use full JSON Schema when you need enums, ranges, or nested objects:
|
|
|
|
```python
|
|
@tool("convert_units", "...", {
|
|
"type": "object",
|
|
"properties": {
|
|
"unit_type": {"type": "string", "enum": ["length", "temperature", "weight"]},
|
|
...
|
|
},
|
|
"required": ["unit_type", ...]
|
|
})
|
|
```
|
|
|
|
## Scaling Beyond a Few Tools
|
|
|
|
Each tool in the server array consumes context window space every turn. For dozens of tools, use [[wiki/agent-sdk/tool-search|tool search]] to load tools on demand instead of all upfront.
|
|
|
|
## Key Takeaways
|
|
|
|
- Define tools with `@tool` (Python) / `tool()` (TS) → wrap in `create_sdk_mcp_server` → pass to `query()` via `mcpServers`
|
|
- Tool names follow `mcp__{server_name}__{tool_name}`; use wildcard `mcp__server__*` to approve all
|
|
- Return `is_error: True` (not throw) to keep the agent loop alive on tool failure
|
|
- `readOnlyHint: true` lets Claude batch parallel calls — keep annotations accurate
|
|
- Use `tools: []` to remove all built-ins; use `tools: ["Read"]` to whitelist specific built-ins
|
|
- Images must be base64 inline; resources carry content in `text` or `blob`, URI is just a label
|
|
- For enum/complex input schemas in Python, pass a full JSON Schema dict instead of the shorthand dict
|
|
|
|
## Related
|
|
|
|
- [[wiki/agent-sdk/mcp-integration|MCP Integration]] — connecting to external MCP servers (filesystem, GitHub, Slack)
|
|
- [[wiki/agent-sdk/configure-permissions|Configure Permissions]] — full evaluation order for allowed/disallowed tools
|
|
- [[wiki/agent-sdk/python-api-reference|Python API Reference]] — `@tool`, `create_sdk_mcp_server`, `ToolAnnotations` signatures
|
|
- [[wiki/agent-sdk/typescript-api-reference|TypeScript API Reference]] — `tool()`, Zod schemas, `ToolAnnotations`
|
|
- [[wiki/agent-sdk/structured-outputs|Structured Outputs]] — returning validated JSON from agent workflows
|
|
|
|
## Sources
|
|
|
|
- `raw/Give Claude custom tools.md` — clipped from https://code.claude.com/docs/en/agent-sdk/custom-tools
|