Spaces:
Running
Running
Merge pull request #706 from jlowin/object-functions
Browse filesSupport flexible @tool decorator call patterns
This view is limited to 50 files because it contains too many changes. See raw diff
- README.md +4 -4
- docs/clients/transports.mdx +1 -1
- 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/decorating-methods.mdx +6 -6
- docs/patterns/http-requests.mdx +2 -2
- docs/patterns/testing.mdx +1 -1
- docs/servers/auth/bearer.mdx +1 -1
- docs/servers/composition.mdx +3 -3
- docs/servers/context.mdx +10 -10
- docs/servers/fastmcp.mdx +4 -4
- docs/servers/proxy.mdx +1 -1
- 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/mount_example.py +3 -3
- examples/sampling.py +1 -1
- examples/screenshot.py +1 -1
- examples/serializer.py +1 -1
- examples/simple_echo.py +1 -1
- examples/smart_home/src/smart_home/hub.py +1 -1
- examples/smart_home/src/smart_home/lights/server.py +9 -9
- src/fastmcp/contrib/bulk_tool_caller/example.py +1 -1
- src/fastmcp/server/context.py +1 -1
- src/fastmcp/server/server.py +59 -19
- tests/auth/providers/test_bearer.py +1 -1
- tests/auth/test_oauth_client.py +1 -1
- tests/client/test_client.py +7 -7
- 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_sse.py +3 -3
- tests/client/test_stdio.py +1 -1
- tests/client/test_streamable_http.py +3 -3
- tests/deprecated/test_deprecated.py +1 -1
- tests/deprecated/test_mount_separators.py +1 -1
- tests/server/http/test_http_dependencies.py +1 -1
- tests/server/test_file_server.py +1 -1
- tests/server/test_import_server.py +6 -6
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/clients/transports.mdx
CHANGED
|
@@ -359,7 +359,7 @@ import asyncio
|
|
| 359 |
# 1. Create your FastMCP server instance
|
| 360 |
server = FastMCP(name="InMemoryServer")
|
| 361 |
|
| 362 |
-
@server.tool
|
| 363 |
def ping():
|
| 364 |
return "pong"
|
| 365 |
|
|
|
|
| 359 |
# 1. Create your FastMCP server instance
|
| 360 |
server = FastMCP(name="InMemoryServer")
|
| 361 |
|
| 362 |
+
@server.tool
|
| 363 |
def ping():
|
| 364 |
return "pong"
|
| 365 |
|
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/decorating-methods.mdx
CHANGED
|
@@ -5,11 +5,11 @@ description: Properly use instance methods, class methods, and static methods wi
|
|
| 5 |
icon: at
|
| 6 |
---
|
| 7 |
|
| 8 |
-
FastMCP's decorator system is designed to work with functions, but you may see unexpected behavior if you try to decorate an instance or class method. This guide explains the correct approach for using methods with all FastMCP decorators (`@tool
|
| 9 |
|
| 10 |
## Why Are Methods Hard?
|
| 11 |
|
| 12 |
-
When you apply a FastMCP decorator like `@tool
|
| 13 |
|
| 14 |
1. For instance methods: The decorator gets the unbound method before any instance exists
|
| 15 |
2. For class methods: The decorator gets the function before it's bound to the class
|
|
@@ -28,7 +28,7 @@ from fastmcp import FastMCP
|
|
| 28 |
mcp = FastMCP()
|
| 29 |
|
| 30 |
class MyClass:
|
| 31 |
-
@mcp.tool
|
| 32 |
def add(self, x, y):
|
| 33 |
return x + y
|
| 34 |
|
|
@@ -83,7 +83,7 @@ mcp = FastMCP()
|
|
| 83 |
|
| 84 |
class MyClass:
|
| 85 |
@classmethod
|
| 86 |
-
@mcp.tool
|
| 87 |
def from_string(cls, s):
|
| 88 |
return cls(s)
|
| 89 |
```
|
|
@@ -122,7 +122,7 @@ mcp = FastMCP()
|
|
| 122 |
|
| 123 |
class MyClass:
|
| 124 |
@staticmethod
|
| 125 |
-
@mcp.tool
|
| 126 |
def utility(x, y):
|
| 127 |
return x + y
|
| 128 |
|
|
@@ -194,7 +194,7 @@ The class automatically registers its methods during initialization, ensuring th
|
|
| 194 |
While FastMCP's decorator pattern works seamlessly with regular functions and static methods, for instance methods and class methods, you should add them after creating the instance or class. This ensures that the methods are properly bound before being registered.
|
| 195 |
|
| 196 |
These patterns apply to all FastMCP decorators and registration methods:
|
| 197 |
-
- `@tool()` and `add_tool
|
| 198 |
- `@resource()` and `add_resource_fn()`
|
| 199 |
- `@prompt()` and `add_prompt()`
|
| 200 |
|
|
|
|
| 5 |
icon: at
|
| 6 |
---
|
| 7 |
|
| 8 |
+
FastMCP's decorator system is designed to work with functions, but you may see unexpected behavior if you try to decorate an instance or class method. This guide explains the correct approach for using methods with all FastMCP decorators (`@tool`, `@resource()`, and `@prompt()`).
|
| 9 |
|
| 10 |
## Why Are Methods Hard?
|
| 11 |
|
| 12 |
+
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:
|
| 13 |
|
| 14 |
1. For instance methods: The decorator gets the unbound method before any instance exists
|
| 15 |
2. For class methods: The decorator gets the function before it's bound to the class
|
|
|
|
| 28 |
mcp = FastMCP()
|
| 29 |
|
| 30 |
class MyClass:
|
| 31 |
+
@mcp.tool # This won't work correctly
|
| 32 |
def add(self, x, y):
|
| 33 |
return x + y
|
| 34 |
|
|
|
|
| 83 |
|
| 84 |
class MyClass:
|
| 85 |
@classmethod
|
| 86 |
+
@mcp.tool # This won't work correctly
|
| 87 |
def from_string(cls, s):
|
| 88 |
return cls(s)
|
| 89 |
```
|
|
|
|
| 122 |
|
| 123 |
class MyClass:
|
| 124 |
@staticmethod
|
| 125 |
+
@mcp.tool # This works!
|
| 126 |
def utility(x, y):
|
| 127 |
return x + y
|
| 128 |
|
|
|
|
| 194 |
While FastMCP's decorator pattern works seamlessly with regular functions and static methods, for instance methods and class methods, you should add them after creating the instance or class. This ensures that the methods are properly bound before being registered.
|
| 195 |
|
| 196 |
These patterns apply to all FastMCP decorators and registration methods:
|
| 197 |
+
- `@tool()` and `add_tool`
|
| 198 |
- `@resource()` and `add_resource_fn()`
|
| 199 |
- `@prompt()` and `add_prompt()`
|
| 200 |
|
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/patterns/testing.mdx
CHANGED
|
@@ -22,7 +22,7 @@ from fastmcp import FastMCP, Client
|
|
| 22 |
def mcp_server():
|
| 23 |
server = FastMCP("TestServer")
|
| 24 |
|
| 25 |
-
@server.tool
|
| 26 |
def greet(name: str) -> str:
|
| 27 |
return f"Hello, {name}!"
|
| 28 |
|
|
|
|
| 22 |
def mcp_server():
|
| 23 |
server = FastMCP("TestServer")
|
| 24 |
|
| 25 |
+
@server.tool
|
| 26 |
def greet(name: str) -> str:
|
| 27 |
return f"Hello, {name}!"
|
| 28 |
|
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/composition.mdx
CHANGED
|
@@ -50,7 +50,7 @@ import asyncio
|
|
| 50 |
# Define subservers
|
| 51 |
weather_mcp = FastMCP(name="WeatherService")
|
| 52 |
|
| 53 |
-
@weather_mcp.tool
|
| 54 |
def get_forecast(city: str) -> dict:
|
| 55 |
"""Get weather forecast."""
|
| 56 |
return {"city": city, "forecast": "Sunny"}
|
|
@@ -102,7 +102,7 @@ from fastmcp import FastMCP, Client
|
|
| 102 |
# Define subserver
|
| 103 |
dynamic_mcp = FastMCP(name="DynamicService")
|
| 104 |
|
| 105 |
-
@dynamic_mcp.tool
|
| 106 |
def initial_tool():
|
| 107 |
"""Initial tool demonstration."""
|
| 108 |
return "Initial Tool Exists"
|
|
@@ -112,7 +112,7 @@ main_mcp = FastMCP(name="MainAppLive")
|
|
| 112 |
main_mcp.mount("dynamic", dynamic_mcp)
|
| 113 |
|
| 114 |
# Add a tool AFTER mounting - it will be accessible through main_mcp
|
| 115 |
-
@dynamic_mcp.tool
|
| 116 |
def added_later():
|
| 117 |
"""Tool added after mounting."""
|
| 118 |
return "Tool Added Dynamically!"
|
|
|
|
| 50 |
# Define subservers
|
| 51 |
weather_mcp = FastMCP(name="WeatherService")
|
| 52 |
|
| 53 |
+
@weather_mcp.tool
|
| 54 |
def get_forecast(city: str) -> dict:
|
| 55 |
"""Get weather forecast."""
|
| 56 |
return {"city": city, "forecast": "Sunny"}
|
|
|
|
| 102 |
# Define subserver
|
| 103 |
dynamic_mcp = FastMCP(name="DynamicService")
|
| 104 |
|
| 105 |
+
@dynamic_mcp.tool
|
| 106 |
def initial_tool():
|
| 107 |
"""Initial tool demonstration."""
|
| 108 |
return "Initial Tool Exists"
|
|
|
|
| 112 |
main_mcp.mount("dynamic", dynamic_mcp)
|
| 113 |
|
| 114 |
# Add a tool AFTER mounting - it will be accessible through main_mcp
|
| 115 |
+
@dynamic_mcp.tool
|
| 116 |
def added_later():
|
| 117 |
"""Tool added after mounting."""
|
| 118 |
return "Tool Added Dynamically!"
|
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}!"
|
|
@@ -145,7 +145,7 @@ import asyncio
|
|
| 145 |
main = FastMCP(name="Main")
|
| 146 |
sub = FastMCP(name="Sub")
|
| 147 |
|
| 148 |
-
@sub.tool
|
| 149 |
def hello():
|
| 150 |
return "hi"
|
| 151 |
|
|
@@ -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}!"
|
|
|
|
| 145 |
main = FastMCP(name="Main")
|
| 146 |
sub = FastMCP(name="Sub")
|
| 147 |
|
| 148 |
+
@sub.tool
|
| 149 |
def hello():
|
| 150 |
return "hi"
|
| 151 |
|
|
|
|
| 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/proxy.mdx
CHANGED
|
@@ -90,7 +90,7 @@ from fastmcp import FastMCP
|
|
| 90 |
# Original server
|
| 91 |
original_server = FastMCP(name="Original")
|
| 92 |
|
| 93 |
-
@original_server.tool
|
| 94 |
def tool_a() -> str:
|
| 95 |
return "A"
|
| 96 |
|
|
|
|
| 90 |
# Original server
|
| 91 |
original_server = FastMCP(name="Original")
|
| 92 |
|
| 93 |
+
@original_server.tool
|
| 94 |
def tool_a() -> str:
|
| 95 |
return "A"
|
| 96 |
|
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/mount_example.py
CHANGED
|
@@ -16,7 +16,7 @@ from fastmcp import FastMCP
|
|
| 16 |
weather_app = FastMCP("Weather App")
|
| 17 |
|
| 18 |
|
| 19 |
-
@weather_app.tool
|
| 20 |
def get_weather_forecast(location: str) -> str:
|
| 21 |
"""Get the weather forecast for a location."""
|
| 22 |
return f"Sunny skies for {location} today!"
|
|
@@ -32,7 +32,7 @@ async def weather_data():
|
|
| 32 |
news_app = FastMCP("News App")
|
| 33 |
|
| 34 |
|
| 35 |
-
@news_app.tool
|
| 36 |
def get_news_headlines() -> list[str]:
|
| 37 |
"""Get the latest news headlines."""
|
| 38 |
return [
|
|
@@ -58,7 +58,7 @@ app = FastMCP(
|
|
| 58 |
)
|
| 59 |
|
| 60 |
|
| 61 |
-
@app.tool
|
| 62 |
def check_app_status() -> dict[str, str]:
|
| 63 |
"""Check the status of the main application."""
|
| 64 |
return {"status": "running", "version": "1.0.0", "uptime": "3h 24m"}
|
|
|
|
| 16 |
weather_app = FastMCP("Weather App")
|
| 17 |
|
| 18 |
|
| 19 |
+
@weather_app.tool
|
| 20 |
def get_weather_forecast(location: str) -> str:
|
| 21 |
"""Get the weather forecast for a location."""
|
| 22 |
return f"Sunny skies for {location} today!"
|
|
|
|
| 32 |
news_app = FastMCP("News App")
|
| 33 |
|
| 34 |
|
| 35 |
+
@news_app.tool
|
| 36 |
def get_news_headlines() -> list[str]:
|
| 37 |
"""Get the latest news headlines."""
|
| 38 |
return [
|
|
|
|
| 58 |
)
|
| 59 |
|
| 60 |
|
| 61 |
+
@app.tool
|
| 62 |
def check_app_status() -> dict[str, str]:
|
| 63 |
"""Check the status of the main application."""
|
| 64 |
return {"status": "running", "version": "1.0.0", "uptime": "3h 24m"}
|
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/serializer.py
CHANGED
|
@@ -14,7 +14,7 @@ def custom_dict_serializer(data: Any) -> str:
|
|
| 14 |
server = FastMCP(name="CustomSerializerExample", tool_serializer=custom_dict_serializer)
|
| 15 |
|
| 16 |
|
| 17 |
-
@server.tool
|
| 18 |
def get_example_data() -> dict:
|
| 19 |
"""Returns some example data."""
|
| 20 |
return {"name": "Test", "value": 123, "status": True}
|
|
|
|
| 14 |
server = FastMCP(name="CustomSerializerExample", tool_serializer=custom_dict_serializer)
|
| 15 |
|
| 16 |
|
| 17 |
+
@server.tool
|
| 18 |
def get_example_data() -> dict:
|
| 19 |
"""Returns some example data."""
|
| 20 |
return {"name": "Test", "value": 123, "status": True}
|
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
|
examples/smart_home/src/smart_home/hub.py
CHANGED
|
@@ -16,7 +16,7 @@ hub_mcp.mount("hue", lights_mcp)
|
|
| 16 |
|
| 17 |
|
| 18 |
# Add a status check for the hub
|
| 19 |
-
@hub_mcp.tool
|
| 20 |
def hub_status() -> str:
|
| 21 |
"""Checks the status of the main hub and connections."""
|
| 22 |
try:
|
|
|
|
| 16 |
|
| 17 |
|
| 18 |
# Add a status check for the hub
|
| 19 |
+
@hub_mcp.tool
|
| 20 |
def hub_status() -> str:
|
| 21 |
"""Checks the status of the main hub and connections."""
|
| 22 |
try:
|
examples/smart_home/src/smart_home/lights/server.py
CHANGED
|
@@ -43,7 +43,7 @@ lights_mcp = FastMCP(
|
|
| 43 |
)
|
| 44 |
|
| 45 |
|
| 46 |
-
@lights_mcp.tool
|
| 47 |
def read_all_lights() -> list[str]:
|
| 48 |
"""Lists the names of all available Hue lights using phue2."""
|
| 49 |
if not (bridge := _get_bridge()):
|
|
@@ -59,7 +59,7 @@ def read_all_lights() -> list[str]:
|
|
| 59 |
# --- Tools ---
|
| 60 |
|
| 61 |
|
| 62 |
-
@lights_mcp.tool
|
| 63 |
def toggle_light(light_name: str, state: bool) -> dict[str, Any]:
|
| 64 |
"""Turns a specific light on (true) or off (false) using phue2."""
|
| 65 |
if not (bridge := _get_bridge()):
|
|
@@ -76,7 +76,7 @@ def toggle_light(light_name: str, state: bool) -> dict[str, Any]:
|
|
| 76 |
return handle_phue_error(light_name, "toggle_light", e)
|
| 77 |
|
| 78 |
|
| 79 |
-
@lights_mcp.tool
|
| 80 |
def set_brightness(light_name: str, brightness: int) -> dict[str, Any]:
|
| 81 |
"""Sets the brightness of a specific light (0-254) using phue2."""
|
| 82 |
if not (bridge := _get_bridge()):
|
|
@@ -100,7 +100,7 @@ def set_brightness(light_name: str, brightness: int) -> dict[str, Any]:
|
|
| 100 |
return handle_phue_error(light_name, "set_brightness", e)
|
| 101 |
|
| 102 |
|
| 103 |
-
@lights_mcp.tool
|
| 104 |
def list_groups() -> list[str]:
|
| 105 |
"""Lists the names of all available Hue light groups."""
|
| 106 |
if not (bridge := _get_bridge()):
|
|
@@ -113,7 +113,7 @@ def list_groups() -> list[str]:
|
|
| 113 |
return [f"Error listing groups: {e}"]
|
| 114 |
|
| 115 |
|
| 116 |
-
@lights_mcp.tool
|
| 117 |
def list_scenes() -> dict[str, list[str]] | list[str]:
|
| 118 |
"""Lists Hue scenes, grouped by the light group they belong to.
|
| 119 |
|
|
@@ -154,7 +154,7 @@ def list_scenes() -> dict[str, list[str]] | list[str]:
|
|
| 154 |
return [f"Error listing scenes by group: {e}"]
|
| 155 |
|
| 156 |
|
| 157 |
-
@lights_mcp.tool
|
| 158 |
def activate_scene(group_name: str, scene_name: str) -> dict[str, Any]:
|
| 159 |
"""Activates a specific scene within a specified light group, verifying the scene belongs to the group."""
|
| 160 |
if not (bridge := _get_bridge()):
|
|
@@ -215,7 +215,7 @@ def activate_scene(group_name: str, scene_name: str) -> dict[str, Any]:
|
|
| 215 |
return handle_phue_error(f"{group_name}/{scene_name}", "activate_scene", e)
|
| 216 |
|
| 217 |
|
| 218 |
-
@lights_mcp.tool
|
| 219 |
def set_light_attributes(light_name: str, attributes: HueAttributes) -> dict[str, Any]:
|
| 220 |
"""Sets multiple attributes (e.g., hue, sat, bri, ct, xy, transitiontime) for a specific light."""
|
| 221 |
if not (bridge := _get_bridge()):
|
|
@@ -242,7 +242,7 @@ def set_light_attributes(light_name: str, attributes: HueAttributes) -> dict[str
|
|
| 242 |
return handle_phue_error(light_name, "set_light_attributes", e)
|
| 243 |
|
| 244 |
|
| 245 |
-
@lights_mcp.tool
|
| 246 |
def set_group_attributes(group_name: str, attributes: HueAttributes) -> dict[str, Any]:
|
| 247 |
"""Sets multiple attributes for all lights within a specific group."""
|
| 248 |
if not (bridge := _get_bridge()):
|
|
@@ -267,7 +267,7 @@ def set_group_attributes(group_name: str, attributes: HueAttributes) -> dict[str
|
|
| 267 |
return handle_phue_error(group_name, "set_group_attributes", e)
|
| 268 |
|
| 269 |
|
| 270 |
-
@lights_mcp.tool
|
| 271 |
def list_lights_by_group() -> dict[str, list[str]] | list[str]:
|
| 272 |
"""Lists Hue lights, grouped by the room/group they belong to.
|
| 273 |
|
|
|
|
| 43 |
)
|
| 44 |
|
| 45 |
|
| 46 |
+
@lights_mcp.tool
|
| 47 |
def read_all_lights() -> list[str]:
|
| 48 |
"""Lists the names of all available Hue lights using phue2."""
|
| 49 |
if not (bridge := _get_bridge()):
|
|
|
|
| 59 |
# --- Tools ---
|
| 60 |
|
| 61 |
|
| 62 |
+
@lights_mcp.tool
|
| 63 |
def toggle_light(light_name: str, state: bool) -> dict[str, Any]:
|
| 64 |
"""Turns a specific light on (true) or off (false) using phue2."""
|
| 65 |
if not (bridge := _get_bridge()):
|
|
|
|
| 76 |
return handle_phue_error(light_name, "toggle_light", e)
|
| 77 |
|
| 78 |
|
| 79 |
+
@lights_mcp.tool
|
| 80 |
def set_brightness(light_name: str, brightness: int) -> dict[str, Any]:
|
| 81 |
"""Sets the brightness of a specific light (0-254) using phue2."""
|
| 82 |
if not (bridge := _get_bridge()):
|
|
|
|
| 100 |
return handle_phue_error(light_name, "set_brightness", e)
|
| 101 |
|
| 102 |
|
| 103 |
+
@lights_mcp.tool
|
| 104 |
def list_groups() -> list[str]:
|
| 105 |
"""Lists the names of all available Hue light groups."""
|
| 106 |
if not (bridge := _get_bridge()):
|
|
|
|
| 113 |
return [f"Error listing groups: {e}"]
|
| 114 |
|
| 115 |
|
| 116 |
+
@lights_mcp.tool
|
| 117 |
def list_scenes() -> dict[str, list[str]] | list[str]:
|
| 118 |
"""Lists Hue scenes, grouped by the light group they belong to.
|
| 119 |
|
|
|
|
| 154 |
return [f"Error listing scenes by group: {e}"]
|
| 155 |
|
| 156 |
|
| 157 |
+
@lights_mcp.tool
|
| 158 |
def activate_scene(group_name: str, scene_name: str) -> dict[str, Any]:
|
| 159 |
"""Activates a specific scene within a specified light group, verifying the scene belongs to the group."""
|
| 160 |
if not (bridge := _get_bridge()):
|
|
|
|
| 215 |
return handle_phue_error(f"{group_name}/{scene_name}", "activate_scene", e)
|
| 216 |
|
| 217 |
|
| 218 |
+
@lights_mcp.tool
|
| 219 |
def set_light_attributes(light_name: str, attributes: HueAttributes) -> dict[str, Any]:
|
| 220 |
"""Sets multiple attributes (e.g., hue, sat, bri, ct, xy, transitiontime) for a specific light."""
|
| 221 |
if not (bridge := _get_bridge()):
|
|
|
|
| 242 |
return handle_phue_error(light_name, "set_light_attributes", e)
|
| 243 |
|
| 244 |
|
| 245 |
+
@lights_mcp.tool
|
| 246 |
def set_group_attributes(group_name: str, attributes: HueAttributes) -> dict[str, Any]:
|
| 247 |
"""Sets multiple attributes for all lights within a specific group."""
|
| 248 |
if not (bridge := _get_bridge()):
|
|
|
|
| 267 |
return handle_phue_error(group_name, "set_group_attributes", e)
|
| 268 |
|
| 269 |
|
| 270 |
+
@lights_mcp.tool
|
| 271 |
def list_lights_by_group() -> dict[str, list[str]] | list[str]:
|
| 272 |
"""Lists Hue lights, grouped by the room/group they belong to.
|
| 273 |
|
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
|
src/fastmcp/server/context.py
CHANGED
|
@@ -49,7 +49,7 @@ class Context:
|
|
| 49 |
To use context in a tool function, add a parameter with the Context type annotation:
|
| 50 |
|
| 51 |
```python
|
| 52 |
-
@server.tool
|
| 53 |
def my_tool(x: int, ctx: Context) -> str:
|
| 54 |
# Log messages to the client
|
| 55 |
ctx.info(f"Processing {x}")
|
|
|
|
| 49 |
To use context in a tool function, add a parameter with the Context type annotation:
|
| 50 |
|
| 51 |
```python
|
| 52 |
+
@server.tool
|
| 53 |
def my_tool(x: int, ctx: Context) -> str:
|
| 54 |
# Log messages to the client
|
| 55 |
ctx.info(f"Processing {x}")
|
src/fastmcp/server/server.py
CHANGED
|
@@ -513,53 +513,69 @@ class FastMCP(Generic[LifespanResultT]):
|
|
| 513 |
|
| 514 |
def tool(
|
| 515 |
self,
|
|
|
|
|
|
|
| 516 |
name: str | None = None,
|
| 517 |
description: str | None = None,
|
| 518 |
tags: set[str] | None = None,
|
| 519 |
annotations: ToolAnnotations | dict[str, Any] | None = None,
|
| 520 |
exclude_args: list[str] | None = None,
|
| 521 |
-
) -> Callable[[AnyFunction], AnyFunction]:
|
| 522 |
"""Decorator to register a tool.
|
| 523 |
|
| 524 |
Tools can optionally request a Context object by adding a parameter with the
|
| 525 |
Context type annotation. The context provides access to MCP capabilities like
|
| 526 |
logging, progress reporting, and resource access.
|
| 527 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 528 |
Args:
|
| 529 |
-
|
| 530 |
description: Optional description of what the tool does
|
| 531 |
tags: Optional set of tags for categorizing the tool
|
| 532 |
annotations: Optional annotations about the tool's behavior
|
|
|
|
|
|
|
| 533 |
|
| 534 |
Example:
|
| 535 |
-
@server.tool
|
| 536 |
def my_tool(x: int) -> str:
|
| 537 |
return str(x)
|
| 538 |
|
| 539 |
-
@server.tool
|
| 540 |
-
def
|
| 541 |
-
ctx.info(f"Processing {x}")
|
| 542 |
return str(x)
|
| 543 |
|
| 544 |
-
@server.tool()
|
| 545 |
-
|
| 546 |
-
await context.report_progress(50, 100)
|
| 547 |
return str(x)
|
| 548 |
-
"""
|
| 549 |
|
| 550 |
-
|
| 551 |
-
|
| 552 |
-
|
| 553 |
-
|
| 554 |
-
|
| 555 |
-
)
|
|
|
|
| 556 |
if isinstance(annotations, dict):
|
| 557 |
annotations = ToolAnnotations(**annotations)
|
| 558 |
|
| 559 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 560 |
tool = Tool.from_function(
|
| 561 |
fn,
|
| 562 |
-
name=
|
| 563 |
description=description,
|
| 564 |
tags=tags,
|
| 565 |
annotations=annotations,
|
|
@@ -569,7 +585,31 @@ class FastMCP(Generic[LifespanResultT]):
|
|
| 569 |
self.add_tool(tool)
|
| 570 |
return fn
|
| 571 |
|
| 572 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 573 |
|
| 574 |
def add_resource(self, resource: Resource, key: str | None = None) -> None:
|
| 575 |
"""Add a resource to the server.
|
|
|
|
| 513 |
|
| 514 |
def tool(
|
| 515 |
self,
|
| 516 |
+
name_or_fn: str | AnyFunction | None = None,
|
| 517 |
+
*,
|
| 518 |
name: str | None = None,
|
| 519 |
description: str | None = None,
|
| 520 |
tags: set[str] | None = None,
|
| 521 |
annotations: ToolAnnotations | dict[str, Any] | None = None,
|
| 522 |
exclude_args: list[str] | None = None,
|
| 523 |
+
) -> Callable[[AnyFunction], AnyFunction] | AnyFunction:
|
| 524 |
"""Decorator to register a tool.
|
| 525 |
|
| 526 |
Tools can optionally request a Context object by adding a parameter with the
|
| 527 |
Context type annotation. The context provides access to MCP capabilities like
|
| 528 |
logging, progress reporting, and resource access.
|
| 529 |
|
| 530 |
+
This decorator supports multiple calling patterns:
|
| 531 |
+
- @server.tool (without parentheses)
|
| 532 |
+
- @server.tool (with empty parentheses)
|
| 533 |
+
- @server.tool("custom_name") (with name as first argument)
|
| 534 |
+
- @server.tool(name="custom_name") (with name as keyword argument)
|
| 535 |
+
- server.tool(function, name="custom_name") (direct function call)
|
| 536 |
+
|
| 537 |
Args:
|
| 538 |
+
name_or_fn: Either a function (when used as @tool), a string name, or None
|
| 539 |
description: Optional description of what the tool does
|
| 540 |
tags: Optional set of tags for categorizing the tool
|
| 541 |
annotations: Optional annotations about the tool's behavior
|
| 542 |
+
exclude_args: Optional list of argument names to exclude from the tool schema
|
| 543 |
+
name: Optional name for the tool (keyword-only, alternative to name_or_fn)
|
| 544 |
|
| 545 |
Example:
|
| 546 |
+
@server.tool
|
| 547 |
def my_tool(x: int) -> str:
|
| 548 |
return str(x)
|
| 549 |
|
| 550 |
+
@server.tool
|
| 551 |
+
def my_tool(x: int) -> str:
|
|
|
|
| 552 |
return str(x)
|
| 553 |
|
| 554 |
+
@server.tool("custom_name")
|
| 555 |
+
def my_tool(x: int) -> str:
|
|
|
|
| 556 |
return str(x)
|
|
|
|
| 557 |
|
| 558 |
+
@server.tool(name="custom_name")
|
| 559 |
+
def my_tool(x: int) -> str:
|
| 560 |
+
return str(x)
|
| 561 |
+
|
| 562 |
+
# Direct function call
|
| 563 |
+
server.tool(my_function, name="custom_name")
|
| 564 |
+
"""
|
| 565 |
if isinstance(annotations, dict):
|
| 566 |
annotations = ToolAnnotations(**annotations)
|
| 567 |
|
| 568 |
+
# Determine the actual name and function based on the calling pattern
|
| 569 |
+
if callable(name_or_fn):
|
| 570 |
+
# Case 1: @tool (without parens) - function passed directly
|
| 571 |
+
# Case 2: direct call like tool(fn, name="something")
|
| 572 |
+
fn = name_or_fn
|
| 573 |
+
tool_name = name # Use keyword name if provided, otherwise None
|
| 574 |
+
|
| 575 |
+
# Register the tool immediately and return the function
|
| 576 |
tool = Tool.from_function(
|
| 577 |
fn,
|
| 578 |
+
name=tool_name,
|
| 579 |
description=description,
|
| 580 |
tags=tags,
|
| 581 |
annotations=annotations,
|
|
|
|
| 585 |
self.add_tool(tool)
|
| 586 |
return fn
|
| 587 |
|
| 588 |
+
elif isinstance(name_or_fn, str):
|
| 589 |
+
# Case 3: @tool("custom_name") - name passed as first argument
|
| 590 |
+
if name is not None:
|
| 591 |
+
raise TypeError(
|
| 592 |
+
"Cannot specify both a name as first argument and as keyword argument. "
|
| 593 |
+
f"Use either @tool('{name_or_fn}') or @tool(name='{name}'), not both."
|
| 594 |
+
)
|
| 595 |
+
tool_name = name_or_fn
|
| 596 |
+
elif name_or_fn is None:
|
| 597 |
+
# Case 4: @tool or @tool(name="something") - use keyword name
|
| 598 |
+
tool_name = name
|
| 599 |
+
else:
|
| 600 |
+
raise TypeError(
|
| 601 |
+
f"First argument to @tool must be a function, string, or None, got {type(name_or_fn)}"
|
| 602 |
+
)
|
| 603 |
+
|
| 604 |
+
# Return partial for cases where we need to wait for the function
|
| 605 |
+
return partial(
|
| 606 |
+
self.tool,
|
| 607 |
+
name=tool_name,
|
| 608 |
+
description=description,
|
| 609 |
+
tags=tags,
|
| 610 |
+
annotations=annotations,
|
| 611 |
+
exclude_args=exclude_args,
|
| 612 |
+
)
|
| 613 |
|
| 614 |
def add_resource(self, resource: Resource, key: str | None = None) -> None:
|
| 615 |
"""Add a resource to the server.
|
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/auth/test_oauth_client.py
CHANGED
|
@@ -24,7 +24,7 @@ def fastmcp_server(issuer_url: str):
|
|
| 24 |
),
|
| 25 |
)
|
| 26 |
|
| 27 |
-
@server.tool
|
| 28 |
def add(a: int, b: int) -> int:
|
| 29 |
"""Add two numbers together."""
|
| 30 |
return a + b
|
|
|
|
| 24 |
),
|
| 25 |
)
|
| 26 |
|
| 27 |
+
@server.tool
|
| 28 |
def add(a: int, b: int) -> int:
|
| 29 |
"""Add two numbers together."""
|
| 30 |
return a + b
|
tests/client/test_client.py
CHANGED
|
@@ -25,18 +25,18 @@ def fastmcp_server():
|
|
| 25 |
server = FastMCP("TestServer")
|
| 26 |
|
| 27 |
# Add a tool
|
| 28 |
-
@server.tool
|
| 29 |
def greet(name: str) -> str:
|
| 30 |
"""Greet someone by name."""
|
| 31 |
return f"Hello, {name}!"
|
| 32 |
|
| 33 |
# Add a second tool
|
| 34 |
-
@server.tool
|
| 35 |
def add(a: int, b: int) -> int:
|
| 36 |
"""Add two numbers together."""
|
| 37 |
return a + b
|
| 38 |
|
| 39 |
-
@server.tool
|
| 40 |
async def sleep(seconds: float) -> str:
|
| 41 |
"""Sleep for a given number of seconds."""
|
| 42 |
await asyncio.sleep(seconds)
|
|
@@ -347,7 +347,7 @@ async def test_concurrent_client_context_managers():
|
|
| 347 |
# Create a simple server
|
| 348 |
server = FastMCP("Test Server")
|
| 349 |
|
| 350 |
-
@server.tool
|
| 351 |
def echo(text: str) -> str:
|
| 352 |
"""Echo tool"""
|
| 353 |
return text
|
|
@@ -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 |
|
|
|
|
| 25 |
server = FastMCP("TestServer")
|
| 26 |
|
| 27 |
# Add a tool
|
| 28 |
+
@server.tool
|
| 29 |
def greet(name: str) -> str:
|
| 30 |
"""Greet someone by name."""
|
| 31 |
return f"Hello, {name}!"
|
| 32 |
|
| 33 |
# Add a second tool
|
| 34 |
+
@server.tool
|
| 35 |
def add(a: int, b: int) -> int:
|
| 36 |
"""Add two numbers together."""
|
| 37 |
return a + b
|
| 38 |
|
| 39 |
+
@server.tool
|
| 40 |
async def sleep(seconds: float) -> str:
|
| 41 |
"""Sleep for a given number of seconds."""
|
| 42 |
await asyncio.sleep(seconds)
|
|
|
|
| 347 |
# Create a simple server
|
| 348 |
server = FastMCP("Test Server")
|
| 349 |
|
| 350 |
+
@server.tool
|
| 351 |
def echo(text: str) -> str:
|
| 352 |
"""Echo tool"""
|
| 353 |
return text
|
|
|
|
| 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_sse.py
CHANGED
|
@@ -21,18 +21,18 @@ def fastmcp_server():
|
|
| 21 |
server = FastMCP("TestServer")
|
| 22 |
|
| 23 |
# Add a tool
|
| 24 |
-
@server.tool
|
| 25 |
def greet(name: str) -> str:
|
| 26 |
"""Greet someone by name."""
|
| 27 |
return f"Hello, {name}!"
|
| 28 |
|
| 29 |
# Add a second tool
|
| 30 |
-
@server.tool
|
| 31 |
def add(a: int, b: int) -> int:
|
| 32 |
"""Add two numbers together."""
|
| 33 |
return a + b
|
| 34 |
|
| 35 |
-
@server.tool
|
| 36 |
async def sleep(seconds: float) -> str:
|
| 37 |
"""Sleep for a given number of seconds."""
|
| 38 |
await asyncio.sleep(seconds)
|
|
|
|
| 21 |
server = FastMCP("TestServer")
|
| 22 |
|
| 23 |
# Add a tool
|
| 24 |
+
@server.tool
|
| 25 |
def greet(name: str) -> str:
|
| 26 |
"""Greet someone by name."""
|
| 27 |
return f"Hello, {name}!"
|
| 28 |
|
| 29 |
# Add a second tool
|
| 30 |
+
@server.tool
|
| 31 |
def add(a: int, b: int) -> int:
|
| 32 |
"""Add two numbers together."""
|
| 33 |
return a + b
|
| 34 |
|
| 35 |
+
@server.tool
|
| 36 |
async def sleep(seconds: float) -> str:
|
| 37 |
"""Sleep for a given number of seconds."""
|
| 38 |
await asyncio.sleep(seconds)
|
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/client/test_streamable_http.py
CHANGED
|
@@ -21,18 +21,18 @@ def fastmcp_server():
|
|
| 21 |
server = FastMCP("TestServer")
|
| 22 |
|
| 23 |
# Add a tool
|
| 24 |
-
@server.tool
|
| 25 |
def greet(name: str) -> str:
|
| 26 |
"""Greet someone by name."""
|
| 27 |
return f"Hello, {name}!"
|
| 28 |
|
| 29 |
# Add a second tool
|
| 30 |
-
@server.tool
|
| 31 |
def add(a: int, b: int) -> int:
|
| 32 |
"""Add two numbers together."""
|
| 33 |
return a + b
|
| 34 |
|
| 35 |
-
@server.tool
|
| 36 |
async def sleep(seconds: float) -> str:
|
| 37 |
"""Sleep for a given number of seconds."""
|
| 38 |
await asyncio.sleep(seconds)
|
|
|
|
| 21 |
server = FastMCP("TestServer")
|
| 22 |
|
| 23 |
# Add a tool
|
| 24 |
+
@server.tool
|
| 25 |
def greet(name: str) -> str:
|
| 26 |
"""Greet someone by name."""
|
| 27 |
return f"Hello, {name}!"
|
| 28 |
|
| 29 |
# Add a second tool
|
| 30 |
+
@server.tool
|
| 31 |
def add(a: int, b: int) -> int:
|
| 32 |
"""Add two numbers together."""
|
| 33 |
return a + b
|
| 34 |
|
| 35 |
+
@server.tool
|
| 36 |
async def sleep(seconds: float) -> str:
|
| 37 |
"""Sleep for a given number of seconds."""
|
| 38 |
await asyncio.sleep(seconds)
|
tests/deprecated/test_deprecated.py
CHANGED
|
@@ -100,7 +100,7 @@ def test_mount_tool_separator_deprecation_warning():
|
|
| 100 |
main_app.mount("sub", sub_app, tool_separator="-")
|
| 101 |
|
| 102 |
# Verify the separator is ignored and the default is used
|
| 103 |
-
@sub_app.tool
|
| 104 |
def test_tool():
|
| 105 |
return "test"
|
| 106 |
|
|
|
|
| 100 |
main_app.mount("sub", sub_app, tool_separator="-")
|
| 101 |
|
| 102 |
# Verify the separator is ignored and the default is used
|
| 103 |
+
@sub_app.tool
|
| 104 |
def test_tool():
|
| 105 |
return "test"
|
| 106 |
|
tests/deprecated/test_mount_separators.py
CHANGED
|
@@ -20,7 +20,7 @@ def test_mount_tool_separator_deprecation_warning():
|
|
| 20 |
main_app.mount("sub", sub_app, tool_separator="-")
|
| 21 |
|
| 22 |
# Verify the separator is ignored and the default is used
|
| 23 |
-
@sub_app.tool
|
| 24 |
def test_tool():
|
| 25 |
return "test"
|
| 26 |
|
|
|
|
| 20 |
main_app.mount("sub", sub_app, tool_separator="-")
|
| 21 |
|
| 22 |
# Verify the separator is ignored and the default is used
|
| 23 |
+
@sub_app.tool
|
| 24 |
def test_tool():
|
| 25 |
return "test"
|
| 26 |
|
tests/server/http/test_http_dependencies.py
CHANGED
|
@@ -14,7 +14,7 @@ def fastmcp_server():
|
|
| 14 |
server = FastMCP()
|
| 15 |
|
| 16 |
# Add a tool
|
| 17 |
-
@server.tool
|
| 18 |
def get_headers_tool() -> dict[str, str]:
|
| 19 |
"""Get the HTTP headers from the request."""
|
| 20 |
request = get_http_request()
|
|
|
|
| 14 |
server = FastMCP()
|
| 15 |
|
| 16 |
# Add a tool
|
| 17 |
+
@server.tool
|
| 18 |
def get_headers_tool() -> dict[str, str]:
|
| 19 |
"""Get the HTTP headers from the request."""
|
| 20 |
request = get_http_request()
|
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_import_server.py
CHANGED
|
@@ -13,7 +13,7 @@ async def test_import_basic_functionality():
|
|
| 13 |
sub_app = FastMCP("SubApp")
|
| 14 |
|
| 15 |
# Add a tool to the sub-app
|
| 16 |
-
@sub_app.tool
|
| 17 |
def sub_tool() -> str:
|
| 18 |
return "This is from the sub app"
|
| 19 |
|
|
@@ -40,11 +40,11 @@ async def test_import_multiple_apps():
|
|
| 40 |
news_app = FastMCP("NewsApp")
|
| 41 |
|
| 42 |
# Add tools to each sub-app
|
| 43 |
-
@weather_app.tool
|
| 44 |
def get_forecast() -> str:
|
| 45 |
return "Weather forecast"
|
| 46 |
|
| 47 |
-
@news_app.tool
|
| 48 |
def get_headlines() -> str:
|
| 49 |
return "News headlines"
|
| 50 |
|
|
@@ -65,11 +65,11 @@ async def test_import_combines_tools():
|
|
| 65 |
second_app = FastMCP("SecondApp")
|
| 66 |
|
| 67 |
# Add tools to each sub-app
|
| 68 |
-
@first_app.tool
|
| 69 |
def first_tool() -> str:
|
| 70 |
return "First app tool"
|
| 71 |
|
| 72 |
-
@second_app.tool
|
| 73 |
def second_tool() -> str:
|
| 74 |
return "Second app tool"
|
| 75 |
|
|
@@ -294,7 +294,7 @@ async def test_import_with_proxy_tools():
|
|
| 294 |
main_app = FastMCP("MainApp")
|
| 295 |
api_app = FastMCP("APIApp")
|
| 296 |
|
| 297 |
-
@api_app.tool
|
| 298 |
def get_data(query: str) -> str:
|
| 299 |
return f"Data for query: {query}"
|
| 300 |
|
|
|
|
| 13 |
sub_app = FastMCP("SubApp")
|
| 14 |
|
| 15 |
# Add a tool to the sub-app
|
| 16 |
+
@sub_app.tool
|
| 17 |
def sub_tool() -> str:
|
| 18 |
return "This is from the sub app"
|
| 19 |
|
|
|
|
| 40 |
news_app = FastMCP("NewsApp")
|
| 41 |
|
| 42 |
# Add tools to each sub-app
|
| 43 |
+
@weather_app.tool
|
| 44 |
def get_forecast() -> str:
|
| 45 |
return "Weather forecast"
|
| 46 |
|
| 47 |
+
@news_app.tool
|
| 48 |
def get_headlines() -> str:
|
| 49 |
return "News headlines"
|
| 50 |
|
|
|
|
| 65 |
second_app = FastMCP("SecondApp")
|
| 66 |
|
| 67 |
# Add tools to each sub-app
|
| 68 |
+
@first_app.tool
|
| 69 |
def first_tool() -> str:
|
| 70 |
return "First app tool"
|
| 71 |
|
| 72 |
+
@second_app.tool
|
| 73 |
def second_tool() -> str:
|
| 74 |
return "Second app tool"
|
| 75 |
|
|
|
|
| 294 |
main_app = FastMCP("MainApp")
|
| 295 |
api_app = FastMCP("APIApp")
|
| 296 |
|
| 297 |
+
@api_app.tool
|
| 298 |
def get_data(query: str) -> str:
|
| 299 |
return f"Data for query: {query}"
|
| 300 |
|