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