diff --git a/README.md b/README.md index 6ea094b26..e9ce712ef 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/clients/transports.mdx b/docs/clients/transports.mdx index 6ed9fc489..94c94833c 100644 --- a/docs/clients/transports.mdx +++ b/docs/clients/transports.mdx @@ -359,7 +359,7 @@ import asyncio # 1. Create your FastMCP server instance server = FastMCP(name="InMemoryServer") -@server.tool() +@server.tool def ping(): return "pong" 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 4d39a1afd..041040b00 100644 --- a/docs/integrations/anthropic.mdx +++ b/docs/integrations/anthropic.mdx @@ -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)] 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 41668232e..1af4de073 100644 --- a/docs/integrations/gemini.mdx +++ b/docs/integrations/gemini.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)] diff --git a/docs/integrations/openai.mdx b/docs/integrations/openai.mdx index 35546d561..a5b1e933f 100644 --- a/docs/integrations/openai.mdx +++ b/docs/integrations/openai.mdx @@ -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)] 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/decorating-methods.mdx b/docs/patterns/decorating-methods.mdx index 7337cfe15..3b2c22dd6 100644 --- a/docs/patterns/decorating-methods.mdx +++ b/docs/patterns/decorating-methods.mdx @@ -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) ``` -- 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 **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 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/patterns/testing.mdx b/docs/patterns/testing.mdx index c8df2c759..910ac6686 100644 --- a/docs/patterns/testing.mdx +++ b/docs/patterns/testing.mdx @@ -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}!" diff --git a/docs/servers/auth/bearer.mdx b/docs/servers/auth/bearer.mdx index a7dc9e5ed..257238bc1 100644 --- a/docs/servers/auth/bearer.mdx +++ b/docs/servers/auth/bearer.mdx @@ -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() diff --git a/docs/servers/composition.mdx b/docs/servers/composition.mdx index b1519b3f3..04dc559d0 100644 --- a/docs/servers/composition.mdx +++ b/docs/servers/composition.mdx @@ -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!" 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..40808fb95 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}!" @@ -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} diff --git a/docs/servers/proxy.mdx b/docs/servers/proxy.mdx index d78d6d694..5a9bffccd 100644 --- a/docs/servers/proxy.mdx +++ b/docs/servers/proxy.mdx @@ -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" diff --git a/docs/servers/tools.mdx b/docs/servers/tools.mdx index 6b1a54356..00fa13252 100644 --- a/docs/servers/tools.mdx +++ b/docs/servers/tools.mdx @@ -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