Jeremiah Lowin commited on
Commit
09438a8
·
1 Parent(s): 26ff0e9

Remove empty parens

Browse files
README.md CHANGED
@@ -31,7 +31,7 @@ from fastmcp import FastMCP
31
 
32
  mcp = FastMCP("Demo 🚀")
33
 
34
- @mcp.tool()
35
  def add(a: int, b: int) -> int:
36
  """Add two numbers"""
37
  return a + b
@@ -144,7 +144,7 @@ Learn more in the [**FastMCP Server Documentation**](https://gofastmcp.com/serve
144
  Tools allow LLMs to perform actions by executing your Python functions (sync or async). Ideal for computations, API calls, or side effects (like `POST`/`PUT`). FastMCP handles schema generation from type hints and docstrings. Tools can return various types, including text, JSON-serializable objects, and even images using the [`fastmcp.Image`](https://gofastmcp.com/servers/tools#return-values) helper.
145
 
146
  ```python
147
- @mcp.tool()
148
  def multiply(a: float, b: float) -> float:
149
  """Multiplies two numbers."""
150
  return a * b
@@ -201,7 +201,7 @@ from fastmcp import FastMCP, Context
201
 
202
  mcp = FastMCP("My MCP Server")
203
 
204
- @mcp.tool()
205
  async def process_data(uri: str, ctx: Context):
206
  # Log a message to the client
207
  await ctx.info(f"Processing {uri}...")
@@ -321,7 +321,7 @@ from fastmcp import FastMCP
321
 
322
  mcp = FastMCP("Demo 🚀")
323
 
324
- @mcp.tool()
325
  def hello(name: str) -> str:
326
  return f"Hello, {name}!"
327
 
 
31
 
32
  mcp = FastMCP("Demo 🚀")
33
 
34
+ @mcp.tool
35
  def add(a: int, b: int) -> int:
36
  """Add two numbers"""
37
  return a + b
 
144
  Tools allow LLMs to perform actions by executing your Python functions (sync or async). Ideal for computations, API calls, or side effects (like `POST`/`PUT`). FastMCP handles schema generation from type hints and docstrings. Tools can return various types, including text, JSON-serializable objects, and even images using the [`fastmcp.Image`](https://gofastmcp.com/servers/tools#return-values) helper.
145
 
146
  ```python
147
+ @mcp.tool
148
  def multiply(a: float, b: float) -> float:
149
  """Multiplies two numbers."""
150
  return a * b
 
201
 
202
  mcp = FastMCP("My MCP Server")
203
 
204
+ @mcp.tool
205
  async def process_data(uri: str, ctx: Context):
206
  # Log a message to the client
207
  await ctx.info(f"Processing {uri}...")
 
321
 
322
  mcp = FastMCP("Demo 🚀")
323
 
324
+ @mcp.tool
325
  def hello(name: str) -> str:
326
  return f"Hello, {name}!"
327
 
docs/deployment/asgi.mdx CHANGED
@@ -32,7 +32,7 @@ from fastmcp import FastMCP
32
 
33
  mcp = FastMCP("MyServer")
34
 
35
- @mcp.tool()
36
  def hello(name: str) -> str:
37
  return f"Hello, {name}!"
38
 
 
32
 
33
  mcp = FastMCP("MyServer")
34
 
35
+ @mcp.tool
36
  def hello(name: str) -> str:
37
  return f"Hello, {name}!"
38
 
docs/deployment/running-server.mdx CHANGED
@@ -22,7 +22,7 @@ from fastmcp import FastMCP
22
 
23
  mcp = FastMCP(name="MyServer")
24
 
25
- @mcp.tool()
26
  def hello(name: str) -> str:
27
  return f"Hello, {name}!"
28
 
@@ -244,7 +244,7 @@ import asyncio
244
 
245
  mcp = FastMCP(name="MyServer")
246
 
247
- @mcp.tool()
248
  def hello(name: str) -> str:
249
  return f"Hello, {name}!"
250
 
 
22
 
23
  mcp = FastMCP(name="MyServer")
24
 
25
+ @mcp.tool
26
  def hello(name: str) -> str:
27
  return f"Hello, {name}!"
28
 
 
244
 
245
  mcp = FastMCP(name="MyServer")
246
 
247
+ @mcp.tool
248
  def hello(name: str) -> str:
249
  return f"Hello, {name}!"
250
 
docs/getting-started/quickstart.mdx CHANGED
@@ -32,7 +32,7 @@ from fastmcp import FastMCP
32
 
33
  mcp = FastMCP("My MCP Server")
34
 
35
- @mcp.tool()
36
  def greet(name: str) -> str:
37
  return f"Hello, {name}!"
38
  ```
@@ -49,7 +49,7 @@ from fastmcp import FastMCP, Client
49
 
50
  mcp = FastMCP("My MCP Server")
51
 
52
- @mcp.tool()
53
  def greet(name: str) -> str:
54
  return f"Hello, {name}!"
55
 
@@ -76,7 +76,7 @@ from fastmcp import FastMCP
76
 
77
  mcp = FastMCP("My MCP Server")
78
 
79
- @mcp.tool()
80
  def greet(name: str) -> str:
81
  return f"Hello, {name}!"
82
 
 
32
 
33
  mcp = FastMCP("My MCP Server")
34
 
35
+ @mcp.tool
36
  def greet(name: str) -> str:
37
  return f"Hello, {name}!"
38
  ```
 
49
 
50
  mcp = FastMCP("My MCP Server")
51
 
52
+ @mcp.tool
53
  def greet(name: str) -> str:
54
  return f"Hello, {name}!"
55
 
 
76
 
77
  mcp = FastMCP("My MCP Server")
78
 
79
+ @mcp.tool
80
  def greet(name: str) -> str:
81
  return f"Hello, {name}!"
82
 
docs/getting-started/welcome.mdx CHANGED
@@ -14,7 +14,7 @@ from fastmcp import FastMCP
14
 
15
  mcp = FastMCP("Demo 🚀")
16
 
17
- @mcp.tool()
18
  def add(a: int, b: int) -> int:
19
  """Add two numbers"""
20
  return a + b
 
14
 
15
  mcp = FastMCP("Demo 🚀")
16
 
17
+ @mcp.tool
18
  def add(a: int, b: int) -> int:
19
  """Add two numbers"""
20
  return a + b
docs/integrations/anthropic.mdx CHANGED
@@ -27,7 +27,7 @@ from fastmcp import FastMCP
27
 
28
  mcp = FastMCP(name="Dice Roller")
29
 
30
- @mcp.tool()
31
  def roll_dice(n_dice: int) -> list[int]:
32
  """Roll `n_dice` 6-sided dice and return the results."""
33
  return [random.randint(1, 6) for _ in range(n_dice)]
@@ -170,7 +170,7 @@ auth = BearerAuthProvider(
170
 
171
  mcp = FastMCP(name="Dice Roller", auth=auth)
172
 
173
- @mcp.tool()
174
  def roll_dice(n_dice: int) -> list[int]:
175
  """Roll `n_dice` 6-sided dice and return the results."""
176
  return [random.randint(1, 6) for _ in range(n_dice)]
 
27
 
28
  mcp = FastMCP(name="Dice Roller")
29
 
30
+ @mcp.tool
31
  def roll_dice(n_dice: int) -> list[int]:
32
  """Roll `n_dice` 6-sided dice and return the results."""
33
  return [random.randint(1, 6) for _ in range(n_dice)]
 
170
 
171
  mcp = FastMCP(name="Dice Roller", auth=auth)
172
 
173
+ @mcp.tool
174
  def roll_dice(n_dice: int) -> list[int]:
175
  """Roll `n_dice` 6-sided dice and return the results."""
176
  return [random.randint(1, 6) for _ in range(n_dice)]
docs/integrations/claude-desktop.mdx CHANGED
@@ -31,7 +31,7 @@ from fastmcp import FastMCP
31
 
32
  mcp = FastMCP(name="Dice Roller")
33
 
34
- @mcp.tool()
35
  def roll_dice(n_dice: int) -> list[int]:
36
  """Roll `n_dice` 6-sided dice and return the results."""
37
  return [random.randint(1, 6) for _ in range(n_dice)]
 
31
 
32
  mcp = FastMCP(name="Dice Roller")
33
 
34
+ @mcp.tool
35
  def roll_dice(n_dice: int) -> list[int]:
36
  """Roll `n_dice` 6-sided dice and return the results."""
37
  return [random.randint(1, 6) for _ in range(n_dice)]
docs/integrations/gemini.mdx CHANGED
@@ -31,7 +31,7 @@ from fastmcp import FastMCP
31
 
32
  mcp = FastMCP(name="Dice Roller")
33
 
34
- @mcp.tool()
35
  def roll_dice(n_dice: int) -> list[int]:
36
  """Roll `n_dice` 6-sided dice and return the results."""
37
  return [random.randint(1, 6) for _ in range(n_dice)]
 
31
 
32
  mcp = FastMCP(name="Dice Roller")
33
 
34
+ @mcp.tool
35
  def roll_dice(n_dice: int) -> list[int]:
36
  """Roll `n_dice` 6-sided dice and return the results."""
37
  return [random.randint(1, 6) for _ in range(n_dice)]
docs/integrations/openai.mdx CHANGED
@@ -32,7 +32,7 @@ from fastmcp import FastMCP
32
 
33
  mcp = FastMCP(name="Dice Roller")
34
 
35
- @mcp.tool()
36
  def roll_dice(n_dice: int) -> list[int]:
37
  """Roll `n_dice` 6-sided dice and return the results."""
38
  return [random.randint(1, 6) for _ in range(n_dice)]
@@ -165,7 +165,7 @@ auth = BearerAuthProvider(
165
 
166
  mcp = FastMCP(name="Dice Roller", auth=auth)
167
 
168
- @mcp.tool()
169
  def roll_dice(n_dice: int) -> list[int]:
170
  """Roll `n_dice` 6-sided dice and return the results."""
171
  return [random.randint(1, 6) for _ in range(n_dice)]
 
32
 
33
  mcp = FastMCP(name="Dice Roller")
34
 
35
+ @mcp.tool
36
  def roll_dice(n_dice: int) -> list[int]:
37
  """Roll `n_dice` 6-sided dice and return the results."""
38
  return [random.randint(1, 6) for _ in range(n_dice)]
 
165
 
166
  mcp = FastMCP(name="Dice Roller", auth=auth)
167
 
168
+ @mcp.tool
169
  def roll_dice(n_dice: int) -> list[int]:
170
  """Roll `n_dice` 6-sided dice and return the results."""
171
  return [random.randint(1, 6) for _ in range(n_dice)]
docs/patterns/cli.mdx CHANGED
@@ -66,7 +66,7 @@ from fastmcp import FastMCP
66
 
67
  mcp = FastMCP("MyServer")
68
 
69
- @mcp.tool()
70
  def hello(name: str) -> str:
71
  return f"Hello, {name}!"
72
 
 
66
 
67
  mcp = FastMCP("MyServer")
68
 
69
+ @mcp.tool
70
  def hello(name: str) -> str:
71
  return f"Hello, {name}!"
72
 
docs/patterns/http-requests.mdx CHANGED
@@ -25,7 +25,7 @@ from starlette.requests import Request
25
 
26
  mcp = FastMCP(name="HTTP Request Demo")
27
 
28
- @mcp.tool()
29
  async def user_agent_info() -> dict:
30
  """Return information about the user agent."""
31
  # Get the HTTP request
@@ -58,7 +58,7 @@ from fastmcp.server.dependencies import get_http_headers
58
 
59
  mcp = FastMCP(name="Headers Demo")
60
 
61
- @mcp.tool()
62
  async def safe_header_info() -> dict:
63
  """Safely get header information without raising errors."""
64
  # Get headers (returns empty dict if no request context)
 
25
 
26
  mcp = FastMCP(name="HTTP Request Demo")
27
 
28
+ @mcp.tool
29
  async def user_agent_info() -> dict:
30
  """Return information about the user agent."""
31
  # Get the HTTP request
 
58
 
59
  mcp = FastMCP(name="Headers Demo")
60
 
61
+ @mcp.tool
62
  async def safe_header_info() -> dict:
63
  """Safely get header information without raising errors."""
64
  # Get headers (returns empty dict if no request context)
docs/servers/auth/bearer.mdx CHANGED
@@ -159,7 +159,7 @@ Once authenticated, your tools, resources, or prompts can access token informati
159
  from fastmcp import FastMCP, Context, ToolError
160
  from fastmcp.server.dependencies import get_access_token, AccessToken
161
 
162
- @mcp.tool()
163
  async def get_my_data(ctx: Context) -> dict:
164
  access_token: AccessToken = get_access_token()
165
 
 
159
  from fastmcp import FastMCP, Context, ToolError
160
  from fastmcp.server.dependencies import get_access_token, AccessToken
161
 
162
+ @mcp.tool
163
  async def get_my_data(ctx: Context) -> dict:
164
  access_token: AccessToken = get_access_token()
165
 
docs/servers/context.mdx CHANGED
@@ -41,7 +41,7 @@ from fastmcp import FastMCP, Context
41
 
42
  mcp = FastMCP(name="ContextDemo")
43
 
44
- @mcp.tool()
45
  async def process_file(file_uri: str, ctx: Context) -> str:
46
  """Processes a file, using context for logging and resource access."""
47
  # Context is available as the ctx parameter
@@ -99,7 +99,7 @@ async def process_data(data: list[float]) -> dict:
99
  ctx = get_context()
100
  await ctx.info(f"Processing {len(data)} data points")
101
 
102
- @mcp.tool()
103
  async def analyze_dataset(dataset_name: str) -> dict:
104
  # Call utility function that uses context internally
105
  data = load_data(dataset_name)
@@ -118,7 +118,7 @@ async def analyze_dataset(dataset_name: str) -> dict:
118
  Send log messages back to the MCP client. This is useful for debugging and providing visibility into function execution during a request.
119
 
120
  ```python
121
- @mcp.tool()
122
  async def analyze_data(data: list[float], ctx: Context) -> dict:
123
  """Analyze numerical data with logging."""
124
  await ctx.debug("Starting analysis of numerical data")
@@ -149,7 +149,7 @@ async def analyze_data(data: list[float], ctx: Context) -> dict:
149
  For long-running operations, notify the client about the progress. This allows clients to display progress indicators and provide a better user experience.
150
 
151
  ```python
152
- @mcp.tool()
153
  async def process_items(items: list[str], ctx: Context) -> dict:
154
  """Process a list of items with progress updates."""
155
  total = len(items)
@@ -182,7 +182,7 @@ Progress reporting requires the client to have sent a `progressToken` in the ini
182
  Read data from resources registered with your FastMCP server. This allows functions to access files, configuration, or dynamically generated content.
183
 
184
  ```python
185
- @mcp.tool()
186
  async def summarize_document(document_uri: str, ctx: Context) -> str:
187
  """Summarize a document by its resource URI."""
188
  # Read the document content
@@ -222,7 +222,7 @@ The returned content is typically accessed via `content_list[0].content` and can
222
  Request the client's LLM to generate text based on provided messages. This is useful when your function needs to leverage the LLM's capabilities to process data or generate responses.
223
 
224
  ```python
225
- @mcp.tool()
226
  async def analyze_sentiment(text: str, ctx: Context) -> dict:
227
  """Analyze the sentiment of a text using the client's LLM."""
228
  # Create a sampling prompt asking for sentiment analysis
@@ -258,7 +258,7 @@ async def analyze_sentiment(text: str, ctx: Context) -> dict:
258
  When providing a simple string, it's treated as a user message. For more complex scenarios, you can provide a list of messages with different roles.
259
 
260
  ```python
261
- @mcp.tool()
262
  async def generate_example(concept: str, ctx: Context) -> str:
263
  """Generate a Python code example for a given concept."""
264
  # Using a system prompt and a user message
@@ -280,7 +280,7 @@ See [Client Sampling](/clients/client#llm-sampling) for more details on how clie
280
  Access metadata about the current request and client.
281
 
282
  ```python
283
- @mcp.tool()
284
  async def request_info(ctx: Context) -> dict:
285
  """Return information about the current request."""
286
  return {
@@ -300,7 +300,7 @@ async def request_info(ctx: Context) -> dict:
300
  #### FastMCP Server and Sessions
301
 
302
  ```python
303
- @mcp.tool()
304
  async def advanced_tool(ctx: Context) -> str:
305
  """Demonstrate advanced context access."""
306
  # Access the FastMCP server instance
@@ -326,7 +326,7 @@ See the [HTTP Requests pattern](/patterns/http-requests) for more details.
326
  For web applications, you can access the underlying HTTP request:
327
 
328
  ```python
329
- @mcp.tool()
330
  async def handle_web_request(ctx: Context) -> dict:
331
  """Access HTTP request information from the Starlette request."""
332
  request = ctx.get_http_request()
 
41
 
42
  mcp = FastMCP(name="ContextDemo")
43
 
44
+ @mcp.tool
45
  async def process_file(file_uri: str, ctx: Context) -> str:
46
  """Processes a file, using context for logging and resource access."""
47
  # Context is available as the ctx parameter
 
99
  ctx = get_context()
100
  await ctx.info(f"Processing {len(data)} data points")
101
 
102
+ @mcp.tool
103
  async def analyze_dataset(dataset_name: str) -> dict:
104
  # Call utility function that uses context internally
105
  data = load_data(dataset_name)
 
118
  Send log messages back to the MCP client. This is useful for debugging and providing visibility into function execution during a request.
119
 
120
  ```python
121
+ @mcp.tool
122
  async def analyze_data(data: list[float], ctx: Context) -> dict:
123
  """Analyze numerical data with logging."""
124
  await ctx.debug("Starting analysis of numerical data")
 
149
  For long-running operations, notify the client about the progress. This allows clients to display progress indicators and provide a better user experience.
150
 
151
  ```python
152
+ @mcp.tool
153
  async def process_items(items: list[str], ctx: Context) -> dict:
154
  """Process a list of items with progress updates."""
155
  total = len(items)
 
182
  Read data from resources registered with your FastMCP server. This allows functions to access files, configuration, or dynamically generated content.
183
 
184
  ```python
185
+ @mcp.tool
186
  async def summarize_document(document_uri: str, ctx: Context) -> str:
187
  """Summarize a document by its resource URI."""
188
  # Read the document content
 
222
  Request the client's LLM to generate text based on provided messages. This is useful when your function needs to leverage the LLM's capabilities to process data or generate responses.
223
 
224
  ```python
225
+ @mcp.tool
226
  async def analyze_sentiment(text: str, ctx: Context) -> dict:
227
  """Analyze the sentiment of a text using the client's LLM."""
228
  # Create a sampling prompt asking for sentiment analysis
 
258
  When providing a simple string, it's treated as a user message. For more complex scenarios, you can provide a list of messages with different roles.
259
 
260
  ```python
261
+ @mcp.tool
262
  async def generate_example(concept: str, ctx: Context) -> str:
263
  """Generate a Python code example for a given concept."""
264
  # Using a system prompt and a user message
 
280
  Access metadata about the current request and client.
281
 
282
  ```python
283
+ @mcp.tool
284
  async def request_info(ctx: Context) -> dict:
285
  """Return information about the current request."""
286
  return {
 
300
  #### FastMCP Server and Sessions
301
 
302
  ```python
303
+ @mcp.tool
304
  async def advanced_tool(ctx: Context) -> str:
305
  """Demonstrate advanced context access."""
306
  # Access the FastMCP server instance
 
326
  For web applications, you can access the underlying HTTP request:
327
 
328
  ```python
329
+ @mcp.tool
330
  async def handle_web_request(ctx: Context) -> dict:
331
  """Access HTTP request information from the Starlette request."""
332
  request = ctx.get_http_request()
docs/servers/fastmcp.mdx CHANGED
@@ -47,7 +47,7 @@ FastMCP servers expose several types of components to the client:
47
  Tools are functions that the client can call to perform actions or access external systems.
48
 
49
  ```python
50
- @mcp.tool()
51
  def multiply(a: float, b: float) -> float:
52
  """Multiplies two numbers together."""
53
  return a * b
@@ -106,7 +106,7 @@ from fastmcp import FastMCP
106
 
107
  mcp = FastMCP(name="MyServer")
108
 
109
- @mcp.tool()
110
  def greet(name: str) -> str:
111
  """Greet a user by name."""
112
  return f"Hello, {name}!"
@@ -216,7 +216,7 @@ def yaml_serializer(data):
216
  # Create a server with the custom serializer
217
  mcp = FastMCP(name="MyServer", tool_serializer=yaml_serializer)
218
 
219
- @mcp.tool()
220
  def get_config():
221
  """Returns configuration in YAML format."""
222
  return {"api_key": "abc123", "debug": True, "rate_limit": 100}
 
47
  Tools are functions that the client can call to perform actions or access external systems.
48
 
49
  ```python
50
+ @mcp.tool
51
  def multiply(a: float, b: float) -> float:
52
  """Multiplies two numbers together."""
53
  return a * b
 
106
 
107
  mcp = FastMCP(name="MyServer")
108
 
109
+ @mcp.tool
110
  def greet(name: str) -> str:
111
  """Greet a user by name."""
112
  return f"Hello, {name}!"
 
216
  # Create a server with the custom serializer
217
  mcp = FastMCP(name="MyServer", tool_serializer=yaml_serializer)
218
 
219
+ @mcp.tool
220
  def get_config():
221
  """Returns configuration in YAML format."""
222
  return {"api_key": "abc123", "debug": True, "rate_limit": 100}
docs/servers/tools.mdx CHANGED
@@ -24,14 +24,14 @@ This allows LLMs to perform tasks like querying databases, calling APIs, making
24
 
25
  ### The `@tool` Decorator
26
 
27
- Creating a tool is as simple as decorating a Python function with `@mcp.tool()`:
28
 
29
  ```python
30
  from fastmcp import FastMCP
31
 
32
  mcp = FastMCP(name="CalculatorServer")
33
 
34
- @mcp.tool()
35
  def add(a: int, b: int) -> int:
36
  """Adds two integer numbers together."""
37
  return a + b
@@ -61,7 +61,7 @@ Type annotations for parameters are essential for proper tool functionality. The
61
  Use standard Python type annotations for parameters:
62
 
63
  ```python
64
- @mcp.tool()
65
  def analyze_text(
66
  text: str,
67
  max_tokens: int = 100,
@@ -79,7 +79,7 @@ You can provide additional metadata about parameters using Pydantic's `Field` cl
79
  from typing import Annotated
80
  from pydantic import Field
81
 
82
- @mcp.tool()
83
  def process_image(
84
  image_url: Annotated[str, Field(description="URL of the image to process")],
85
  resize: Annotated[bool, Field(description="Whether to resize the image")] = False,
@@ -97,7 +97,7 @@ def process_image(
97
  You can also use the Field as a default value, though the Annotated approach is preferred:
98
 
99
  ```python
100
- @mcp.tool()
101
  def search_database(
102
  query: str = Field(description="Search query string"),
103
  limit: int = Field(10, description="Maximum number of results", ge=1, le=100)
@@ -137,7 +137,7 @@ For additional type annotations not listed here, see the [Parameter Types](#para
137
  FastMCP follows Python's standard function parameter conventions. Parameters without default values are required, while those with default values are optional.
138
 
139
  ```python
140
- @mcp.tool()
141
  def search_products(
142
  query: str, # Required - no default value
143
  max_results: int = 10, # Optional - has default value
@@ -197,14 +197,14 @@ FastMCP seamlessly supports both standard (`def`) and asynchronous (`async def`)
197
 
198
  ```python
199
  # Synchronous tool (suitable for CPU-bound or quick tasks)
200
- @mcp.tool()
201
  def calculate_distance(lat1: float, lon1: float, lat2: float, lon2: float) -> float:
202
  """Calculate the distance between two coordinates."""
203
  # Implementation...
204
  return 42.5
205
 
206
  # Asynchronous tool (ideal for I/O-bound operations)
207
- @mcp.tool()
208
  async def fetch_weather(city: str) -> dict:
209
  """Retrieve current weather conditions for a city."""
210
  # Use 'async def' for operations involving network calls, file I/O, etc.
@@ -244,7 +244,7 @@ except ImportError:
244
 
245
  mcp = FastMCP("Image Demo")
246
 
247
- @mcp.tool()
248
  def generate_image(width: int, height: int, color: str) -> Image:
249
  """Generates a solid color image."""
250
  # Create image using Pillow
@@ -258,7 +258,7 @@ def generate_image(width: int, height: int, color: str) -> Image:
258
  # Return using FastMCP's Image helper
259
  return Image(data=img_bytes, format="png")
260
 
261
- @mcp.tool()
262
  def do_nothing() -> None:
263
  """This tool performs an action but returns no data."""
264
  print("Performing a side effect...")
@@ -285,7 +285,7 @@ mcp = FastMCP(name="SecureServer", mask_error_details=True)
285
  from fastmcp import FastMCP
286
  from fastmcp.exceptions import ToolError
287
 
288
- @mcp.tool()
289
  def divide(a: float, b: float) -> float:
290
  """Divide a by b."""
291
 
@@ -315,7 +315,7 @@ Annotations serve several purposes in client applications:
315
  - Describing the safety profile of tools (destructive vs. non-destructive)
316
  - Signaling if tools interact with external systems
317
 
318
- You can add annotations to a tool using the `annotations` parameter in the `@mcp.tool()` decorator:
319
 
320
  ```python
321
  @mcp.tool(
@@ -351,7 +351,7 @@ from fastmcp import FastMCP, Context
351
 
352
  mcp = FastMCP(name="ContextDemo")
353
 
354
- @mcp.tool()
355
  async def process_data(data_uri: str, ctx: Context) -> dict:
356
  """Process data from a resource with progress reporting."""
357
  await ctx.info(f"Processing data from {data_uri}")
@@ -396,7 +396,7 @@ FastMCP supports **type coercion** when possible. This means that if a client se
396
  The most common parameter types are Python's built-in scalar types:
397
 
398
  ```python
399
- @mcp.tool()
400
  def process_values(
401
  name: str, # Text data
402
  count: int, # Integer numbers
@@ -416,7 +416,7 @@ FastMCP supports various date and time types from the `datetime` module:
416
  ```python
417
  from datetime import datetime, date, timedelta
418
 
419
- @mcp.tool()
420
  def process_date_time(
421
  event_date: date, # ISO format date string or date object
422
  event_time: datetime, # ISO format datetime string or datetime object
@@ -440,7 +440,7 @@ def process_date_time(
440
  FastMCP supports all standard Python collection types:
441
 
442
  ```python
443
- @mcp.tool()
444
  def analyze_data(
445
  values: list[float], # List of numbers
446
  properties: dict[str, str], # Dictionary with string keys and values
@@ -465,7 +465,7 @@ Collection types can be nested and combined to represent complex data structures
465
  For parameters that can accept multiple types or may be omitted:
466
 
467
  ```python
468
- @mcp.tool()
469
  def flexible_search(
470
  query: str | int, # Can be either string or integer
471
  filters: dict[str, str] | None = None, # Optional dictionary
@@ -488,7 +488,7 @@ Literals constrain parameters to a specific set of values:
488
  ```python
489
  from typing import Literal
490
 
491
- @mcp.tool()
492
  def sort_data(
493
  data: list[float],
494
  order: Literal["ascending", "descending"] = "ascending",
@@ -516,7 +516,7 @@ class Color(Enum):
516
  GREEN = "green"
517
  BLUE = "blue"
518
 
519
- @mcp.tool()
520
  def process_image(
521
  image_path: str,
522
  color_filter: Color = Color.RED
@@ -539,7 +539,7 @@ There are two approaches to handling binary data in tool parameters:
539
  #### Bytes
540
 
541
  ```python
542
- @mcp.tool()
543
  def process_binary(data: bytes):
544
  """Process binary data directly.
545
 
@@ -563,7 +563,7 @@ FastMCP does not automatically decode base64-encoded strings for bytes parameter
563
  from typing import Annotated
564
  from pydantic import Field
565
 
566
- @mcp.tool()
567
  def process_image_data(
568
  image_data: Annotated[str, Field(description="Base64-encoded image data")]
569
  ):
@@ -587,7 +587,7 @@ The `Path` type from the `pathlib` module can be used for file system paths:
587
  ```python
588
  from pathlib import Path
589
 
590
- @mcp.tool()
591
  def process_file(path: Path) -> str:
592
  """Process a file at the given path."""
593
  assert isinstance(path, Path) # Path is properly converted
@@ -603,7 +603,7 @@ The `UUID` type from the `uuid` module can be used for unique identifiers:
603
  ```python
604
  import uuid
605
 
606
- @mcp.tool()
607
  def process_item(
608
  item_id: uuid.UUID # String UUID or UUID object
609
  ) -> str:
@@ -628,7 +628,7 @@ class User(BaseModel):
628
  age: int | None = None
629
  is_active: bool = True
630
 
631
- @mcp.tool()
632
  def create_user(user: User):
633
  """Create a new user in the system."""
634
  # The input is automatically validated against the User model
@@ -657,7 +657,7 @@ Note that fields can be used *outside* Pydantic models to provide metadata and v
657
  from typing import Annotated
658
  from pydantic import Field
659
 
660
- @mcp.tool()
661
  def analyze_metrics(
662
  # Numbers with range constraints
663
  count: Annotated[int, Field(ge=0, le=100)], # 0 <= count <= 100
@@ -682,7 +682,7 @@ def analyze_metrics(
682
  You can also use `Field` as a default value, though the `Annotated` approach is preferred:
683
 
684
  ```python
685
- @mcp.tool()
686
  def validate_data(
687
  # Value constraints
688
  age: int = Field(ge=0, lt=120), # 0 <= age < 120
@@ -727,12 +727,12 @@ mcp = FastMCP(
727
  on_duplicate_tools="error"
728
  )
729
 
730
- @mcp.tool()
731
  def my_tool(): return "Version 1"
732
 
733
  # This will now raise a ValueError because 'my_tool' already exists
734
  # and on_duplicate_tools is set to "error".
735
- # @mcp.tool()
736
  # def my_tool(): return "Version 2"
737
  ```
738
 
@@ -754,7 +754,7 @@ from fastmcp import FastMCP
754
 
755
  mcp = FastMCP(name="DynamicToolServer")
756
 
757
- @mcp.tool()
758
  def calculate_sum(a: int, b: int) -> int:
759
  """Add two numbers together."""
760
  return a + b
 
24
 
25
  ### The `@tool` Decorator
26
 
27
+ Creating a tool is as simple as decorating a Python function with `@mcp.tool`:
28
 
29
  ```python
30
  from fastmcp import FastMCP
31
 
32
  mcp = FastMCP(name="CalculatorServer")
33
 
34
+ @mcp.tool
35
  def add(a: int, b: int) -> int:
36
  """Adds two integer numbers together."""
37
  return a + b
 
61
  Use standard Python type annotations for parameters:
62
 
63
  ```python
64
+ @mcp.tool
65
  def analyze_text(
66
  text: str,
67
  max_tokens: int = 100,
 
79
  from typing import Annotated
80
  from pydantic import Field
81
 
82
+ @mcp.tool
83
  def process_image(
84
  image_url: Annotated[str, Field(description="URL of the image to process")],
85
  resize: Annotated[bool, Field(description="Whether to resize the image")] = False,
 
97
  You can also use the Field as a default value, though the Annotated approach is preferred:
98
 
99
  ```python
100
+ @mcp.tool
101
  def search_database(
102
  query: str = Field(description="Search query string"),
103
  limit: int = Field(10, description="Maximum number of results", ge=1, le=100)
 
137
  FastMCP follows Python's standard function parameter conventions. Parameters without default values are required, while those with default values are optional.
138
 
139
  ```python
140
+ @mcp.tool
141
  def search_products(
142
  query: str, # Required - no default value
143
  max_results: int = 10, # Optional - has default value
 
197
 
198
  ```python
199
  # Synchronous tool (suitable for CPU-bound or quick tasks)
200
+ @mcp.tool
201
  def calculate_distance(lat1: float, lon1: float, lat2: float, lon2: float) -> float:
202
  """Calculate the distance between two coordinates."""
203
  # Implementation...
204
  return 42.5
205
 
206
  # Asynchronous tool (ideal for I/O-bound operations)
207
+ @mcp.tool
208
  async def fetch_weather(city: str) -> dict:
209
  """Retrieve current weather conditions for a city."""
210
  # Use 'async def' for operations involving network calls, file I/O, etc.
 
244
 
245
  mcp = FastMCP("Image Demo")
246
 
247
+ @mcp.tool
248
  def generate_image(width: int, height: int, color: str) -> Image:
249
  """Generates a solid color image."""
250
  # Create image using Pillow
 
258
  # Return using FastMCP's Image helper
259
  return Image(data=img_bytes, format="png")
260
 
261
+ @mcp.tool
262
  def do_nothing() -> None:
263
  """This tool performs an action but returns no data."""
264
  print("Performing a side effect...")
 
285
  from fastmcp import FastMCP
286
  from fastmcp.exceptions import ToolError
287
 
288
+ @mcp.tool
289
  def divide(a: float, b: float) -> float:
290
  """Divide a by b."""
291
 
 
315
  - Describing the safety profile of tools (destructive vs. non-destructive)
316
  - Signaling if tools interact with external systems
317
 
318
+ You can add annotations to a tool using the `annotations` parameter in the `@mcp.tool` decorator:
319
 
320
  ```python
321
  @mcp.tool(
 
351
 
352
  mcp = FastMCP(name="ContextDemo")
353
 
354
+ @mcp.tool
355
  async def process_data(data_uri: str, ctx: Context) -> dict:
356
  """Process data from a resource with progress reporting."""
357
  await ctx.info(f"Processing data from {data_uri}")
 
396
  The most common parameter types are Python's built-in scalar types:
397
 
398
  ```python
399
+ @mcp.tool
400
  def process_values(
401
  name: str, # Text data
402
  count: int, # Integer numbers
 
416
  ```python
417
  from datetime import datetime, date, timedelta
418
 
419
+ @mcp.tool
420
  def process_date_time(
421
  event_date: date, # ISO format date string or date object
422
  event_time: datetime, # ISO format datetime string or datetime object
 
440
  FastMCP supports all standard Python collection types:
441
 
442
  ```python
443
+ @mcp.tool
444
  def analyze_data(
445
  values: list[float], # List of numbers
446
  properties: dict[str, str], # Dictionary with string keys and values
 
465
  For parameters that can accept multiple types or may be omitted:
466
 
467
  ```python
468
+ @mcp.tool
469
  def flexible_search(
470
  query: str | int, # Can be either string or integer
471
  filters: dict[str, str] | None = None, # Optional dictionary
 
488
  ```python
489
  from typing import Literal
490
 
491
+ @mcp.tool
492
  def sort_data(
493
  data: list[float],
494
  order: Literal["ascending", "descending"] = "ascending",
 
516
  GREEN = "green"
517
  BLUE = "blue"
518
 
519
+ @mcp.tool
520
  def process_image(
521
  image_path: str,
522
  color_filter: Color = Color.RED
 
539
  #### Bytes
540
 
541
  ```python
542
+ @mcp.tool
543
  def process_binary(data: bytes):
544
  """Process binary data directly.
545
 
 
563
  from typing import Annotated
564
  from pydantic import Field
565
 
566
+ @mcp.tool
567
  def process_image_data(
568
  image_data: Annotated[str, Field(description="Base64-encoded image data")]
569
  ):
 
587
  ```python
588
  from pathlib import Path
589
 
590
+ @mcp.tool
591
  def process_file(path: Path) -> str:
592
  """Process a file at the given path."""
593
  assert isinstance(path, Path) # Path is properly converted
 
603
  ```python
604
  import uuid
605
 
606
+ @mcp.tool
607
  def process_item(
608
  item_id: uuid.UUID # String UUID or UUID object
609
  ) -> str:
 
628
  age: int | None = None
629
  is_active: bool = True
630
 
631
+ @mcp.tool
632
  def create_user(user: User):
633
  """Create a new user in the system."""
634
  # The input is automatically validated against the User model
 
657
  from typing import Annotated
658
  from pydantic import Field
659
 
660
+ @mcp.tool
661
  def analyze_metrics(
662
  # Numbers with range constraints
663
  count: Annotated[int, Field(ge=0, le=100)], # 0 <= count <= 100
 
682
  You can also use `Field` as a default value, though the `Annotated` approach is preferred:
683
 
684
  ```python
685
+ @mcp.tool
686
  def validate_data(
687
  # Value constraints
688
  age: int = Field(ge=0, lt=120), # 0 <= age < 120
 
727
  on_duplicate_tools="error"
728
  )
729
 
730
+ @mcp.tool
731
  def my_tool(): return "Version 1"
732
 
733
  # This will now raise a ValueError because 'my_tool' already exists
734
  # and on_duplicate_tools is set to "error".
735
+ # @mcp.tool
736
  # def my_tool(): return "Version 2"
737
  ```
738
 
 
754
 
755
  mcp = FastMCP(name="DynamicToolServer")
756
 
757
+ @mcp.tool
758
  def calculate_sum(a: int, b: int) -> int:
759
  """Add two numbers together."""
760
  return a + b
examples/complex_inputs.py CHANGED
@@ -20,7 +20,7 @@ class ShrimpTank(BaseModel):
20
  shrimp: list[Shrimp]
21
 
22
 
23
- @mcp.tool()
24
  def name_shrimp(
25
  tank: ShrimpTank,
26
  # You can use pydantic Field in function signatures for validation.
 
20
  shrimp: list[Shrimp]
21
 
22
 
23
+ @mcp.tool
24
  def name_shrimp(
25
  tank: ShrimpTank,
26
  # You can use pydantic Field in function signatures for validation.
examples/config_server.py CHANGED
@@ -24,7 +24,7 @@ if args.debug:
24
  mcp = FastMCP(server_name)
25
 
26
 
27
- @mcp.tool()
28
  def get_status() -> dict[str, str | bool]:
29
  """Get the current server configuration and status."""
30
  return {
@@ -34,7 +34,7 @@ def get_status() -> dict[str, str | bool]:
34
  }
35
 
36
 
37
- @mcp.tool()
38
  def echo_message(message: str) -> str:
39
  """Echo a message, with debug info if debug mode is enabled."""
40
  if args.debug:
 
24
  mcp = FastMCP(server_name)
25
 
26
 
27
+ @mcp.tool
28
  def get_status() -> dict[str, str | bool]:
29
  """Get the current server configuration and status."""
30
  return {
 
34
  }
35
 
36
 
37
+ @mcp.tool
38
  def echo_message(message: str) -> str:
39
  """Echo a message, with debug info if debug mode is enabled."""
40
  if args.debug:
examples/desktop.py CHANGED
@@ -26,7 +26,7 @@ def get_greeting(name: str) -> str:
26
  return f"Hello, {name}!"
27
 
28
 
29
- @mcp.tool()
30
  def add(a: int, b: int) -> int:
31
  """Add two numbers"""
32
  return a + b
 
26
  return f"Hello, {name}!"
27
 
28
 
29
+ @mcp.tool
30
  def add(a: int, b: int) -> int:
31
  """Add two numbers"""
32
  return a + b
examples/echo.py CHANGED
@@ -8,7 +8,7 @@ from fastmcp import FastMCP
8
  mcp = FastMCP("Echo Server")
9
 
10
 
11
- @mcp.tool()
12
  def echo_tool(text: str) -> str:
13
  """Echo the input text"""
14
  return text
 
8
  mcp = FastMCP("Echo Server")
9
 
10
 
11
+ @mcp.tool
12
  def echo_tool(text: str) -> str:
13
  """Echo the input text"""
14
  return text
examples/memory.py CHANGED
@@ -279,7 +279,7 @@ async def display_memory_tree(deps: Deps) -> str:
279
  return result
280
 
281
 
282
- @mcp.tool()
283
  async def remember(
284
  contents: list[str] = Field(
285
  description="List of observations or memories to store"
@@ -294,7 +294,7 @@ async def remember(
294
  await deps.pool.close()
295
 
296
 
297
- @mcp.tool()
298
  async def read_profile() -> str:
299
  deps = Deps(openai=AsyncOpenAI(), pool=await get_db_pool())
300
  profile = await display_memory_tree(deps)
 
279
  return result
280
 
281
 
282
+ @mcp.tool
283
  async def remember(
284
  contents: list[str] = Field(
285
  description="List of observations or memories to store"
 
294
  await deps.pool.close()
295
 
296
 
297
+ @mcp.tool
298
  async def read_profile() -> str:
299
  deps = Deps(openai=AsyncOpenAI(), pool=await get_db_pool())
300
  profile = await display_memory_tree(deps)
examples/sampling.py CHANGED
@@ -15,7 +15,7 @@ from fastmcp.client.sampling import RequestContext, SamplingMessage, SamplingPar
15
  mcp = FastMCP("Sampling Example")
16
 
17
 
18
- @mcp.tool()
19
  async def example_tool(prompt: str, context: Context) -> str:
20
  """Sample a completion from the LLM."""
21
  response = await context.sample(
 
15
  mcp = FastMCP("Sampling Example")
16
 
17
 
18
+ @mcp.tool
19
  async def example_tool(prompt: str, context: Context) -> str:
20
  """Sample a completion from the LLM."""
21
  response = await context.sample(
examples/screenshot.py CHANGED
@@ -12,7 +12,7 @@ from fastmcp import FastMCP, Image
12
  mcp = FastMCP("Screenshot Demo", dependencies=["pyautogui", "Pillow"])
13
 
14
 
15
- @mcp.tool()
16
  def take_screenshot() -> Image:
17
  """
18
  Take a screenshot of the user's screen and return it as an image. Use
 
12
  mcp = FastMCP("Screenshot Demo", dependencies=["pyautogui", "Pillow"])
13
 
14
 
15
+ @mcp.tool
16
  def take_screenshot() -> Image:
17
  """
18
  Take a screenshot of the user's screen and return it as an image. Use
examples/simple_echo.py CHANGED
@@ -8,7 +8,7 @@ from fastmcp import FastMCP
8
  mcp = FastMCP("Echo Server")
9
 
10
 
11
- @mcp.tool()
12
  def echo(text: str) -> str:
13
  """Echo the input text"""
14
  return text
 
8
  mcp = FastMCP("Echo Server")
9
 
10
 
11
+ @mcp.tool
12
  def echo(text: str) -> str:
13
  """Echo the input text"""
14
  return text
src/fastmcp/contrib/bulk_tool_caller/example.py CHANGED
@@ -6,7 +6,7 @@ from fastmcp.contrib.bulk_tool_caller import BulkToolCaller
6
  mcp = FastMCP()
7
 
8
 
9
- @mcp.tool()
10
  def echo_tool(text: str) -> str:
11
  """Echo the input text"""
12
  return text
 
6
  mcp = FastMCP()
7
 
8
 
9
+ @mcp.tool
10
  def echo_tool(text: str) -> str:
11
  """Echo the input text"""
12
  return text
tests/auth/providers/test_bearer.py CHANGED
@@ -53,7 +53,7 @@ def run_mcp_server(
53
  )
54
  )
55
 
56
- @mcp.tool()
57
  def add(a: int, b: int) -> int:
58
  return a + b
59
 
 
53
  )
54
  )
55
 
56
+ @mcp.tool
57
  def add(a: int, b: int) -> int:
58
  return a + b
59
 
tests/client/test_client.py CHANGED
@@ -510,7 +510,7 @@ class TestErrorHandling:
510
  async def test_general_tool_exceptions_are_not_masked_by_default(self):
511
  mcp = FastMCP("TestServer")
512
 
513
- @mcp.tool()
514
  def error_tool():
515
  raise ValueError("This is a test error (abc)")
516
 
@@ -525,7 +525,7 @@ class TestErrorHandling:
525
  async def test_general_tool_exceptions_are_masked_when_enabled(self):
526
  mcp = FastMCP("TestServer", mask_error_details=True)
527
 
528
- @mcp.tool()
529
  def error_tool():
530
  raise ValueError("This is a test error (abc)")
531
 
@@ -540,7 +540,7 @@ class TestErrorHandling:
540
  async def test_specific_tool_errors_are_sent_to_client(self):
541
  mcp = FastMCP("TestServer")
542
 
543
- @mcp.tool()
544
  def custom_error_tool():
545
  raise ToolError("This is a test error (abc)")
546
 
 
510
  async def test_general_tool_exceptions_are_not_masked_by_default(self):
511
  mcp = FastMCP("TestServer")
512
 
513
+ @mcp.tool
514
  def error_tool():
515
  raise ValueError("This is a test error (abc)")
516
 
 
525
  async def test_general_tool_exceptions_are_masked_when_enabled(self):
526
  mcp = FastMCP("TestServer", mask_error_details=True)
527
 
528
+ @mcp.tool
529
  def error_tool():
530
  raise ValueError("This is a test error (abc)")
531
 
 
540
  async def test_specific_tool_errors_are_sent_to_client(self):
541
  mcp = FastMCP("TestServer")
542
 
543
+ @mcp.tool
544
  def custom_error_tool():
545
  raise ToolError("This is a test error (abc)")
546
 
tests/client/test_logs.py CHANGED
@@ -17,11 +17,11 @@ class LogHandler:
17
  def fastmcp_server():
18
  mcp = FastMCP()
19
 
20
- @mcp.tool()
21
  async def log(context: Context) -> None:
22
  await context.info(message="hello?")
23
 
24
- @mcp.tool()
25
  async def echo_log(
26
  message: str,
27
  context: Context,
 
17
  def fastmcp_server():
18
  mcp = FastMCP()
19
 
20
+ @mcp.tool
21
  async def log(context: Context) -> None:
22
  await context.info(message="hello?")
23
 
24
+ @mcp.tool
25
  async def echo_log(
26
  message: str,
27
  context: Context,
tests/client/test_progress.py CHANGED
@@ -16,7 +16,7 @@ def clear_progress_messages():
16
  def fastmcp_server():
17
  mcp = FastMCP()
18
 
19
- @mcp.tool()
20
  async def progress_tool(context: Context) -> int:
21
  for i in range(3):
22
  await context.report_progress(
 
16
  def fastmcp_server():
17
  mcp = FastMCP()
18
 
19
+ @mcp.tool
20
  async def progress_tool(context: Context) -> int:
21
  for i in range(3):
22
  await context.report_progress(
tests/client/test_roots.py CHANGED
@@ -9,7 +9,7 @@ from fastmcp import Client, Context, FastMCP
9
  def fastmcp_server():
10
  mcp = FastMCP()
11
 
12
- @mcp.tool()
13
  async def list_roots(context: Context) -> list[str]:
14
  roots = await context.list_roots()
15
  return [str(r.uri) for r in roots]
 
9
  def fastmcp_server():
10
  mcp = FastMCP()
11
 
12
+ @mcp.tool
13
  async def list_roots(context: Context) -> list[str]:
14
  roots = await context.list_roots()
15
  return [str(r.uri) for r in roots]
tests/client/test_sampling.py CHANGED
@@ -11,17 +11,17 @@ from fastmcp.client.sampling import RequestContext, SamplingMessage, SamplingPar
11
  def fastmcp_server():
12
  mcp = FastMCP()
13
 
14
- @mcp.tool()
15
  async def simple_sample(message: str, context: Context) -> str:
16
  result = await context.sample("Hello, world!")
17
  return cast(TextContent, result).text
18
 
19
- @mcp.tool()
20
  async def sample_with_system_prompt(message: str, context: Context) -> str:
21
  result = await context.sample("Hello, world!", system_prompt="You love FastMCP")
22
  return cast(TextContent, result).text
23
 
24
- @mcp.tool()
25
  async def sample_with_messages(message: str, context: Context) -> str:
26
  result = await context.sample(
27
  [
 
11
  def fastmcp_server():
12
  mcp = FastMCP()
13
 
14
+ @mcp.tool
15
  async def simple_sample(message: str, context: Context) -> str:
16
  result = await context.sample("Hello, world!")
17
  return cast(TextContent, result).text
18
 
19
+ @mcp.tool
20
  async def sample_with_system_prompt(message: str, context: Context) -> str:
21
  result = await context.sample("Hello, world!", system_prompt="You love FastMCP")
22
  return cast(TextContent, result).text
23
 
24
+ @mcp.tool
25
  async def sample_with_messages(message: str, context: Context) -> str:
26
  result = await context.sample(
27
  [
tests/client/test_stdio.py CHANGED
@@ -17,7 +17,7 @@ class TestKeepAlive:
17
 
18
  mcp = FastMCP()
19
 
20
- @mcp.tool()
21
  def pid() -> int:
22
  """Gets PID of server"""
23
  return os.getpid()
 
17
 
18
  mcp = FastMCP()
19
 
20
+ @mcp.tool
21
  def pid() -> int:
22
  """Gets PID of server"""
23
  return os.getpid()
tests/server/test_file_server.py CHANGED
@@ -62,7 +62,7 @@ def resources(mcp: FastMCP, test_dir: Path) -> FastMCP:
62
 
63
  @pytest.fixture(autouse=True)
64
  def tools(mcp: FastMCP, test_dir: Path) -> FastMCP:
65
- @mcp.tool()
66
  def delete_file(path: str) -> bool:
67
  # ensure path is in test_dir
68
  if Path(path).resolve().parent != test_dir:
 
62
 
63
  @pytest.fixture(autouse=True)
64
  def tools(mcp: FastMCP, test_dir: Path) -> FastMCP:
65
+ @mcp.tool
66
  def delete_file(path: str) -> bool:
67
  # ensure path is in test_dir
68
  if Path(path).resolve().parent != test_dir:
tests/server/test_server.py CHANGED
@@ -57,7 +57,7 @@ class TestTools:
57
 
58
  mcp = FastMCP()
59
 
60
- @mcp.tool()
61
  def fn(x: int) -> int:
62
  return x + 1
63
 
@@ -126,7 +126,7 @@ class TestToolDecorator:
126
  async def test_tool_decorator(self):
127
  mcp = FastMCP()
128
 
129
- @mcp.tool()
130
  def add(x: int, y: int) -> int:
131
  return x + y
132
 
@@ -179,7 +179,7 @@ class TestToolDecorator:
179
  def __init__(self, x: int):
180
  self.x = x
181
 
182
- @mcp.tool()
183
  def add(self, y: int) -> int:
184
  return self.x + y
185
 
@@ -207,7 +207,7 @@ class TestToolDecorator:
207
 
208
  class MyClass:
209
  @staticmethod
210
- @mcp.tool()
211
  def add(x: int, y: int) -> int:
212
  return x + y
213
 
@@ -217,7 +217,7 @@ class TestToolDecorator:
217
  async def test_tool_decorator_async_function(self):
218
  mcp = FastMCP()
219
 
220
- @mcp.tool()
221
  async def add(x: int, y: int) -> int:
222
  return x + y
223
 
@@ -288,7 +288,7 @@ class TestToolDecorator:
288
  """Test that tools with annotated arguments work correctly."""
289
  mcp = FastMCP()
290
 
291
- @mcp.tool()
292
  def add(
293
  x: Annotated[int, Field(description="x is an int")],
294
  y: Annotated[str, Field(description="y is not an int")],
@@ -303,7 +303,7 @@ class TestToolDecorator:
303
  """Test that tools with annotated arguments work correctly."""
304
  mcp = FastMCP()
305
 
306
- @mcp.tool()
307
  def add(
308
  x: int = Field(description="x is an int"),
309
  y: str = Field(description="y is not an int"),
 
57
 
58
  mcp = FastMCP()
59
 
60
+ @mcp.tool
61
  def fn(x: int) -> int:
62
  return x + 1
63
 
 
126
  async def test_tool_decorator(self):
127
  mcp = FastMCP()
128
 
129
+ @mcp.tool
130
  def add(x: int, y: int) -> int:
131
  return x + y
132
 
 
179
  def __init__(self, x: int):
180
  self.x = x
181
 
182
+ @mcp.tool
183
  def add(self, y: int) -> int:
184
  return self.x + y
185
 
 
207
 
208
  class MyClass:
209
  @staticmethod
210
+ @mcp.tool
211
  def add(x: int, y: int) -> int:
212
  return x + y
213
 
 
217
  async def test_tool_decorator_async_function(self):
218
  mcp = FastMCP()
219
 
220
+ @mcp.tool
221
  async def add(x: int, y: int) -> int:
222
  return x + y
223
 
 
288
  """Test that tools with annotated arguments work correctly."""
289
  mcp = FastMCP()
290
 
291
+ @mcp.tool
292
  def add(
293
  x: Annotated[int, Field(description="x is an int")],
294
  y: Annotated[str, Field(description="y is not an int")],
 
303
  """Test that tools with annotated arguments work correctly."""
304
  mcp = FastMCP()
305
 
306
+ @mcp.tool
307
  def add(
308
  x: int = Field(description="x is an int"),
309
  y: str = Field(description="y is not an int"),
tests/server/test_server_interactions.py CHANGED
@@ -30,30 +30,30 @@ from fastmcp.utilities.types import Image
30
  def tool_server():
31
  mcp = FastMCP()
32
 
33
- @mcp.tool()
34
  def add(x: int, y: int) -> int:
35
  return x + y
36
 
37
- @mcp.tool()
38
  def list_tool() -> list[str | int]:
39
  return ["x", 2]
40
 
41
- @mcp.tool()
42
  def error_tool() -> None:
43
  raise ValueError("Test error")
44
 
45
- @mcp.tool()
46
  def image_tool(path: str) -> Image:
47
  return Image(path)
48
 
49
- @mcp.tool()
50
  def mixed_content_tool() -> list[TextContent | ImageContent]:
51
  return [
52
  TextContent(type="text", text="Hello"),
53
  ImageContent(type="image", data="abc", mimeType="image/png"),
54
  ]
55
 
56
- @mcp.tool()
57
  def mixed_list_fn(image_path: str) -> list:
58
  return [
59
  "text message",
@@ -100,7 +100,7 @@ class TestTools:
100
  mcp = FastMCP()
101
  client = Client(transport=FastMCPTransport(mcp))
102
 
103
- @mcp.tool()
104
  def error_tool():
105
  raise ValueError("Test error")
106
 
@@ -119,7 +119,7 @@ class TestToolReturnTypes:
119
  async def test_string(self):
120
  mcp = FastMCP()
121
 
122
- @mcp.tool()
123
  def string_tool() -> str:
124
  return "Hello, world!"
125
 
@@ -130,7 +130,7 @@ class TestToolReturnTypes:
130
  async def test_bytes(self, tmp_path: Path):
131
  mcp = FastMCP()
132
 
133
- @mcp.tool()
134
  def bytes_tool() -> bytes:
135
  return b"Hello, world!"
136
 
@@ -143,7 +143,7 @@ class TestToolReturnTypes:
143
 
144
  test_uuid = uuid.uuid4()
145
 
146
- @mcp.tool()
147
  def uuid_tool() -> uuid.UUID:
148
  return test_uuid
149
 
@@ -156,7 +156,7 @@ class TestToolReturnTypes:
156
 
157
  test_path = Path("/tmp/test.txt")
158
 
159
- @mcp.tool()
160
  def path_tool() -> Path:
161
  return test_path
162
 
@@ -169,7 +169,7 @@ class TestToolReturnTypes:
169
 
170
  dt = datetime.datetime(2025, 4, 25, 1, 2, 3)
171
 
172
- @mcp.tool()
173
  def datetime_tool() -> datetime.datetime:
174
  return dt
175
 
@@ -180,7 +180,7 @@ class TestToolReturnTypes:
180
  async def test_image(self, tmp_path: Path):
181
  mcp = FastMCP()
182
 
183
- @mcp.tool()
184
  def image_tool(path: str) -> Image:
185
  return Image(path)
186
 
@@ -243,7 +243,7 @@ class TestToolParameters:
243
  async def test_parameter_descriptions_with_field_annotations(self):
244
  mcp = FastMCP("Test Server")
245
 
246
- @mcp.tool()
247
  def greet(
248
  name: Annotated[str, Field(description="The name to greet")],
249
  title: Annotated[str, Field(description="Optional title", default="")],
@@ -268,7 +268,7 @@ class TestToolParameters:
268
  async def test_parameter_descriptions_with_field_defaults(self):
269
  mcp = FastMCP("Test Server")
270
 
271
- @mcp.tool()
272
  def greet(
273
  name: str = Field(description="The name to greet"),
274
  title: str = Field(description="Optional title", default=""),
@@ -293,7 +293,7 @@ class TestToolParameters:
293
  async def test_tool_with_bytes_input(self):
294
  mcp = FastMCP()
295
 
296
- @mcp.tool()
297
  def process_image(image: bytes) -> Image:
298
  return Image(data=image)
299
 
@@ -308,7 +308,7 @@ class TestToolParameters:
308
  async def test_tool_with_invalid_input(self):
309
  mcp = FastMCP()
310
 
311
- @mcp.tool()
312
  def my_tool(x: int) -> int:
313
  return x + 1
314
 
@@ -323,7 +323,7 @@ class TestToolParameters:
323
  """Test string-to-int type coercion."""
324
  mcp = FastMCP()
325
 
326
- @mcp.tool()
327
  def add_one(x: int) -> int:
328
  return x + 1
329
 
@@ -336,7 +336,7 @@ class TestToolParameters:
336
  """Test string-to-bool type coercion."""
337
  mcp = FastMCP()
338
 
339
- @mcp.tool()
340
  def toggle(flag: bool) -> bool:
341
  return not flag
342
 
@@ -351,7 +351,7 @@ class TestToolParameters:
351
  async def test_annotated_field_validation(self):
352
  mcp = FastMCP()
353
 
354
- @mcp.tool()
355
  def analyze(x: Annotated[int, Field(ge=1)]) -> None:
356
  pass
357
 
@@ -362,7 +362,7 @@ class TestToolParameters:
362
  async def test_default_field_validation(self):
363
  mcp = FastMCP()
364
 
365
- @mcp.tool()
366
  def analyze(x: int = Field(ge=1)) -> None:
367
  pass
368
 
@@ -373,7 +373,7 @@ class TestToolParameters:
373
  async def test_default_field_is_still_required_if_no_default_specified(self):
374
  mcp = FastMCP()
375
 
376
- @mcp.tool()
377
  def analyze(x: int = Field()) -> None:
378
  pass
379
 
@@ -384,7 +384,7 @@ class TestToolParameters:
384
  async def test_literal_type_validation_error(self):
385
  mcp = FastMCP()
386
 
387
- @mcp.tool()
388
  def analyze(x: Literal["a", "b"]) -> None:
389
  pass
390
 
@@ -395,7 +395,7 @@ class TestToolParameters:
395
  async def test_literal_type_validation_success(self):
396
  mcp = FastMCP()
397
 
398
- @mcp.tool()
399
  def analyze(x: Literal["a", "b"]) -> str:
400
  return x
401
 
@@ -411,7 +411,7 @@ class TestToolParameters:
411
  GREEN = "green"
412
  BLUE = "blue"
413
 
414
- @mcp.tool()
415
  def analyze(x: MyEnum) -> str:
416
  return x.value
417
 
@@ -427,7 +427,7 @@ class TestToolParameters:
427
  GREEN = "green"
428
  BLUE = "blue"
429
 
430
- @mcp.tool()
431
  def analyze(x: MyEnum) -> str:
432
  return x.value
433
 
@@ -438,7 +438,7 @@ class TestToolParameters:
438
  async def test_union_type_validation(self):
439
  mcp = FastMCP()
440
 
441
- @mcp.tool()
442
  def analyze(x: int | float) -> str:
443
  return str(x)
444
 
@@ -455,7 +455,7 @@ class TestToolParameters:
455
  async def test_path_type(self):
456
  mcp = FastMCP()
457
 
458
- @mcp.tool()
459
  def send_path(path: Path) -> str:
460
  assert isinstance(path, Path)
461
  return str(path)
@@ -470,7 +470,7 @@ class TestToolParameters:
470
  async def test_path_type_error(self):
471
  mcp = FastMCP()
472
 
473
- @mcp.tool()
474
  def send_path(path: Path) -> str:
475
  return str(path)
476
 
@@ -481,7 +481,7 @@ class TestToolParameters:
481
  async def test_uuid_type(self):
482
  mcp = FastMCP()
483
 
484
- @mcp.tool()
485
  def send_uuid(x: uuid.UUID) -> str:
486
  assert isinstance(x, uuid.UUID)
487
  return str(x)
@@ -495,7 +495,7 @@ class TestToolParameters:
495
  async def test_uuid_type_error(self):
496
  mcp = FastMCP()
497
 
498
- @mcp.tool()
499
  def send_uuid(x: uuid.UUID) -> str:
500
  return str(x)
501
 
@@ -506,7 +506,7 @@ class TestToolParameters:
506
  async def test_datetime_type(self):
507
  mcp = FastMCP()
508
 
509
- @mcp.tool()
510
  def send_datetime(x: datetime.datetime) -> str:
511
  return x.isoformat()
512
 
@@ -519,7 +519,7 @@ class TestToolParameters:
519
  async def test_datetime_type_parse_string(self):
520
  mcp = FastMCP()
521
 
522
- @mcp.tool()
523
  def send_datetime(x: datetime.datetime) -> str:
524
  return x.isoformat()
525
 
@@ -532,7 +532,7 @@ class TestToolParameters:
532
  async def test_datetime_type_error(self):
533
  mcp = FastMCP()
534
 
535
- @mcp.tool()
536
  def send_datetime(x: datetime.datetime) -> str:
537
  return x.isoformat()
538
 
@@ -543,7 +543,7 @@ class TestToolParameters:
543
  async def test_date_type(self):
544
  mcp = FastMCP()
545
 
546
- @mcp.tool()
547
  def send_date(x: datetime.date) -> str:
548
  return x.isoformat()
549
 
@@ -554,7 +554,7 @@ class TestToolParameters:
554
  async def test_date_type_parse_string(self):
555
  mcp = FastMCP()
556
 
557
- @mcp.tool()
558
  def send_date(x: datetime.date) -> str:
559
  return x.isoformat()
560
 
@@ -565,7 +565,7 @@ class TestToolParameters:
565
  async def test_timedelta_type(self):
566
  mcp = FastMCP()
567
 
568
- @mcp.tool()
569
  def send_timedelta(x: datetime.timedelta) -> str:
570
  return str(x)
571
 
@@ -578,7 +578,7 @@ class TestToolParameters:
578
  async def test_timedelta_type_parse_int(self):
579
  mcp = FastMCP()
580
 
581
- @mcp.tool()
582
  def send_timedelta(x: datetime.timedelta) -> str:
583
  return str(x)
584
 
@@ -594,7 +594,7 @@ class TestToolContextInjection:
594
  """Test that context parameters are properly detected."""
595
  mcp = FastMCP()
596
 
597
- @mcp.tool()
598
  def tool_with_context(x: int, ctx: Context) -> str:
599
  return f"Request {ctx.request_id}: {x}"
600
 
@@ -607,7 +607,7 @@ class TestToolContextInjection:
607
  """Test that context is properly injected into tool calls."""
608
  mcp = FastMCP()
609
 
610
- @mcp.tool()
611
  def tool_with_context(x: int, ctx: Context) -> str:
612
  assert isinstance(ctx, Context)
613
  assert ctx.request_id is not None
@@ -623,7 +623,7 @@ class TestToolContextInjection:
623
  """Test that context works in async functions."""
624
  mcp = FastMCP()
625
 
626
- @mcp.tool()
627
  async def async_tool(x: int, ctx: Context) -> str:
628
  assert ctx.request_id is not None
629
  return f"Async request {ctx.request_id}: {x}"
@@ -638,7 +638,7 @@ class TestToolContextInjection:
638
  """Test that context is optional."""
639
  mcp = FastMCP()
640
 
641
- @mcp.tool()
642
  def no_context(x: int) -> int:
643
  return x * 2
644
 
@@ -656,7 +656,7 @@ class TestToolContextInjection:
656
  def test_resource() -> str:
657
  return "resource data"
658
 
659
- @mcp.tool()
660
  async def tool_with_resource(ctx: Context) -> str:
661
  r_iter = await ctx.read_resource("test://data")
662
  r_list = list(r_iter)
 
30
  def tool_server():
31
  mcp = FastMCP()
32
 
33
+ @mcp.tool
34
  def add(x: int, y: int) -> int:
35
  return x + y
36
 
37
+ @mcp.tool
38
  def list_tool() -> list[str | int]:
39
  return ["x", 2]
40
 
41
+ @mcp.tool
42
  def error_tool() -> None:
43
  raise ValueError("Test error")
44
 
45
+ @mcp.tool
46
  def image_tool(path: str) -> Image:
47
  return Image(path)
48
 
49
+ @mcp.tool
50
  def mixed_content_tool() -> list[TextContent | ImageContent]:
51
  return [
52
  TextContent(type="text", text="Hello"),
53
  ImageContent(type="image", data="abc", mimeType="image/png"),
54
  ]
55
 
56
+ @mcp.tool
57
  def mixed_list_fn(image_path: str) -> list:
58
  return [
59
  "text message",
 
100
  mcp = FastMCP()
101
  client = Client(transport=FastMCPTransport(mcp))
102
 
103
+ @mcp.tool
104
  def error_tool():
105
  raise ValueError("Test error")
106
 
 
119
  async def test_string(self):
120
  mcp = FastMCP()
121
 
122
+ @mcp.tool
123
  def string_tool() -> str:
124
  return "Hello, world!"
125
 
 
130
  async def test_bytes(self, tmp_path: Path):
131
  mcp = FastMCP()
132
 
133
+ @mcp.tool
134
  def bytes_tool() -> bytes:
135
  return b"Hello, world!"
136
 
 
143
 
144
  test_uuid = uuid.uuid4()
145
 
146
+ @mcp.tool
147
  def uuid_tool() -> uuid.UUID:
148
  return test_uuid
149
 
 
156
 
157
  test_path = Path("/tmp/test.txt")
158
 
159
+ @mcp.tool
160
  def path_tool() -> Path:
161
  return test_path
162
 
 
169
 
170
  dt = datetime.datetime(2025, 4, 25, 1, 2, 3)
171
 
172
+ @mcp.tool
173
  def datetime_tool() -> datetime.datetime:
174
  return dt
175
 
 
180
  async def test_image(self, tmp_path: Path):
181
  mcp = FastMCP()
182
 
183
+ @mcp.tool
184
  def image_tool(path: str) -> Image:
185
  return Image(path)
186
 
 
243
  async def test_parameter_descriptions_with_field_annotations(self):
244
  mcp = FastMCP("Test Server")
245
 
246
+ @mcp.tool
247
  def greet(
248
  name: Annotated[str, Field(description="The name to greet")],
249
  title: Annotated[str, Field(description="Optional title", default="")],
 
268
  async def test_parameter_descriptions_with_field_defaults(self):
269
  mcp = FastMCP("Test Server")
270
 
271
+ @mcp.tool
272
  def greet(
273
  name: str = Field(description="The name to greet"),
274
  title: str = Field(description="Optional title", default=""),
 
293
  async def test_tool_with_bytes_input(self):
294
  mcp = FastMCP()
295
 
296
+ @mcp.tool
297
  def process_image(image: bytes) -> Image:
298
  return Image(data=image)
299
 
 
308
  async def test_tool_with_invalid_input(self):
309
  mcp = FastMCP()
310
 
311
+ @mcp.tool
312
  def my_tool(x: int) -> int:
313
  return x + 1
314
 
 
323
  """Test string-to-int type coercion."""
324
  mcp = FastMCP()
325
 
326
+ @mcp.tool
327
  def add_one(x: int) -> int:
328
  return x + 1
329
 
 
336
  """Test string-to-bool type coercion."""
337
  mcp = FastMCP()
338
 
339
+ @mcp.tool
340
  def toggle(flag: bool) -> bool:
341
  return not flag
342
 
 
351
  async def test_annotated_field_validation(self):
352
  mcp = FastMCP()
353
 
354
+ @mcp.tool
355
  def analyze(x: Annotated[int, Field(ge=1)]) -> None:
356
  pass
357
 
 
362
  async def test_default_field_validation(self):
363
  mcp = FastMCP()
364
 
365
+ @mcp.tool
366
  def analyze(x: int = Field(ge=1)) -> None:
367
  pass
368
 
 
373
  async def test_default_field_is_still_required_if_no_default_specified(self):
374
  mcp = FastMCP()
375
 
376
+ @mcp.tool
377
  def analyze(x: int = Field()) -> None:
378
  pass
379
 
 
384
  async def test_literal_type_validation_error(self):
385
  mcp = FastMCP()
386
 
387
+ @mcp.tool
388
  def analyze(x: Literal["a", "b"]) -> None:
389
  pass
390
 
 
395
  async def test_literal_type_validation_success(self):
396
  mcp = FastMCP()
397
 
398
+ @mcp.tool
399
  def analyze(x: Literal["a", "b"]) -> str:
400
  return x
401
 
 
411
  GREEN = "green"
412
  BLUE = "blue"
413
 
414
+ @mcp.tool
415
  def analyze(x: MyEnum) -> str:
416
  return x.value
417
 
 
427
  GREEN = "green"
428
  BLUE = "blue"
429
 
430
+ @mcp.tool
431
  def analyze(x: MyEnum) -> str:
432
  return x.value
433
 
 
438
  async def test_union_type_validation(self):
439
  mcp = FastMCP()
440
 
441
+ @mcp.tool
442
  def analyze(x: int | float) -> str:
443
  return str(x)
444
 
 
455
  async def test_path_type(self):
456
  mcp = FastMCP()
457
 
458
+ @mcp.tool
459
  def send_path(path: Path) -> str:
460
  assert isinstance(path, Path)
461
  return str(path)
 
470
  async def test_path_type_error(self):
471
  mcp = FastMCP()
472
 
473
+ @mcp.tool
474
  def send_path(path: Path) -> str:
475
  return str(path)
476
 
 
481
  async def test_uuid_type(self):
482
  mcp = FastMCP()
483
 
484
+ @mcp.tool
485
  def send_uuid(x: uuid.UUID) -> str:
486
  assert isinstance(x, uuid.UUID)
487
  return str(x)
 
495
  async def test_uuid_type_error(self):
496
  mcp = FastMCP()
497
 
498
+ @mcp.tool
499
  def send_uuid(x: uuid.UUID) -> str:
500
  return str(x)
501
 
 
506
  async def test_datetime_type(self):
507
  mcp = FastMCP()
508
 
509
+ @mcp.tool
510
  def send_datetime(x: datetime.datetime) -> str:
511
  return x.isoformat()
512
 
 
519
  async def test_datetime_type_parse_string(self):
520
  mcp = FastMCP()
521
 
522
+ @mcp.tool
523
  def send_datetime(x: datetime.datetime) -> str:
524
  return x.isoformat()
525
 
 
532
  async def test_datetime_type_error(self):
533
  mcp = FastMCP()
534
 
535
+ @mcp.tool
536
  def send_datetime(x: datetime.datetime) -> str:
537
  return x.isoformat()
538
 
 
543
  async def test_date_type(self):
544
  mcp = FastMCP()
545
 
546
+ @mcp.tool
547
  def send_date(x: datetime.date) -> str:
548
  return x.isoformat()
549
 
 
554
  async def test_date_type_parse_string(self):
555
  mcp = FastMCP()
556
 
557
+ @mcp.tool
558
  def send_date(x: datetime.date) -> str:
559
  return x.isoformat()
560
 
 
565
  async def test_timedelta_type(self):
566
  mcp = FastMCP()
567
 
568
+ @mcp.tool
569
  def send_timedelta(x: datetime.timedelta) -> str:
570
  return str(x)
571
 
 
578
  async def test_timedelta_type_parse_int(self):
579
  mcp = FastMCP()
580
 
581
+ @mcp.tool
582
  def send_timedelta(x: datetime.timedelta) -> str:
583
  return str(x)
584
 
 
594
  """Test that context parameters are properly detected."""
595
  mcp = FastMCP()
596
 
597
+ @mcp.tool
598
  def tool_with_context(x: int, ctx: Context) -> str:
599
  return f"Request {ctx.request_id}: {x}"
600
 
 
607
  """Test that context is properly injected into tool calls."""
608
  mcp = FastMCP()
609
 
610
+ @mcp.tool
611
  def tool_with_context(x: int, ctx: Context) -> str:
612
  assert isinstance(ctx, Context)
613
  assert ctx.request_id is not None
 
623
  """Test that context works in async functions."""
624
  mcp = FastMCP()
625
 
626
+ @mcp.tool
627
  async def async_tool(x: int, ctx: Context) -> str:
628
  assert ctx.request_id is not None
629
  return f"Async request {ctx.request_id}: {x}"
 
638
  """Test that context is optional."""
639
  mcp = FastMCP()
640
 
641
+ @mcp.tool
642
  def no_context(x: int) -> int:
643
  return x * 2
644
 
 
656
  def test_resource() -> str:
657
  return "resource data"
658
 
659
+ @mcp.tool
660
  async def tool_with_resource(ctx: Context) -> str:
661
  r_iter = await ctx.read_resource("test://data")
662
  r_list = list(r_iter)
tests/tools/test_tool.py CHANGED
@@ -320,7 +320,7 @@ class TestLegacyToolJsonParsing:
320
  """Test JSON string to collection type coercion."""
321
  mcp = FastMCP()
322
 
323
- @mcp.tool()
324
  def process_list(items: list[int]) -> int:
325
  return sum(items)
326
 
@@ -335,7 +335,7 @@ class TestLegacyToolJsonParsing:
335
  """Test that a list coercion error is raised if the input is not a valid list."""
336
  mcp = FastMCP()
337
 
338
- @mcp.tool()
339
  def process_list(items: list[int]) -> int:
340
  return sum(items)
341
 
@@ -350,7 +350,7 @@ class TestLegacyToolJsonParsing:
350
  """Test JSON string to dict type coercion."""
351
  mcp = FastMCP()
352
 
353
- @mcp.tool()
354
  def process_dict(data: dict[str, int]) -> int:
355
  return sum(data.values())
356
 
@@ -365,7 +365,7 @@ class TestLegacyToolJsonParsing:
365
  """Test JSON string to set type coercion."""
366
  mcp = FastMCP()
367
 
368
- @mcp.tool()
369
  def process_set(items: set[int]) -> int:
370
  assert isinstance(items, set)
371
  return sum(items)
@@ -378,7 +378,7 @@ class TestLegacyToolJsonParsing:
378
  """Test JSON string to tuple type coercion."""
379
  mcp = FastMCP()
380
 
381
- @mcp.tool()
382
  def process_tuple(items: tuple[int, str]) -> int:
383
  assert isinstance(items, tuple)
384
  return items[0] + len(items[1])
 
320
  """Test JSON string to collection type coercion."""
321
  mcp = FastMCP()
322
 
323
+ @mcp.tool
324
  def process_list(items: list[int]) -> int:
325
  return sum(items)
326
 
 
335
  """Test that a list coercion error is raised if the input is not a valid list."""
336
  mcp = FastMCP()
337
 
338
+ @mcp.tool
339
  def process_list(items: list[int]) -> int:
340
  return sum(items)
341
 
 
350
  """Test JSON string to dict type coercion."""
351
  mcp = FastMCP()
352
 
353
+ @mcp.tool
354
  def process_dict(data: dict[str, int]) -> int:
355
  return sum(data.values())
356
 
 
365
  """Test JSON string to set type coercion."""
366
  mcp = FastMCP()
367
 
368
+ @mcp.tool
369
  def process_set(items: set[int]) -> int:
370
  assert isinstance(items, set)
371
  return sum(items)
 
378
  """Test JSON string to tuple type coercion."""
379
  mcp = FastMCP()
380
 
381
+ @mcp.tool
382
  def process_tuple(items: tuple[int, str]) -> int:
383
  assert isinstance(items, tuple)
384
  return items[0] + len(items[1])
tests/tools/test_tool_manager.py CHANGED
@@ -519,7 +519,7 @@ class TestCallTools:
519
  mcp = FastMCP(tool_serializer=custom_serializer)
520
  manager = mcp._tool_manager
521
 
522
- @mcp.tool()
523
  def get_data() -> dict:
524
  return {"key": "value", "number": 123}
525
 
@@ -537,7 +537,7 @@ class TestCallTools:
537
  mcp = FastMCP(tool_serializer=custom_serializer)
538
  manager = mcp._tool_manager
539
 
540
- @mcp.tool()
541
  def get_data() -> list[dict]:
542
  return [
543
  {"key": "value", "number": 123},
@@ -561,7 +561,7 @@ class TestCallTools:
561
  mcp = FastMCP(tool_serializer=custom_serializer)
562
  manager = mcp._tool_manager
563
 
564
- @mcp.tool()
565
  def get_data() -> uuid.UUID:
566
  return uuid_result
567
 
 
519
  mcp = FastMCP(tool_serializer=custom_serializer)
520
  manager = mcp._tool_manager
521
 
522
+ @mcp.tool
523
  def get_data() -> dict:
524
  return {"key": "value", "number": 123}
525
 
 
537
  mcp = FastMCP(tool_serializer=custom_serializer)
538
  manager = mcp._tool_manager
539
 
540
+ @mcp.tool
541
  def get_data() -> list[dict]:
542
  return [
543
  {"key": "value", "number": 123},
 
561
  mcp = FastMCP(tool_serializer=custom_serializer)
562
  manager = mcp._tool_manager
563
 
564
+ @mcp.tool
565
  def get_data() -> uuid.UUID:
566
  return uuid_result
567
 
tests/utilities/test_mcp_config.py CHANGED
@@ -102,7 +102,7 @@ async def test_multi_client(tmp_path: Path):
102
 
103
  mcp = FastMCP()
104
 
105
- @mcp.tool()
106
  def add(a: int, b: int) -> int:
107
  return a + b
108
 
 
102
 
103
  mcp = FastMCP()
104
 
105
+ @mcp.tool
106
  def add(a: int, b: int) -> int:
107
  return a + b
108