0
# Custom Tools
1
2
Create custom tools that Claude can invoke using in-process MCP servers that run directly within your Python application. These provide better performance than external MCP servers, simpler deployment, and direct access to your application's state.
3
4
## Capabilities
5
6
### Tool Decorator
7
8
Decorator for defining MCP tools with type safety and automatic registration.
9
10
```python { .api }
11
def tool(
12
name: str, description: str, input_schema: type | dict[str, Any]
13
) -> Callable[[Callable[[Any], Awaitable[dict[str, Any]]]], SdkMcpTool[Any]]:
14
"""
15
Decorator for defining MCP tools with type safety.
16
17
Creates a tool that can be used with SDK MCP servers. The tool runs
18
in-process within your Python application, providing better performance
19
than external MCP servers.
20
21
Args:
22
name: Unique identifier for the tool. This is what Claude will use
23
to reference the tool in function calls.
24
description: Human-readable description of what the tool does.
25
This helps Claude understand when to use the tool.
26
input_schema: Schema defining the tool's input parameters.
27
Can be either:
28
- A dictionary mapping parameter names to types (e.g., {"text": str})
29
- A TypedDict class for more complex schemas
30
- A JSON Schema dictionary for full validation
31
32
Returns:
33
A decorator function that wraps the tool implementation and returns
34
an SdkMcpTool instance ready for use with create_sdk_mcp_server().
35
"""
36
```
37
38
### SDK MCP Tool
39
40
Definition structure for an SDK MCP tool containing metadata and handler function.
41
42
```python { .api }
43
@dataclass
44
class SdkMcpTool(Generic[T]):
45
"""Definition for an SDK MCP tool."""
46
47
name: str
48
description: str
49
input_schema: type[T] | dict[str, Any]
50
handler: Callable[[T], Awaitable[dict[str, Any]]]
51
```
52
53
### SDK MCP Server Creation
54
55
Create an in-process MCP server that runs within your Python application.
56
57
```python { .api }
58
def create_sdk_mcp_server(
59
name: str, version: str = "1.0.0", tools: list[SdkMcpTool[Any]] | None = None
60
) -> McpSdkServerConfig:
61
"""
62
Create an in-process MCP server that runs within your Python application.
63
64
Unlike external MCP servers that run as separate processes, SDK MCP servers
65
run directly in your application's process. This provides:
66
- Better performance (no IPC overhead)
67
- Simpler deployment (single process)
68
- Easier debugging (same process)
69
- Direct access to your application's state
70
71
Args:
72
name: Unique identifier for the server. This name is used to reference
73
the server in the mcp_servers configuration.
74
version: Server version string. Defaults to "1.0.0". This is for
75
informational purposes and doesn't affect functionality.
76
tools: List of SdkMcpTool instances created with the @tool decorator.
77
These are the functions that Claude can call through this server.
78
If None or empty, the server will have no tools (rarely useful).
79
80
Returns:
81
McpSdkServerConfig: A configuration object that can be passed to
82
ClaudeCodeOptions.mcp_servers. This config contains the server
83
instance and metadata needed for the SDK to route tool calls.
84
"""
85
```
86
87
## Usage Examples
88
89
### Basic Tool Creation
90
91
```python
92
from claude_code_sdk import tool, create_sdk_mcp_server, ClaudeCodeOptions, ClaudeSDKClient
93
94
@tool("greet", "Greet a user", {"name": str})
95
async def greet_user(args):
96
return {
97
"content": [
98
{"type": "text", "text": f"Hello, {args['name']}!"}
99
]
100
}
101
102
async def main():
103
# Create an SDK MCP server
104
server = create_sdk_mcp_server(
105
name="my-tools",
106
version="1.0.0",
107
tools=[greet_user]
108
)
109
110
# Use it with Claude
111
options = ClaudeCodeOptions(
112
mcp_servers={"tools": server},
113
allowed_tools=["mcp__tools__greet"] # Note the naming convention
114
)
115
116
async with ClaudeSDKClient(options=options) as client:
117
await client.query("Greet Alice")
118
119
async for msg in client.receive_response():
120
print(msg)
121
```
122
123
### Tool with Multiple Parameters
124
125
```python
126
@tool("add", "Add two numbers", {"a": float, "b": float})
127
async def add_numbers(args):
128
result = args["a"] + args["b"]
129
return {
130
"content": [
131
{"type": "text", "text": f"Result: {result}"}
132
]
133
}
134
135
@tool("multiply", "Multiply two numbers", {"a": float, "b": float})
136
async def multiply_numbers(args):
137
result = args["a"] * args["b"]
138
return {
139
"content": [
140
{"type": "text", "text": f"Product: {result}"}
141
]
142
}
143
144
async def main():
145
calculator = create_sdk_mcp_server(
146
name="calculator",
147
version="2.0.0",
148
tools=[add_numbers, multiply_numbers]
149
)
150
151
options = ClaudeCodeOptions(
152
mcp_servers={"calc": calculator},
153
allowed_tools=["mcp__calc__add", "mcp__calc__multiply"]
154
)
155
156
async with ClaudeSDKClient(options=options) as client:
157
await client.query("Calculate 15 + 27, then multiply the result by 3")
158
159
async for msg in client.receive_response():
160
print(msg)
161
```
162
163
### Tool with Error Handling
164
165
```python
166
@tool("divide", "Divide two numbers", {"a": float, "b": float})
167
async def divide_numbers(args):
168
if args["b"] == 0:
169
return {
170
"content": [
171
{"type": "text", "text": "Error: Division by zero"}
172
],
173
"is_error": True
174
}
175
176
result = args["a"] / args["b"]
177
return {
178
"content": [
179
{"type": "text", "text": f"Result: {result}"}
180
]
181
}
182
```
183
184
### Tool with Complex Schema
185
186
```python
187
from typing_extensions import TypedDict
188
189
class SearchParameters(TypedDict):
190
query: str
191
max_results: int
192
include_metadata: bool
193
194
@tool("search", "Search for items", SearchParameters)
195
async def search_items(args):
196
# Access typed parameters
197
query = args["query"]
198
max_results = args.get("max_results", 10)
199
include_metadata = args.get("include_metadata", False)
200
201
# Perform search logic
202
results = perform_search(query, max_results, include_metadata)
203
204
return {
205
"content": [
206
{"type": "text", "text": f"Found {len(results)} results for '{query}'"}
207
]
208
}
209
210
# Alternative: JSON Schema approach
211
json_schema = {
212
"type": "object",
213
"properties": {
214
"query": {"type": "string", "description": "Search query"},
215
"max_results": {"type": "integer", "minimum": 1, "maximum": 100, "default": 10},
216
"include_metadata": {"type": "boolean", "default": False}
217
},
218
"required": ["query"]
219
}
220
221
@tool("advanced_search", "Advanced search with JSON schema", json_schema)
222
async def advanced_search(args):
223
# Implementation
224
pass
225
```
226
227
### Tool with Application State Access
228
229
```python
230
class DataStore:
231
def __init__(self):
232
self.items = []
233
self.counter = 0
234
235
# Global application state
236
app_store = DataStore()
237
238
@tool("add_item", "Add item to store", {"item": str})
239
async def add_item(args):
240
app_store.items.append(args["item"])
241
app_store.counter += 1
242
return {
243
"content": [
244
{"type": "text", "text": f"Added: {args['item']} (total: {app_store.counter})"}
245
]
246
}
247
248
@tool("list_items", "List all items in store", {})
249
async def list_items(args):
250
if not app_store.items:
251
return {
252
"content": [
253
{"type": "text", "text": "No items in store"}
254
]
255
}
256
257
items_text = "\n".join(f"- {item}" for item in app_store.items)
258
return {
259
"content": [
260
{"type": "text", "text": f"Items in store:\n{items_text}"}
261
]
262
}
263
264
async def main():
265
server = create_sdk_mcp_server(
266
name="store",
267
tools=[add_item, list_items]
268
)
269
270
options = ClaudeCodeOptions(
271
mcp_servers={"store": server},
272
allowed_tools=["mcp__store__add_item", "mcp__store__list_items"]
273
)
274
275
async with ClaudeSDKClient(options=options) as client:
276
await client.query("Add 'apple' to the store, then list all items")
277
278
async for msg in client.receive_response():
279
print(msg)
280
```
281
282
### Mixed Server Configuration
283
284
You can use both SDK and external MCP servers together:
285
286
```python
287
# SDK server for in-process tools
288
internal_server = create_sdk_mcp_server(
289
name="internal",
290
tools=[my_custom_tool]
291
)
292
293
# External server configuration
294
external_server_config = {
295
"type": "stdio",
296
"command": "external-mcp-server",
297
"args": ["--config", "config.json"]
298
}
299
300
options = ClaudeCodeOptions(
301
mcp_servers={
302
"internal": internal_server, # In-process SDK server
303
"external": external_server_config # External subprocess server
304
},
305
allowed_tools=[
306
"mcp__internal__my_custom_tool",
307
"mcp__external__some_external_tool"
308
]
309
)
310
```
311
312
## Tool Naming Convention
313
314
When using SDK MCP servers, tools are referenced using the pattern:
315
`mcp__<server_name>__<tool_name>`
316
317
For example:
318
- Server named "calculator" with tool "add" → `mcp__calculator__add`
319
- Server named "my-tools" with tool "greet" → `mcp__my-tools__greet`
320
321
## Benefits Over External MCP Servers
322
323
**Performance:**
324
- No subprocess or IPC overhead
325
- Direct function calls within the same process
326
- Faster tool execution and response times
327
328
**Deployment:**
329
- Single Python process instead of multiple processes
330
- No need to manage external server lifecycles
331
- Simplified packaging and distribution
332
333
**Development:**
334
- Direct access to your application's variables and state
335
- Same debugging environment as your main application
336
- No cross-process communication complexity
337
- Standard Python exception handling
338
339
**Type Safety:**
340
- Direct Python function calls with type hints
341
- IDE support for tool function definitions
342
- Compile-time type checking
343
344
## Tool Function Requirements
345
346
All tool functions must:
347
348
1. **Be async**: Defined with `async def`
349
2. **Accept single dict argument**: Receive input parameters as a dictionary
350
3. **Return dict with content**: Return response in the expected format
351
4. **Handle errors appropriately**: Use `"is_error": True` for error responses
352
353
**Response Format:**
354
```python
355
{
356
"content": [
357
{"type": "text", "text": "Response text"}
358
],
359
"is_error": False # Optional, defaults to False
360
}
361
```
362
363
## Integration with Configuration
364
365
SDK MCP servers are configured through the `ClaudeCodeOptions.mcp_servers` field and work seamlessly with all other Claude Code SDK features including permission systems, hooks, and transport customization.
366
367
See [Configuration and Options](./configuration-options.md) for complete configuration details.