Spaces:
Running
Running
Jeremiah Lowin commited on
Commit ·
09438a8
1
Parent(s): 26ff0e9
Remove empty parens
Browse files- README.md +4 -4
- docs/deployment/asgi.mdx +1 -1
- docs/deployment/running-server.mdx +2 -2
- docs/getting-started/quickstart.mdx +3 -3
- docs/getting-started/welcome.mdx +1 -1
- docs/integrations/anthropic.mdx +2 -2
- docs/integrations/claude-desktop.mdx +1 -1
- docs/integrations/gemini.mdx +1 -1
- docs/integrations/openai.mdx +2 -2
- docs/patterns/cli.mdx +1 -1
- docs/patterns/http-requests.mdx +2 -2
- docs/servers/auth/bearer.mdx +1 -1
- docs/servers/context.mdx +10 -10
- docs/servers/fastmcp.mdx +3 -3
- docs/servers/tools.mdx +29 -29
- examples/complex_inputs.py +1 -1
- examples/config_server.py +2 -2
- examples/desktop.py +1 -1
- examples/echo.py +1 -1
- examples/memory.py +2 -2
- examples/sampling.py +1 -1
- examples/screenshot.py +1 -1
- examples/simple_echo.py +1 -1
- src/fastmcp/contrib/bulk_tool_caller/example.py +1 -1
- tests/auth/providers/test_bearer.py +1 -1
- tests/client/test_client.py +3 -3
- tests/client/test_logs.py +2 -2
- tests/client/test_progress.py +1 -1
- tests/client/test_roots.py +1 -1
- tests/client/test_sampling.py +3 -3
- tests/client/test_stdio.py +1 -1
- tests/server/test_file_server.py +1 -1
- tests/server/test_server.py +7 -7
- tests/server/test_server_interactions.py +43 -43
- tests/tools/test_tool.py +5 -5
- tests/tools/test_tool_manager.py +3 -3
- tests/utilities/test_mcp_config.py +1 -1
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
|
| 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 |
|