0
# Tools System
1
2
Tools allow LLMs to perform actions by executing Python functions, with automatic schema generation and flexible return types. The FastMCP tools system handles function introspection, parameter validation, and result formatting automatically.
3
4
## Capabilities
5
6
### Tool Classes
7
8
Base classes for creating and managing tools with schema generation and execution.
9
10
```python { .api }
11
class Tool:
12
def __init__(
13
self,
14
name: str,
15
description: str,
16
func: Callable,
17
schema: dict | None = None
18
):
19
"""
20
Base tool class.
21
22
Parameters:
23
- name: Tool name for LLM reference
24
- description: Tool description for LLM understanding
25
- func: Python function to execute
26
- schema: Optional custom JSON schema (auto-generated if None)
27
"""
28
29
class FunctionTool(Tool):
30
"""Tool implementation for function-based tools with automatic schema generation."""
31
```
32
33
### Tool Manager
34
35
Manages tool registration, retrieval, and execution within a FastMCP server.
36
37
```python { .api }
38
class ToolManager:
39
def add_tool(self, tool: Tool) -> None:
40
"""
41
Add a tool to the manager.
42
43
Parameters:
44
- tool: Tool instance to add
45
"""
46
47
def remove_tool(self, name: str) -> None:
48
"""
49
Remove a tool by name.
50
51
Parameters:
52
- name: Name of tool to remove
53
"""
54
55
def get_tool(self, name: str) -> Tool | None:
56
"""
57
Get a tool by name.
58
59
Parameters:
60
- name: Name of tool to retrieve
61
62
Returns:
63
Tool instance or None if not found
64
"""
65
66
def list_tools(self) -> list[Tool]:
67
"""
68
List all registered tools.
69
70
Returns:
71
List of all tool instances
72
"""
73
```
74
75
### Tool Transformations
76
77
Functions for transforming and forwarding tool calls to other implementations.
78
79
```python { .api }
80
def forward(
81
tool_name: str,
82
target_tool_name: str | None = None,
83
transform_args: Callable | None = None,
84
transform_result: Callable | None = None
85
) -> Callable:
86
"""
87
Forward tool calls to another tool with optional transformations.
88
89
Parameters:
90
- tool_name: Name of tool to forward
91
- target_tool_name: Target tool name (defaults to same name)
92
- transform_args: Function to transform arguments
93
- transform_result: Function to transform result
94
95
Returns:
96
Tool transformation function
97
"""
98
99
def forward_raw(
100
tool_name: str,
101
target_function: Callable,
102
transform_args: Callable | None = None,
103
transform_result: Callable | None = None
104
) -> Callable:
105
"""
106
Forward tool calls to a raw function with optional transformations.
107
108
Parameters:
109
- tool_name: Name of tool to forward
110
- target_function: Target function to call
111
- transform_args: Function to transform arguments
112
- transform_result: Function to transform result
113
114
Returns:
115
Tool transformation function
116
"""
117
```
118
119
## Usage Examples
120
121
### Basic Tool Creation
122
123
```python
124
from fastmcp import FastMCP
125
126
mcp = FastMCP("Math Server")
127
128
@mcp.tool
129
def add(a: int, b: int) -> int:
130
"""Add two numbers together."""
131
return a + b
132
133
@mcp.tool
134
def divide(a: float, b: float) -> float:
135
"""Divide two numbers with error handling."""
136
if b == 0:
137
raise ValueError("Cannot divide by zero")
138
return a / b
139
140
@mcp.tool
141
def factorial(n: int) -> int:
142
"""Calculate factorial of a number."""
143
if n < 0:
144
raise ValueError("Factorial not defined for negative numbers")
145
if n == 0 or n == 1:
146
return 1
147
result = 1
148
for i in range(2, n + 1):
149
result *= i
150
return result
151
```
152
153
### Advanced Tool with Custom Types
154
155
```python
156
from fastmcp import FastMCP
157
from fastmcp.utilities.types import Image, File
158
from typing import List, Dict, Optional
159
from dataclasses import dataclass
160
161
mcp = FastMCP("Advanced Server")
162
163
@dataclass
164
class ProcessingResult:
165
success: bool
166
message: str
167
data: Dict[str, any]
168
169
@mcp.tool
170
def process_data(
171
items: List[str],
172
options: Optional[Dict[str, str]] = None,
173
batch_size: int = 10
174
) -> ProcessingResult:
175
"""
176
Process a list of data items with configurable options.
177
178
Parameters:
179
- items: List of items to process
180
- options: Optional processing configuration
181
- batch_size: Number of items to process at once
182
183
Returns:
184
ProcessingResult with success status and processed data
185
"""
186
if not items:
187
return ProcessingResult(
188
success=False,
189
message="No items provided",
190
data={}
191
)
192
193
processed = []
194
for i in range(0, len(items), batch_size):
195
batch = items[i:i + batch_size]
196
processed.extend([item.upper() for item in batch])
197
198
return ProcessingResult(
199
success=True,
200
message=f"Processed {len(items)} items",
201
data={"processed_items": processed}
202
)
203
204
@mcp.tool
205
def create_chart(
206
data: Dict[str, List[float]],
207
title: str = "Chart"
208
) -> Image:
209
"""
210
Create a chart from data and return as image.
211
212
Parameters:
213
- data: Dictionary with series names as keys and data points as values
214
- title: Chart title
215
216
Returns:
217
Chart image as PNG
218
"""
219
# This would use matplotlib or similar to create a chart
220
import matplotlib.pyplot as plt
221
import io
222
223
plt.figure(figsize=(10, 6))
224
for series_name, values in data.items():
225
plt.plot(values, label=series_name)
226
227
plt.title(title)
228
plt.legend()
229
plt.grid(True)
230
231
# Save to bytes
232
buffer = io.BytesIO()
233
plt.savefig(buffer, format='png')
234
buffer.seek(0)
235
236
return Image(buffer.read(), mime_type="image/png")
237
```
238
239
### Tool with Context Usage
240
241
```python
242
from fastmcp import FastMCP, Context
243
import httpx
244
245
mcp = FastMCP("API Server")
246
247
@mcp.tool
248
async def fetch_and_analyze(
249
url: str,
250
analysis_prompt: str,
251
ctx: Context
252
) -> str:
253
"""
254
Fetch data from URL and analyze it using LLM.
255
256
Parameters:
257
- url: URL to fetch data from
258
- analysis_prompt: Prompt for LLM analysis
259
- ctx: Execution context for capabilities
260
261
Returns:
262
Analysis result from LLM
263
"""
264
# Log the operation
265
await ctx.info(f"Fetching data from {url}")
266
267
# Fetch data using HTTP request
268
response = await ctx.http_request("GET", url)
269
270
if response.status_code != 200:
271
await ctx.error(f"Failed to fetch data: {response.status_code}")
272
return f"Error: Unable to fetch data from {url}"
273
274
data = response.text
275
await ctx.info(f"Fetched {len(data)} characters of data")
276
277
# Use LLM sampling for analysis
278
messages = [
279
{
280
"role": "user",
281
"content": f"{analysis_prompt}\n\nData to analyze:\n{data[:1000]}..."
282
}
283
]
284
285
# Report progress
286
await ctx.report_progress(50, 100)
287
288
analysis = await ctx.sample(messages)
289
290
await ctx.report_progress(100, 100)
291
292
return analysis.text
293
294
@mcp.tool
295
async def multi_step_process(
296
input_data: str,
297
ctx: Context
298
) -> Dict[str, str]:
299
"""
300
Perform multi-step processing with progress reporting.
301
302
Parameters:
303
- input_data: Data to process
304
- ctx: Execution context
305
306
Returns:
307
Dictionary with processing results
308
"""
309
results = {}
310
total_steps = 4
311
312
# Step 1: Validation
313
await ctx.info("Step 1: Validating input")
314
await ctx.report_progress(1, total_steps)
315
if not input_data.strip():
316
raise ValueError("Input data cannot be empty")
317
results["validation"] = "passed"
318
319
# Step 2: Processing
320
await ctx.info("Step 2: Processing data")
321
await ctx.report_progress(2, total_steps)
322
processed = input_data.upper().strip()
323
results["processed"] = processed
324
325
# Step 3: Analysis
326
await ctx.info("Step 3: Analyzing results")
327
await ctx.report_progress(3, total_steps)
328
analysis = f"Data contains {len(processed)} characters"
329
results["analysis"] = analysis
330
331
# Step 4: Completion
332
await ctx.info("Step 4: Finalizing results")
333
await ctx.report_progress(4, total_steps)
334
results["status"] = "complete"
335
336
await ctx.info("Multi-step process completed successfully")
337
338
return results
339
```
340
341
### Tool Error Handling
342
343
```python
344
from fastmcp import FastMCP
345
from fastmcp.exceptions import ToolError
346
347
mcp = FastMCP("Robust Server")
348
349
@mcp.tool
350
def safe_divide(a: float, b: float) -> float:
351
"""
352
Safely divide two numbers with proper error handling.
353
354
Parameters:
355
- a: Dividend
356
- b: Divisor
357
358
Returns:
359
Result of division
360
361
Raises:
362
ToolError: If division by zero or invalid input
363
"""
364
try:
365
if not isinstance(a, (int, float)) or not isinstance(b, (int, float)):
366
raise ToolError("Both arguments must be numbers")
367
368
if b == 0:
369
raise ToolError("Division by zero is not allowed")
370
371
result = a / b
372
373
# Check for infinite or NaN results
374
if not isinstance(result, (int, float)) or result != result: # NaN check
375
raise ToolError("Division resulted in invalid number")
376
377
return result
378
379
except Exception as e:
380
if isinstance(e, ToolError):
381
raise
382
raise ToolError(f"Unexpected error during division: {str(e)}")
383
384
@mcp.tool
385
def validate_and_process(data: Dict[str, any]) -> Dict[str, any]:
386
"""
387
Validate and process input data with comprehensive error handling.
388
389
Parameters:
390
- data: Input data dictionary
391
392
Returns:
393
Processed data dictionary
394
"""
395
if not isinstance(data, dict):
396
raise ToolError("Input must be a dictionary")
397
398
required_fields = ["name", "value"]
399
missing_fields = [field for field in required_fields if field not in data]
400
401
if missing_fields:
402
raise ToolError(f"Missing required fields: {', '.join(missing_fields)}")
403
404
# Validate field types
405
if not isinstance(data["name"], str):
406
raise ToolError("Field 'name' must be a string")
407
408
if not isinstance(data["value"], (int, float)):
409
raise ToolError("Field 'value' must be a number")
410
411
# Process the data
412
return {
413
"processed_name": data["name"].strip().title(),
414
"processed_value": round(data["value"], 2),
415
"timestamp": "2024-01-01T00:00:00Z", # Would use real timestamp
416
"status": "processed"
417
}
418
```
419
420
### Tool Transformations and Forwarding
421
422
```python
423
from fastmcp import FastMCP
424
from fastmcp.tools import forward, forward_raw
425
426
mcp = FastMCP("Transform Server")
427
428
# Original tools
429
@mcp.tool
430
def original_add(a: int, b: int) -> int:
431
"""Add two integers."""
432
return a + b
433
434
@mcp.tool
435
def original_multiply(x: float, y: float) -> float:
436
"""Multiply two floats."""
437
return x * y
438
439
# Transform arguments: convert strings to numbers
440
def string_to_number_transform(args):
441
"""Transform string arguments to numbers."""
442
return {
443
key: float(value) if isinstance(value, str) and value.replace('.', '').isdigit() else value
444
for key, value in args.items()
445
}
446
447
# Transform result: add metadata
448
def add_metadata_transform(result):
449
"""Add metadata to result."""
450
return {
451
"result": result,
452
"type": type(result).__name__,
453
"timestamp": "2024-01-01T00:00:00Z"
454
}
455
456
# Apply transformations
457
mcp.add_tool_transformation(forward(
458
"string_add",
459
target_tool_name="original_add",
460
transform_args=string_to_number_transform,
461
transform_result=add_metadata_transform
462
))
463
464
# Forward to external function
465
def external_power(base: float, exponent: float) -> float:
466
"""Calculate base raised to exponent."""
467
return base ** exponent
468
469
mcp.add_tool_transformation(forward_raw(
470
"power",
471
target_function=external_power,
472
transform_result=lambda result: {"power_result": result}
473
))
474
```
475
476
## Return Types
477
478
Tools can return various types of data:
479
480
```python
481
# Simple types
482
@mcp.tool
483
def get_string() -> str:
484
return "Hello, world!"
485
486
@mcp.tool
487
def get_number() -> float:
488
return 42.5
489
490
@mcp.tool
491
def get_boolean() -> bool:
492
return True
493
494
# Complex types
495
@mcp.tool
496
def get_dict() -> Dict[str, any]:
497
return {"key": "value", "count": 10}
498
499
@mcp.tool
500
def get_list() -> List[str]:
501
return ["item1", "item2", "item3"]
502
503
# Media types
504
@mcp.tool
505
def get_image() -> Image:
506
# Return image data
507
with open("chart.png", "rb") as f:
508
return Image(f.read(), mime_type="image/png")
509
510
@mcp.tool
511
def get_file() -> File:
512
# Return file data
513
return File(
514
data="file content",
515
name="output.txt",
516
mime_type="text/plain"
517
)
518
```