From d1549c3d3512c5299e0203ad8db993df8953a23a Mon Sep 17 00:00:00 2001 From: Jeremiah Lowin <153965+jlowin@users.noreply.github.com> Date: Wed, 4 Jun 2025 15:37:55 -0400 Subject: [PATCH] Remove empty parens --- README.md | 8 +- docs/deployment/asgi.mdx | 2 +- docs/deployment/running-server.mdx | 4 +- docs/getting-started/quickstart.mdx | 6 +- docs/getting-started/welcome.mdx | 2 +- docs/integrations/anthropic.mdx | 4 +- docs/integrations/claude-desktop.mdx | 2 +- docs/integrations/gemini.mdx | 2 +- docs/integrations/openai.mdx | 4 +- docs/patterns/cli.mdx | 2 +- docs/patterns/http-requests.mdx | 4 +- docs/servers/auth/bearer.mdx | 2 +- docs/servers/context.mdx | 20 ++--- docs/servers/fastmcp.mdx | 6 +- docs/servers/tools.mdx | 58 ++++++------- examples/complex_inputs.py | 2 +- examples/config_server.py | 4 +- examples/desktop.py | 2 +- examples/echo.py | 2 +- examples/memory.py | 4 +- examples/sampling.py | 2 +- examples/screenshot.py | 2 +- examples/simple_echo.py | 2 +- .../contrib/bulk_tool_caller/example.py | 2 +- tests/auth/providers/test_bearer.py | 2 +- tests/client/test_client.py | 6 +- tests/client/test_logs.py | 4 +- tests/client/test_progress.py | 2 +- tests/client/test_roots.py | 2 +- tests/client/test_sampling.py | 6 +- tests/client/test_stdio.py | 2 +- tests/server/test_file_server.py | 2 +- tests/server/test_server.py | 14 +-- tests/server/test_server_interactions.py | 86 +++++++++---------- tests/tools/test_tool.py | 10 +-- tests/tools/test_tool_manager.py | 6 +- tests/utilities/test_mcp_config.py | 2 +- 37 files changed, 146 insertions(+), 146 deletions(-) diff --git a/README.md b/README.md index c2552c910..dded24a46 100644 --- a/README.md +++ b/README.md @@ -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}!" diff --git a/docs/deployment/asgi.mdx b/docs/deployment/asgi.mdx index ace7ee136..06da46374 100644 --- a/docs/deployment/asgi.mdx +++ b/docs/deployment/asgi.mdx @@ -32,7 +32,7 @@ from fastmcp import FastMCP mcp = FastMCP("MyServer") -@mcp.tool() +@mcp.tool def hello(name: str) -> str: return f"Hello, {name}!" diff --git a/docs/deployment/running-server.mdx b/docs/deployment/running-server.mdx index 5f5d758d2..5b6a7eadd 100644 --- a/docs/deployment/running-server.mdx +++ b/docs/deployment/running-server.mdx @@ -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}!" diff --git a/docs/getting-started/quickstart.mdx b/docs/getting-started/quickstart.mdx index 13a6bbae5..b3569d486 100644 --- a/docs/getting-started/quickstart.mdx +++ b/docs/getting-started/quickstart.mdx @@ -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}!" diff --git a/docs/getting-started/welcome.mdx b/docs/getting-started/welcome.mdx index f5574217c..d9d921a7a 100644 --- a/docs/getting-started/welcome.mdx +++ b/docs/getting-started/welcome.mdx @@ -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 diff --git a/docs/integrations/anthropic.mdx b/docs/integrations/anthropic.mdx index 0920afce2..bcea25769 100644 --- a/docs/integrations/anthropic.mdx +++ b/docs/integrations/anthropic.mdx @@ -27,7 +27,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)] @@ -170,7 +170,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)] diff --git a/docs/integrations/claude-desktop.mdx b/docs/integrations/claude-desktop.mdx index 7edafa68b..9b8dc5404 100644 --- a/docs/integrations/claude-desktop.mdx +++ b/docs/integrations/claude-desktop.mdx @@ -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)] diff --git a/docs/integrations/gemini.mdx b/docs/integrations/gemini.mdx index d61d0feb0..46ffd7a9d 100644 --- a/docs/integrations/gemini.mdx +++ b/docs/integrations/gemini.mdx @@ -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)] diff --git a/docs/integrations/openai.mdx b/docs/integrations/openai.mdx index 86f01e626..52f3bb164 100644 --- a/docs/integrations/openai.mdx +++ b/docs/integrations/openai.mdx @@ -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)] @@ -165,7 +165,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)] diff --git a/docs/patterns/cli.mdx b/docs/patterns/cli.mdx index b46d928d4..19e1ad1ab 100644 --- a/docs/patterns/cli.mdx +++ b/docs/patterns/cli.mdx @@ -66,7 +66,7 @@ from fastmcp import FastMCP mcp = FastMCP("MyServer") -@mcp.tool() +@mcp.tool def hello(name: str) -> str: return f"Hello, {name}!" diff --git a/docs/patterns/http-requests.mdx b/docs/patterns/http-requests.mdx index ceb333e19..c9b412c5d 100644 --- a/docs/patterns/http-requests.mdx +++ b/docs/patterns/http-requests.mdx @@ -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) diff --git a/docs/servers/auth/bearer.mdx b/docs/servers/auth/bearer.mdx index c695c10e6..13053b5b2 100644 --- a/docs/servers/auth/bearer.mdx +++ b/docs/servers/auth/bearer.mdx @@ -159,7 +159,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() diff --git a/docs/servers/context.mdx b/docs/servers/context.mdx index 555a0c105..2818de3c0 100644 --- a/docs/servers/context.mdx +++ b/docs/servers/context.mdx @@ -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() diff --git a/docs/servers/fastmcp.mdx b/docs/servers/fastmcp.mdx index 598d056b6..ebb3fcb64 100644 --- a/docs/servers/fastmcp.mdx +++ b/docs/servers/fastmcp.mdx @@ -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}!" @@ -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} diff --git a/docs/servers/tools.mdx b/docs/servers/tools.mdx index 365a8611d..00fa13252 100644 --- a/docs/servers/tools.mdx +++ b/docs/servers/tools.mdx @@ -24,14 +24,14 @@ This allows LLMs to perform tasks like querying databases, calling APIs, making ### The `@tool` Decorator -Creating a tool is as simple as decorating a Python function with `@mcp.tool()`: +Creating a tool is as simple as decorating a Python function with `@mcp.tool`: ```python 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.""" @@ -315,7 +315,7 @@ Annotations serve several purposes in client applications: - Describing the safety profile of tools (destructive vs. non-destructive) - Signaling if tools interact with external systems -You can add annotations to a tool using the `annotations` parameter in the `@mcp.tool()` decorator: +You can add annotations to a tool using the `annotations` parameter in the `@mcp.tool` decorator: ```python @mcp.tool( @@ -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,12 +727,12 @@ 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 # and on_duplicate_tools is set to "error". -# @mcp.tool() +# @mcp.tool # def my_tool(): return "Version 2" ``` @@ -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 diff --git a/examples/complex_inputs.py b/examples/complex_inputs.py index 41276858f..014482a4a 100644 --- a/examples/complex_inputs.py +++ b/examples/complex_inputs.py @@ -20,7 +20,7 @@ class ShrimpTank(BaseModel): shrimp: list[Shrimp] -@mcp.tool() +@mcp.tool def name_shrimp( tank: ShrimpTank, # You can use pydantic Field in function signatures for validation. diff --git a/examples/config_server.py b/examples/config_server.py index d7964bc14..9d6976d8b 100644 --- a/examples/config_server.py +++ b/examples/config_server.py @@ -24,7 +24,7 @@ if args.debug: mcp = FastMCP(server_name) -@mcp.tool() +@mcp.tool def get_status() -> dict[str, str | bool]: """Get the current server configuration and status.""" return { @@ -34,7 +34,7 @@ def get_status() -> dict[str, str | bool]: } -@mcp.tool() +@mcp.tool def echo_message(message: str) -> str: """Echo a message, with debug info if debug mode is enabled.""" if args.debug: diff --git a/examples/desktop.py b/examples/desktop.py index 8ba0d4562..b32a31484 100644 --- a/examples/desktop.py +++ b/examples/desktop.py @@ -26,7 +26,7 @@ def get_greeting(name: str) -> str: return f"Hello, {name}!" -@mcp.tool() +@mcp.tool def add(a: int, b: int) -> int: """Add two numbers""" return a + b diff --git a/examples/echo.py b/examples/echo.py index 48c0883a5..c98f23d2f 100644 --- a/examples/echo.py +++ b/examples/echo.py @@ -8,7 +8,7 @@ from fastmcp import FastMCP mcp = FastMCP("Echo Server") -@mcp.tool() +@mcp.tool def echo_tool(text: str) -> str: """Echo the input text""" return text diff --git a/examples/memory.py b/examples/memory.py index 7be486dd3..161f0d7f5 100644 --- a/examples/memory.py +++ b/examples/memory.py @@ -279,7 +279,7 @@ async def display_memory_tree(deps: Deps) -> str: return result -@mcp.tool() +@mcp.tool async def remember( contents: list[str] = Field( description="List of observations or memories to store" @@ -294,7 +294,7 @@ async def remember( await deps.pool.close() -@mcp.tool() +@mcp.tool async def read_profile() -> str: deps = Deps(openai=AsyncOpenAI(), pool=await get_db_pool()) profile = await display_memory_tree(deps) diff --git a/examples/sampling.py b/examples/sampling.py index 385f9d576..cfb9c395a 100644 --- a/examples/sampling.py +++ b/examples/sampling.py @@ -15,7 +15,7 @@ from fastmcp.client.sampling import RequestContext, SamplingMessage, SamplingPar mcp = FastMCP("Sampling Example") -@mcp.tool() +@mcp.tool async def example_tool(prompt: str, context: Context) -> str: """Sample a completion from the LLM.""" response = await context.sample( diff --git a/examples/screenshot.py b/examples/screenshot.py index 968d55f52..92b2bdb01 100644 --- a/examples/screenshot.py +++ b/examples/screenshot.py @@ -12,7 +12,7 @@ from fastmcp import FastMCP, Image mcp = FastMCP("Screenshot Demo", dependencies=["pyautogui", "Pillow"]) -@mcp.tool() +@mcp.tool def take_screenshot() -> Image: """ Take a screenshot of the user's screen and return it as an image. Use diff --git a/examples/simple_echo.py b/examples/simple_echo.py index f98d8456a..b1dc1f35a 100644 --- a/examples/simple_echo.py +++ b/examples/simple_echo.py @@ -8,7 +8,7 @@ from fastmcp import FastMCP mcp = FastMCP("Echo Server") -@mcp.tool() +@mcp.tool def echo(text: str) -> str: """Echo the input text""" return text diff --git a/src/fastmcp/contrib/bulk_tool_caller/example.py b/src/fastmcp/contrib/bulk_tool_caller/example.py index 85139feda..b86a53e2a 100644 --- a/src/fastmcp/contrib/bulk_tool_caller/example.py +++ b/src/fastmcp/contrib/bulk_tool_caller/example.py @@ -6,7 +6,7 @@ from fastmcp.contrib.bulk_tool_caller import BulkToolCaller mcp = FastMCP() -@mcp.tool() +@mcp.tool def echo_tool(text: str) -> str: """Echo the input text""" return text diff --git a/tests/auth/providers/test_bearer.py b/tests/auth/providers/test_bearer.py index aed70af71..6f59c96fe 100644 --- a/tests/auth/providers/test_bearer.py +++ b/tests/auth/providers/test_bearer.py @@ -53,7 +53,7 @@ def run_mcp_server( ) ) - @mcp.tool() + @mcp.tool def add(a: int, b: int) -> int: return a + b diff --git a/tests/client/test_client.py b/tests/client/test_client.py index d1398640d..bfb75e492 100644 --- a/tests/client/test_client.py +++ b/tests/client/test_client.py @@ -510,7 +510,7 @@ class TestErrorHandling: async def test_general_tool_exceptions_are_not_masked_by_default(self): mcp = FastMCP("TestServer") - @mcp.tool() + @mcp.tool def error_tool(): raise ValueError("This is a test error (abc)") @@ -525,7 +525,7 @@ class TestErrorHandling: async def test_general_tool_exceptions_are_masked_when_enabled(self): mcp = FastMCP("TestServer", mask_error_details=True) - @mcp.tool() + @mcp.tool def error_tool(): raise ValueError("This is a test error (abc)") @@ -540,7 +540,7 @@ class TestErrorHandling: async def test_specific_tool_errors_are_sent_to_client(self): mcp = FastMCP("TestServer") - @mcp.tool() + @mcp.tool def custom_error_tool(): raise ToolError("This is a test error (abc)") diff --git a/tests/client/test_logs.py b/tests/client/test_logs.py index 93f7720a1..649bfe185 100644 --- a/tests/client/test_logs.py +++ b/tests/client/test_logs.py @@ -17,11 +17,11 @@ class LogHandler: def fastmcp_server(): mcp = FastMCP() - @mcp.tool() + @mcp.tool async def log(context: Context) -> None: await context.info(message="hello?") - @mcp.tool() + @mcp.tool async def echo_log( message: str, context: Context, diff --git a/tests/client/test_progress.py b/tests/client/test_progress.py index f67f5c54c..63244df7c 100644 --- a/tests/client/test_progress.py +++ b/tests/client/test_progress.py @@ -16,7 +16,7 @@ def clear_progress_messages(): def fastmcp_server(): mcp = FastMCP() - @mcp.tool() + @mcp.tool async def progress_tool(context: Context) -> int: for i in range(3): await context.report_progress( diff --git a/tests/client/test_roots.py b/tests/client/test_roots.py index 91739aa6b..f4df827de 100644 --- a/tests/client/test_roots.py +++ b/tests/client/test_roots.py @@ -9,7 +9,7 @@ from fastmcp import Client, Context, FastMCP def fastmcp_server(): mcp = FastMCP() - @mcp.tool() + @mcp.tool async def list_roots(context: Context) -> list[str]: roots = await context.list_roots() return [str(r.uri) for r in roots] diff --git a/tests/client/test_sampling.py b/tests/client/test_sampling.py index 15c2a2ff1..497aa8513 100644 --- a/tests/client/test_sampling.py +++ b/tests/client/test_sampling.py @@ -11,17 +11,17 @@ from fastmcp.client.sampling import RequestContext, SamplingMessage, SamplingPar def fastmcp_server(): mcp = FastMCP() - @mcp.tool() + @mcp.tool async def simple_sample(message: str, context: Context) -> str: result = await context.sample("Hello, world!") return cast(TextContent, result).text - @mcp.tool() + @mcp.tool async def sample_with_system_prompt(message: str, context: Context) -> str: result = await context.sample("Hello, world!", system_prompt="You love FastMCP") return cast(TextContent, result).text - @mcp.tool() + @mcp.tool async def sample_with_messages(message: str, context: Context) -> str: result = await context.sample( [ diff --git a/tests/client/test_stdio.py b/tests/client/test_stdio.py index c32975b48..d9f9247d8 100644 --- a/tests/client/test_stdio.py +++ b/tests/client/test_stdio.py @@ -17,7 +17,7 @@ class TestKeepAlive: mcp = FastMCP() - @mcp.tool() + @mcp.tool def pid() -> int: """Gets PID of server""" return os.getpid() diff --git a/tests/server/test_file_server.py b/tests/server/test_file_server.py index b483ac110..c10b44519 100644 --- a/tests/server/test_file_server.py +++ b/tests/server/test_file_server.py @@ -62,7 +62,7 @@ def resources(mcp: FastMCP, test_dir: Path) -> FastMCP: @pytest.fixture(autouse=True) def tools(mcp: FastMCP, test_dir: Path) -> FastMCP: - @mcp.tool() + @mcp.tool def delete_file(path: str) -> bool: # ensure path is in test_dir if Path(path).resolve().parent != test_dir: diff --git a/tests/server/test_server.py b/tests/server/test_server.py index 198deefd5..00d4271ec 100644 --- a/tests/server/test_server.py +++ b/tests/server/test_server.py @@ -57,7 +57,7 @@ class TestTools: mcp = FastMCP() - @mcp.tool() + @mcp.tool def fn(x: int) -> int: return x + 1 @@ -126,7 +126,7 @@ class TestToolDecorator: async def test_tool_decorator(self): mcp = FastMCP() - @mcp.tool() + @mcp.tool def add(x: int, y: int) -> int: return x + y @@ -179,7 +179,7 @@ class TestToolDecorator: def __init__(self, x: int): self.x = x - @mcp.tool() + @mcp.tool def add(self, y: int) -> int: return self.x + y @@ -207,7 +207,7 @@ class TestToolDecorator: class MyClass: @staticmethod - @mcp.tool() + @mcp.tool def add(x: int, y: int) -> int: return x + y @@ -217,7 +217,7 @@ class TestToolDecorator: async def test_tool_decorator_async_function(self): mcp = FastMCP() - @mcp.tool() + @mcp.tool async def add(x: int, y: int) -> int: return x + y @@ -288,7 +288,7 @@ class TestToolDecorator: """Test that tools with annotated arguments work correctly.""" mcp = FastMCP() - @mcp.tool() + @mcp.tool def add( x: Annotated[int, Field(description="x is an int")], y: Annotated[str, Field(description="y is not an int")], @@ -303,7 +303,7 @@ class TestToolDecorator: """Test that tools with annotated arguments work correctly.""" mcp = FastMCP() - @mcp.tool() + @mcp.tool def add( x: int = Field(description="x is an int"), y: str = Field(description="y is not an int"), diff --git a/tests/server/test_server_interactions.py b/tests/server/test_server_interactions.py index 4e4e5d27b..19fe2c3f6 100644 --- a/tests/server/test_server_interactions.py +++ b/tests/server/test_server_interactions.py @@ -30,30 +30,30 @@ from fastmcp.utilities.types import Image def tool_server(): mcp = FastMCP() - @mcp.tool() + @mcp.tool def add(x: int, y: int) -> int: return x + y - @mcp.tool() + @mcp.tool def list_tool() -> list[str | int]: return ["x", 2] - @mcp.tool() + @mcp.tool def error_tool() -> None: raise ValueError("Test error") - @mcp.tool() + @mcp.tool def image_tool(path: str) -> Image: return Image(path) - @mcp.tool() + @mcp.tool def mixed_content_tool() -> list[TextContent | ImageContent]: return [ TextContent(type="text", text="Hello"), ImageContent(type="image", data="abc", mimeType="image/png"), ] - @mcp.tool() + @mcp.tool def mixed_list_fn(image_path: str) -> list: return [ "text message", @@ -100,7 +100,7 @@ class TestTools: mcp = FastMCP() client = Client(transport=FastMCPTransport(mcp)) - @mcp.tool() + @mcp.tool def error_tool(): raise ValueError("Test error") @@ -119,7 +119,7 @@ class TestToolReturnTypes: async def test_string(self): mcp = FastMCP() - @mcp.tool() + @mcp.tool def string_tool() -> str: return "Hello, world!" @@ -130,7 +130,7 @@ class TestToolReturnTypes: async def test_bytes(self, tmp_path: Path): mcp = FastMCP() - @mcp.tool() + @mcp.tool def bytes_tool() -> bytes: return b"Hello, world!" @@ -143,7 +143,7 @@ class TestToolReturnTypes: test_uuid = uuid.uuid4() - @mcp.tool() + @mcp.tool def uuid_tool() -> uuid.UUID: return test_uuid @@ -156,7 +156,7 @@ class TestToolReturnTypes: test_path = Path("/tmp/test.txt") - @mcp.tool() + @mcp.tool def path_tool() -> Path: return test_path @@ -169,7 +169,7 @@ class TestToolReturnTypes: dt = datetime.datetime(2025, 4, 25, 1, 2, 3) - @mcp.tool() + @mcp.tool def datetime_tool() -> datetime.datetime: return dt @@ -180,7 +180,7 @@ class TestToolReturnTypes: async def test_image(self, tmp_path: Path): mcp = FastMCP() - @mcp.tool() + @mcp.tool def image_tool(path: str) -> Image: return Image(path) @@ -243,7 +243,7 @@ class TestToolParameters: async def test_parameter_descriptions_with_field_annotations(self): mcp = FastMCP("Test Server") - @mcp.tool() + @mcp.tool def greet( name: Annotated[str, Field(description="The name to greet")], title: Annotated[str, Field(description="Optional title", default="")], @@ -268,7 +268,7 @@ class TestToolParameters: async def test_parameter_descriptions_with_field_defaults(self): mcp = FastMCP("Test Server") - @mcp.tool() + @mcp.tool def greet( name: str = Field(description="The name to greet"), title: str = Field(description="Optional title", default=""), @@ -293,7 +293,7 @@ class TestToolParameters: async def test_tool_with_bytes_input(self): mcp = FastMCP() - @mcp.tool() + @mcp.tool def process_image(image: bytes) -> Image: return Image(data=image) @@ -308,7 +308,7 @@ class TestToolParameters: async def test_tool_with_invalid_input(self): mcp = FastMCP() - @mcp.tool() + @mcp.tool def my_tool(x: int) -> int: return x + 1 @@ -323,7 +323,7 @@ class TestToolParameters: """Test string-to-int type coercion.""" mcp = FastMCP() - @mcp.tool() + @mcp.tool def add_one(x: int) -> int: return x + 1 @@ -336,7 +336,7 @@ class TestToolParameters: """Test string-to-bool type coercion.""" mcp = FastMCP() - @mcp.tool() + @mcp.tool def toggle(flag: bool) -> bool: return not flag @@ -351,7 +351,7 @@ class TestToolParameters: async def test_annotated_field_validation(self): mcp = FastMCP() - @mcp.tool() + @mcp.tool def analyze(x: Annotated[int, Field(ge=1)]) -> None: pass @@ -362,7 +362,7 @@ class TestToolParameters: async def test_default_field_validation(self): mcp = FastMCP() - @mcp.tool() + @mcp.tool def analyze(x: int = Field(ge=1)) -> None: pass @@ -373,7 +373,7 @@ class TestToolParameters: async def test_default_field_is_still_required_if_no_default_specified(self): mcp = FastMCP() - @mcp.tool() + @mcp.tool def analyze(x: int = Field()) -> None: pass @@ -384,7 +384,7 @@ class TestToolParameters: async def test_literal_type_validation_error(self): mcp = FastMCP() - @mcp.tool() + @mcp.tool def analyze(x: Literal["a", "b"]) -> None: pass @@ -395,7 +395,7 @@ class TestToolParameters: async def test_literal_type_validation_success(self): mcp = FastMCP() - @mcp.tool() + @mcp.tool def analyze(x: Literal["a", "b"]) -> str: return x @@ -411,7 +411,7 @@ class TestToolParameters: GREEN = "green" BLUE = "blue" - @mcp.tool() + @mcp.tool def analyze(x: MyEnum) -> str: return x.value @@ -427,7 +427,7 @@ class TestToolParameters: GREEN = "green" BLUE = "blue" - @mcp.tool() + @mcp.tool def analyze(x: MyEnum) -> str: return x.value @@ -438,7 +438,7 @@ class TestToolParameters: async def test_union_type_validation(self): mcp = FastMCP() - @mcp.tool() + @mcp.tool def analyze(x: int | float) -> str: return str(x) @@ -455,7 +455,7 @@ class TestToolParameters: async def test_path_type(self): mcp = FastMCP() - @mcp.tool() + @mcp.tool def send_path(path: Path) -> str: assert isinstance(path, Path) return str(path) @@ -470,7 +470,7 @@ class TestToolParameters: async def test_path_type_error(self): mcp = FastMCP() - @mcp.tool() + @mcp.tool def send_path(path: Path) -> str: return str(path) @@ -481,7 +481,7 @@ class TestToolParameters: async def test_uuid_type(self): mcp = FastMCP() - @mcp.tool() + @mcp.tool def send_uuid(x: uuid.UUID) -> str: assert isinstance(x, uuid.UUID) return str(x) @@ -495,7 +495,7 @@ class TestToolParameters: async def test_uuid_type_error(self): mcp = FastMCP() - @mcp.tool() + @mcp.tool def send_uuid(x: uuid.UUID) -> str: return str(x) @@ -506,7 +506,7 @@ class TestToolParameters: async def test_datetime_type(self): mcp = FastMCP() - @mcp.tool() + @mcp.tool def send_datetime(x: datetime.datetime) -> str: return x.isoformat() @@ -519,7 +519,7 @@ class TestToolParameters: async def test_datetime_type_parse_string(self): mcp = FastMCP() - @mcp.tool() + @mcp.tool def send_datetime(x: datetime.datetime) -> str: return x.isoformat() @@ -532,7 +532,7 @@ class TestToolParameters: async def test_datetime_type_error(self): mcp = FastMCP() - @mcp.tool() + @mcp.tool def send_datetime(x: datetime.datetime) -> str: return x.isoformat() @@ -543,7 +543,7 @@ class TestToolParameters: async def test_date_type(self): mcp = FastMCP() - @mcp.tool() + @mcp.tool def send_date(x: datetime.date) -> str: return x.isoformat() @@ -554,7 +554,7 @@ class TestToolParameters: async def test_date_type_parse_string(self): mcp = FastMCP() - @mcp.tool() + @mcp.tool def send_date(x: datetime.date) -> str: return x.isoformat() @@ -565,7 +565,7 @@ class TestToolParameters: async def test_timedelta_type(self): mcp = FastMCP() - @mcp.tool() + @mcp.tool def send_timedelta(x: datetime.timedelta) -> str: return str(x) @@ -578,7 +578,7 @@ class TestToolParameters: async def test_timedelta_type_parse_int(self): mcp = FastMCP() - @mcp.tool() + @mcp.tool def send_timedelta(x: datetime.timedelta) -> str: return str(x) @@ -594,7 +594,7 @@ class TestToolContextInjection: """Test that context parameters are properly detected.""" mcp = FastMCP() - @mcp.tool() + @mcp.tool def tool_with_context(x: int, ctx: Context) -> str: return f"Request {ctx.request_id}: {x}" @@ -607,7 +607,7 @@ class TestToolContextInjection: """Test that context is properly injected into tool calls.""" mcp = FastMCP() - @mcp.tool() + @mcp.tool def tool_with_context(x: int, ctx: Context) -> str: assert isinstance(ctx, Context) assert ctx.request_id is not None @@ -623,7 +623,7 @@ class TestToolContextInjection: """Test that context works in async functions.""" mcp = FastMCP() - @mcp.tool() + @mcp.tool async def async_tool(x: int, ctx: Context) -> str: assert ctx.request_id is not None return f"Async request {ctx.request_id}: {x}" @@ -638,7 +638,7 @@ class TestToolContextInjection: """Test that context is optional.""" mcp = FastMCP() - @mcp.tool() + @mcp.tool def no_context(x: int) -> int: return x * 2 @@ -656,7 +656,7 @@ class TestToolContextInjection: def test_resource() -> str: return "resource data" - @mcp.tool() + @mcp.tool async def tool_with_resource(ctx: Context) -> str: r_iter = await ctx.read_resource("test://data") r_list = list(r_iter) diff --git a/tests/tools/test_tool.py b/tests/tools/test_tool.py index 97f6fdf81..ab2808fb7 100644 --- a/tests/tools/test_tool.py +++ b/tests/tools/test_tool.py @@ -320,7 +320,7 @@ class TestLegacyToolJsonParsing: """Test JSON string to collection type coercion.""" mcp = FastMCP() - @mcp.tool() + @mcp.tool def process_list(items: list[int]) -> int: return sum(items) @@ -335,7 +335,7 @@ class TestLegacyToolJsonParsing: """Test that a list coercion error is raised if the input is not a valid list.""" mcp = FastMCP() - @mcp.tool() + @mcp.tool def process_list(items: list[int]) -> int: return sum(items) @@ -350,7 +350,7 @@ class TestLegacyToolJsonParsing: """Test JSON string to dict type coercion.""" mcp = FastMCP() - @mcp.tool() + @mcp.tool def process_dict(data: dict[str, int]) -> int: return sum(data.values()) @@ -365,7 +365,7 @@ class TestLegacyToolJsonParsing: """Test JSON string to set type coercion.""" mcp = FastMCP() - @mcp.tool() + @mcp.tool def process_set(items: set[int]) -> int: assert isinstance(items, set) return sum(items) @@ -378,7 +378,7 @@ class TestLegacyToolJsonParsing: """Test JSON string to tuple type coercion.""" mcp = FastMCP() - @mcp.tool() + @mcp.tool def process_tuple(items: tuple[int, str]) -> int: assert isinstance(items, tuple) return items[0] + len(items[1]) diff --git a/tests/tools/test_tool_manager.py b/tests/tools/test_tool_manager.py index 83f20a745..dadf3f000 100644 --- a/tests/tools/test_tool_manager.py +++ b/tests/tools/test_tool_manager.py @@ -519,7 +519,7 @@ class TestCallTools: mcp = FastMCP(tool_serializer=custom_serializer) manager = mcp._tool_manager - @mcp.tool() + @mcp.tool def get_data() -> dict: return {"key": "value", "number": 123} @@ -537,7 +537,7 @@ class TestCallTools: mcp = FastMCP(tool_serializer=custom_serializer) manager = mcp._tool_manager - @mcp.tool() + @mcp.tool def get_data() -> list[dict]: return [ {"key": "value", "number": 123}, @@ -561,7 +561,7 @@ class TestCallTools: mcp = FastMCP(tool_serializer=custom_serializer) manager = mcp._tool_manager - @mcp.tool() + @mcp.tool def get_data() -> uuid.UUID: return uuid_result diff --git a/tests/utilities/test_mcp_config.py b/tests/utilities/test_mcp_config.py index bd0c84bee..ec334153d 100644 --- a/tests/utilities/test_mcp_config.py +++ b/tests/utilities/test_mcp_config.py @@ -102,7 +102,7 @@ async def test_multi_client(tmp_path: Path): mcp = FastMCP() - @mcp.tool() + @mcp.tool def add(a: int, b: int) -> int: return a + b