remove empty parens from tool

This commit is contained in:
Jeremiah Lowin 2025-06-05 14:55:32 -04:00
commit 5a0f57498f
20 changed files with 75 additions and 75 deletions

View file

@ -31,7 +31,7 @@ from fastmcp import FastMCP
mcp = FastMCP("Demo 🚀")
@mcp.tool()
@mcp.tool
def add(a: int, b: int) -> int:
"""Add two numbers"""
return a + b
@ -144,7 +144,7 @@ Learn more in the [**FastMCP Server Documentation**](https://gofastmcp.com/serve
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.
```python
@mcp.tool()
@mcp.tool
def multiply(a: float, b: float) -> float:
"""Multiplies two numbers."""
return a * b
@ -201,7 +201,7 @@ from fastmcp import FastMCP, Context
mcp = FastMCP("My MCP Server")
@mcp.tool()
@mcp.tool
async def process_data(uri: str, ctx: Context):
# Log a message to the client
await ctx.info(f"Processing {uri}...")
@ -321,7 +321,7 @@ from fastmcp import FastMCP
mcp = FastMCP("Demo 🚀")
@mcp.tool()
@mcp.tool
def hello(name: str) -> str:
return f"Hello, {name}!"

View file

@ -359,7 +359,7 @@ import asyncio
# 1. Create your FastMCP server instance
server = FastMCP(name="InMemoryServer")
@server.tool()
@server.tool
def ping():
return "pong"

View file

@ -32,7 +32,7 @@ from fastmcp import FastMCP
mcp = FastMCP("MyServer")
@mcp.tool()
@mcp.tool
def hello(name: str) -> str:
return f"Hello, {name}!"

View file

@ -22,7 +22,7 @@ from fastmcp import FastMCP
mcp = FastMCP(name="MyServer")
@mcp.tool()
@mcp.tool
def hello(name: str) -> str:
return f"Hello, {name}!"
@ -244,7 +244,7 @@ import asyncio
mcp = FastMCP(name="MyServer")
@mcp.tool()
@mcp.tool
def hello(name: str) -> str:
return f"Hello, {name}!"

View file

@ -32,7 +32,7 @@ from fastmcp import FastMCP
mcp = FastMCP("My MCP Server")
@mcp.tool()
@mcp.tool
def greet(name: str) -> str:
return f"Hello, {name}!"
```
@ -49,7 +49,7 @@ from fastmcp import FastMCP, Client
mcp = FastMCP("My MCP Server")
@mcp.tool()
@mcp.tool
def greet(name: str) -> str:
return f"Hello, {name}!"
@ -76,7 +76,7 @@ from fastmcp import FastMCP
mcp = FastMCP("My MCP Server")
@mcp.tool()
@mcp.tool
def greet(name: str) -> str:
return f"Hello, {name}!"

View file

@ -14,7 +14,7 @@ from fastmcp import FastMCP
mcp = FastMCP("Demo 🚀")
@mcp.tool()
@mcp.tool
def add(a: int, b: int) -> int:
"""Add two numbers"""
return a + b

View file

@ -28,7 +28,7 @@ from fastmcp import FastMCP
mcp = FastMCP(name="Dice Roller")
@mcp.tool()
@mcp.tool
def roll_dice(n_dice: int) -> list[int]:
"""Roll `n_dice` 6-sided dice and return the results."""
return [random.randint(1, 6) for _ in range(n_dice)]
@ -171,7 +171,7 @@ auth = BearerAuthProvider(
mcp = FastMCP(name="Dice Roller", auth=auth)
@mcp.tool()
@mcp.tool
def roll_dice(n_dice: int) -> list[int]:
"""Roll `n_dice` 6-sided dice and return the results."""
return [random.randint(1, 6) for _ in range(n_dice)]

View file

@ -31,7 +31,7 @@ from fastmcp import FastMCP
mcp = FastMCP(name="Dice Roller")
@mcp.tool()
@mcp.tool
def roll_dice(n_dice: int) -> list[int]:
"""Roll `n_dice` 6-sided dice and return the results."""
return [random.randint(1, 6) for _ in range(n_dice)]

View file

@ -32,7 +32,7 @@ from fastmcp import FastMCP
mcp = FastMCP(name="Dice Roller")
@mcp.tool()
@mcp.tool
def roll_dice(n_dice: int) -> list[int]:
"""Roll `n_dice` 6-sided dice and return the results."""
return [random.randint(1, 6) for _ in range(n_dice)]

View file

@ -33,7 +33,7 @@ from fastmcp import FastMCP
mcp = FastMCP(name="Dice Roller")
@mcp.tool()
@mcp.tool
def roll_dice(n_dice: int) -> list[int]:
"""Roll `n_dice` 6-sided dice and return the results."""
return [random.randint(1, 6) for _ in range(n_dice)]
@ -166,7 +166,7 @@ auth = BearerAuthProvider(
mcp = FastMCP(name="Dice Roller", auth=auth)
@mcp.tool()
@mcp.tool
def roll_dice(n_dice: int) -> list[int]:
"""Roll `n_dice` 6-sided dice and return the results."""
return [random.randint(1, 6) for _ in range(n_dice)]

View file

@ -66,7 +66,7 @@ from fastmcp import FastMCP
mcp = FastMCP("MyServer")
@mcp.tool()
@mcp.tool
def hello(name: str) -> str:
return f"Hello, {name}!"

View file

@ -9,7 +9,7 @@ FastMCP's decorator system is designed to work with functions, but you may see u
## Why Are Methods Hard?
When you apply a FastMCP decorator like `@tool`, `@resource()`, or `@prompt()` to a method, the decorator captures the function at decoration time. For instance methods and class methods, this poses a challenge because:
When you apply a FastMCP decorator like `@tool`, `@resource`, or `@prompt` to a method, the decorator captures the function at decoration time. For instance methods and class methods, this poses a challenge because:
1. For instance methods: The decorator gets the unbound method before any instance exists
2. For class methods: The decorator gets the function before it's bound to the class
@ -29,7 +29,7 @@ from fastmcp import FastMCP
mcp = FastMCP()
class MyClass:
@mcp.tool()
@mcp.tool
def my_method(self, x: int) -> int:
return x * 2
@ -53,7 +53,7 @@ from fastmcp import FastMCP
mcp = FastMCP()
class MyClass:
@mcp.tool() # This won't work correctly
@mcp.tool # This won't work correctly
def add(self, x, y):
return x + y
```
@ -100,19 +100,19 @@ mcp = FastMCP()
class MyClass:
@classmethod
@mcp.tool() # This won't work but won't raise an error
@mcp.tool # This won't work but won't raise an error
def from_string_v1(cls, s):
return cls(s)
@mcp.tool()
@mcp.tool
@classmethod # This will raise a helpful ValueError
def from_string_v2(cls, s):
return cls(s)
```
</Warning>
- If `@classmethod` comes first, then `@mcp.tool()`: No error is raised, but it won't work correctly
- If `@mcp.tool()` comes first, then `@classmethod`: FastMCP will detect this and raise a helpful `ValueError` with guidance
- If `@classmethod` comes first, then `@mcp.tool`: No error is raised, but it won't work correctly
- If `@mcp.tool` comes first, then `@classmethod`: FastMCP will detect this and raise a helpful `ValueError` with guidance
<Check>
**Do this instead**:
@ -150,7 +150,7 @@ from fastmcp import FastMCP
mcp = FastMCP()
class MyClass:
@mcp.tool()
@mcp.tool
@staticmethod
def utility(x, y):
return x + y

View file

@ -25,7 +25,7 @@ from starlette.requests import Request
mcp = FastMCP(name="HTTP Request Demo")
@mcp.tool()
@mcp.tool
async def user_agent_info() -> dict:
"""Return information about the user agent."""
# Get the HTTP request
@ -58,7 +58,7 @@ from fastmcp.server.dependencies import get_http_headers
mcp = FastMCP(name="Headers Demo")
@mcp.tool()
@mcp.tool
async def safe_header_info() -> dict:
"""Safely get header information without raising errors."""
# Get headers (returns empty dict if no request context)

View file

@ -22,7 +22,7 @@ from fastmcp import FastMCP, Client
def mcp_server():
server = FastMCP("TestServer")
@server.tool()
@server.tool
def greet(name: str) -> str:
return f"Hello, {name}!"

View file

@ -160,7 +160,7 @@ Once authenticated, your tools, resources, or prompts can access token informati
from fastmcp import FastMCP, Context, ToolError
from fastmcp.server.dependencies import get_access_token, AccessToken
@mcp.tool()
@mcp.tool
async def get_my_data(ctx: Context) -> dict:
access_token: AccessToken = get_access_token()

View file

@ -50,7 +50,7 @@ import asyncio
# Define subservers
weather_mcp = FastMCP(name="WeatherService")
@weather_mcp.tool()
@weather_mcp.tool
def get_forecast(city: str) -> dict:
"""Get weather forecast."""
return {"city": city, "forecast": "Sunny"}
@ -102,7 +102,7 @@ from fastmcp import FastMCP, Client
# Define subserver
dynamic_mcp = FastMCP(name="DynamicService")
@dynamic_mcp.tool()
@dynamic_mcp.tool
def initial_tool():
"""Initial tool demonstration."""
return "Initial Tool Exists"
@ -112,7 +112,7 @@ main_mcp = FastMCP(name="MainAppLive")
main_mcp.mount("dynamic", dynamic_mcp)
# Add a tool AFTER mounting - it will be accessible through main_mcp
@dynamic_mcp.tool()
@dynamic_mcp.tool
def added_later():
"""Tool added after mounting."""
return "Tool Added Dynamically!"

View file

@ -41,7 +41,7 @@ from fastmcp import FastMCP, Context
mcp = FastMCP(name="ContextDemo")
@mcp.tool()
@mcp.tool
async def process_file(file_uri: str, ctx: Context) -> str:
"""Processes a file, using context for logging and resource access."""
# Context is available as the ctx parameter
@ -99,7 +99,7 @@ async def process_data(data: list[float]) -> dict:
ctx = get_context()
await ctx.info(f"Processing {len(data)} data points")
@mcp.tool()
@mcp.tool
async def analyze_dataset(dataset_name: str) -> dict:
# Call utility function that uses context internally
data = load_data(dataset_name)
@ -118,7 +118,7 @@ async def analyze_dataset(dataset_name: str) -> dict:
Send log messages back to the MCP client. This is useful for debugging and providing visibility into function execution during a request.
```python
@mcp.tool()
@mcp.tool
async def analyze_data(data: list[float], ctx: Context) -> dict:
"""Analyze numerical data with logging."""
await ctx.debug("Starting analysis of numerical data")
@ -149,7 +149,7 @@ async def analyze_data(data: list[float], ctx: Context) -> dict:
For long-running operations, notify the client about the progress. This allows clients to display progress indicators and provide a better user experience.
```python
@mcp.tool()
@mcp.tool
async def process_items(items: list[str], ctx: Context) -> dict:
"""Process a list of items with progress updates."""
total = len(items)
@ -182,7 +182,7 @@ Progress reporting requires the client to have sent a `progressToken` in the ini
Read data from resources registered with your FastMCP server. This allows functions to access files, configuration, or dynamically generated content.
```python
@mcp.tool()
@mcp.tool
async def summarize_document(document_uri: str, ctx: Context) -> str:
"""Summarize a document by its resource URI."""
# Read the document content
@ -222,7 +222,7 @@ The returned content is typically accessed via `content_list[0].content` and can
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.
```python
@mcp.tool()
@mcp.tool
async def analyze_sentiment(text: str, ctx: Context) -> dict:
"""Analyze the sentiment of a text using the client's LLM."""
# Create a sampling prompt asking for sentiment analysis
@ -258,7 +258,7 @@ async def analyze_sentiment(text: str, ctx: Context) -> dict:
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.
```python
@mcp.tool()
@mcp.tool
async def generate_example(concept: str, ctx: Context) -> str:
"""Generate a Python code example for a given concept."""
# 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
Access metadata about the current request and client.
```python
@mcp.tool()
@mcp.tool
async def request_info(ctx: Context) -> dict:
"""Return information about the current request."""
return {
@ -300,7 +300,7 @@ async def request_info(ctx: Context) -> dict:
#### FastMCP Server and Sessions
```python
@mcp.tool()
@mcp.tool
async def advanced_tool(ctx: Context) -> str:
"""Demonstrate advanced context access."""
# Access the FastMCP server instance
@ -326,7 +326,7 @@ See the [HTTP Requests pattern](/patterns/http-requests) for more details.
For web applications, you can access the underlying HTTP request:
```python
@mcp.tool()
@mcp.tool
async def handle_web_request(ctx: Context) -> dict:
"""Access HTTP request information from the Starlette request."""
request = ctx.get_http_request()

View file

@ -47,7 +47,7 @@ FastMCP servers expose several types of components to the client:
Tools are functions that the client can call to perform actions or access external systems.
```python
@mcp.tool()
@mcp.tool
def multiply(a: float, b: float) -> float:
"""Multiplies two numbers together."""
return a * b
@ -106,7 +106,7 @@ from fastmcp import FastMCP
mcp = FastMCP(name="MyServer")
@mcp.tool()
@mcp.tool
def greet(name: str) -> str:
"""Greet a user by name."""
return f"Hello, {name}!"
@ -145,7 +145,7 @@ import asyncio
main = FastMCP(name="Main")
sub = FastMCP(name="Sub")
@sub.tool()
@sub.tool
def hello():
return "hi"
@ -216,7 +216,7 @@ def yaml_serializer(data):
# Create a server with the custom serializer
mcp = FastMCP(name="MyServer", tool_serializer=yaml_serializer)
@mcp.tool()
@mcp.tool
def get_config():
"""Returns configuration in YAML format."""
return {"api_key": "abc123", "debug": True, "rate_limit": 100}

View file

@ -90,7 +90,7 @@ from fastmcp import FastMCP
# Original server
original_server = FastMCP(name="Original")
@original_server.tool()
@original_server.tool
def tool_a() -> str:
return "A"

View file

@ -31,7 +31,7 @@ from fastmcp import FastMCP
mcp = FastMCP(name="CalculatorServer")
@mcp.tool()
@mcp.tool
def add(a: int, b: int) -> int:
"""Adds two integer numbers together."""
return a + b
@ -61,7 +61,7 @@ Type annotations for parameters are essential for proper tool functionality. The
Use standard Python type annotations for parameters:
```python
@mcp.tool()
@mcp.tool
def analyze_text(
text: str,
max_tokens: int = 100,
@ -79,7 +79,7 @@ You can provide additional metadata about parameters using Pydantic's `Field` cl
from typing import Annotated
from pydantic import Field
@mcp.tool()
@mcp.tool
def process_image(
image_url: Annotated[str, Field(description="URL of the image to process")],
resize: Annotated[bool, Field(description="Whether to resize the image")] = False,
@ -97,7 +97,7 @@ def process_image(
You can also use the Field as a default value, though the Annotated approach is preferred:
```python
@mcp.tool()
@mcp.tool
def search_database(
query: str = Field(description="Search query string"),
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
FastMCP follows Python's standard function parameter conventions. Parameters without default values are required, while those with default values are optional.
```python
@mcp.tool()
@mcp.tool
def search_products(
query: str, # Required - no default value
max_results: int = 10, # Optional - has default value
@ -197,14 +197,14 @@ FastMCP seamlessly supports both standard (`def`) and asynchronous (`async def`)
```python
# Synchronous tool (suitable for CPU-bound or quick tasks)
@mcp.tool()
@mcp.tool
def calculate_distance(lat1: float, lon1: float, lat2: float, lon2: float) -> float:
"""Calculate the distance between two coordinates."""
# Implementation...
return 42.5
# Asynchronous tool (ideal for I/O-bound operations)
@mcp.tool()
@mcp.tool
async def fetch_weather(city: str) -> dict:
"""Retrieve current weather conditions for a city."""
# Use 'async def' for operations involving network calls, file I/O, etc.
@ -244,7 +244,7 @@ except ImportError:
mcp = FastMCP("Image Demo")
@mcp.tool()
@mcp.tool
def generate_image(width: int, height: int, color: str) -> Image:
"""Generates a solid color image."""
# Create image using Pillow
@ -258,7 +258,7 @@ def generate_image(width: int, height: int, color: str) -> Image:
# Return using FastMCP's Image helper
return Image(data=img_bytes, format="png")
@mcp.tool()
@mcp.tool
def do_nothing() -> None:
"""This tool performs an action but returns no data."""
print("Performing a side effect...")
@ -285,7 +285,7 @@ mcp = FastMCP(name="SecureServer", mask_error_details=True)
from fastmcp import FastMCP
from fastmcp.exceptions import ToolError
@mcp.tool()
@mcp.tool
def divide(a: float, b: float) -> float:
"""Divide a by b."""
@ -351,7 +351,7 @@ from fastmcp import FastMCP, Context
mcp = FastMCP(name="ContextDemo")
@mcp.tool()
@mcp.tool
async def process_data(data_uri: str, ctx: Context) -> dict:
"""Process data from a resource with progress reporting."""
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
The most common parameter types are Python's built-in scalar types:
```python
@mcp.tool()
@mcp.tool
def process_values(
name: str, # Text data
count: int, # Integer numbers
@ -416,7 +416,7 @@ FastMCP supports various date and time types from the `datetime` module:
```python
from datetime import datetime, date, timedelta
@mcp.tool()
@mcp.tool
def process_date_time(
event_date: date, # ISO format date string or date object
event_time: datetime, # ISO format datetime string or datetime object
@ -440,7 +440,7 @@ def process_date_time(
FastMCP supports all standard Python collection types:
```python
@mcp.tool()
@mcp.tool
def analyze_data(
values: list[float], # List of numbers
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
For parameters that can accept multiple types or may be omitted:
```python
@mcp.tool()
@mcp.tool
def flexible_search(
query: str | int, # Can be either string or integer
filters: dict[str, str] | None = None, # Optional dictionary
@ -488,7 +488,7 @@ Literals constrain parameters to a specific set of values:
```python
from typing import Literal
@mcp.tool()
@mcp.tool
def sort_data(
data: list[float],
order: Literal["ascending", "descending"] = "ascending",
@ -516,7 +516,7 @@ class Color(Enum):
GREEN = "green"
BLUE = "blue"
@mcp.tool()
@mcp.tool
def process_image(
image_path: str,
color_filter: Color = Color.RED
@ -539,7 +539,7 @@ There are two approaches to handling binary data in tool parameters:
#### Bytes
```python
@mcp.tool()
@mcp.tool
def process_binary(data: bytes):
"""Process binary data directly.
@ -563,7 +563,7 @@ FastMCP does not automatically decode base64-encoded strings for bytes parameter
from typing import Annotated
from pydantic import Field
@mcp.tool()
@mcp.tool
def process_image_data(
image_data: Annotated[str, Field(description="Base64-encoded image data")]
):
@ -587,7 +587,7 @@ The `Path` type from the `pathlib` module can be used for file system paths:
```python
from pathlib import Path
@mcp.tool()
@mcp.tool
def process_file(path: Path) -> str:
"""Process a file at the given path."""
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:
```python
import uuid
@mcp.tool()
@mcp.tool
def process_item(
item_id: uuid.UUID # String UUID or UUID object
) -> str:
@ -628,7 +628,7 @@ class User(BaseModel):
age: int | None = None
is_active: bool = True
@mcp.tool()
@mcp.tool
def create_user(user: User):
"""Create a new user in the system."""
# 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
from typing import Annotated
from pydantic import Field
@mcp.tool()
@mcp.tool
def analyze_metrics(
# Numbers with range constraints
count: Annotated[int, Field(ge=0, le=100)], # 0 <= count <= 100
@ -682,7 +682,7 @@ def analyze_metrics(
You can also use `Field` as a default value, though the `Annotated` approach is preferred:
```python
@mcp.tool()
@mcp.tool
def validate_data(
# Value constraints
age: int = Field(ge=0, lt=120), # 0 <= age < 120
@ -727,7 +727,7 @@ mcp = FastMCP(
on_duplicate_tools="error"
)
@mcp.tool()
@mcp.tool
def my_tool(): return "Version 1"
# This will now raise a ValueError because 'my_tool' already exists
@ -754,7 +754,7 @@ from fastmcp import FastMCP
mcp = FastMCP(name="DynamicToolServer")
@mcp.tool()
@mcp.tool
def calculate_sum(a: int, b: int) -> int:
"""Add two numbers together."""
return a + b