# Useful but Little-Known Features of Claude Agent SDK
🌍 Define custom functions as agent tools running in-process using the
@tool decorator and create_sdk_mcp_server.
Give your agents access to databases, external APIs, and domain-specific logic without managing separate processes.
📌 Title: In-Process Custom Tools (create_sdk_mcp_server /
@tool)
🔗 URL:
🧩 Overview
Custom tools let you define your own functions that Claude can call during conversations. In Python, use the `
@tool` decorator; in TypeScript, use the `tool()` helper. Define a name, description, input schema, and async handler, then wrap tools in `create_sdk_mcp_server` and pass them to `query()`. Tools run in-process — no separate process needed. Tool names follow the `mcp__{server_name}__{tool_name}` convention. Use `readOnlyHint` annotations to enable parallel execution, `isError` for recoverable failures, and return images (base64), resources, or structured content.
🛠 Usage
Define tools with the `
@tool` decorator and bundle them into an in-process MCP server.
```python
from typing import Any
import httpx
from claude_agent_sdk import tool, create_sdk_mcp_server, query, ClaudeAgentOptions, ResultMessage
@tool(
"get_temperature",
"Get the current temperature at a location",
{"latitude": float, "longitude": float},
)
async def get_temperature(args: dict[str, Any]) -> dict[str, Any]:
async with httpx.AsyncClient() as client:
response = await client.get(
"",
params={
"latitude": args["latitude"],
"longitude": args["longitude"],
"current": "temperature_2m",
},
)
data = response.json()
return {
"content": [
{"type": "text", "text": f"Temperature: {data['current']['temperature_2m']}"}
]
}
# Wrap in an in-process MCP server
weather_server = create_sdk_mcp_server(
name="weather",
version="1.0.0",
tools=[get_temperature],
)
# Pass to query()
async for message in query(
prompt="What's the temperature in Tokyo?",
options=ClaudeAgentOptions(
mcp_servers={"weather": weather_server},
allowed_tools=["mcp__weather__get_temperature"],
),
):
if isinstance(message, ResultMessage) and message.subtype == "success":
print(message.result)
```
🏗 Integration into Production Systems
- Return `{"is_error": True}` instead of throwing exceptions to keep the agent loop alive and let Claude attempt recovery
- Set `readOnlyHint=True` on side-effect-free tools to enable parallel execution with other read-only tools
- Return images via `{"type": "image", "data": base64_str, "mimeType": "image/png"}` content blocks
- Use `structuredContent` to return machine-readable JSON alongside content (note: not supported by Python `
@tool` — requires standalone MCP server)
💡 Use Cases
🗄 Database access: let agents execute DB queries directly
🌐 External API integration: expose internal APIs or third-party services as tools
📊 Data visualization: generate charts and return them as base64 images
🔧 Domain logic: encapsulate calculations, conversions, and validations as callable tools
⚠️ Caveats
- Python dict schema treats all keys as required; for optional parameters, omit from schema, describe in the description, and read with `args.get()`
- `structuredContent` is not forwarded by Python's `
@tool` decorator (use a standalone MCP server)
- Tool search is on by default and defers SDK MCP tool schemas; with it disabled, every tool in the `tools` array consumes context on every turn
- Annotations are metadata, not enforcement — a `readOnlyHint=True` tool can still write if the handler does so
✨ In-process custom tools let you freely extend agent capabilities with your own functions, without the overhead of managing external processes.
#
ClaudeAgentSDK# #
AIAgent#