fastmcp 2.2.2__tar.gz → 2.2.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {fastmcp-2.2.2 → fastmcp-2.2.3}/PKG-INFO +19 -19
- {fastmcp-2.2.2 → fastmcp-2.2.3}/README.md +18 -18
- {fastmcp-2.2.2 → fastmcp-2.2.3}/docs/clients/transports.mdx +4 -0
- {fastmcp-2.2.2 → fastmcp-2.2.3}/docs/patterns/contrib.mdx +3 -0
- {fastmcp-2.2.2 → fastmcp-2.2.3}/docs/patterns/proxy.mdx +11 -9
- {fastmcp-2.2.2 → fastmcp-2.2.3}/docs/servers/fastmcp.mdx +6 -0
- {fastmcp-2.2.2 → fastmcp-2.2.3}/docs/servers/prompts.mdx +4 -0
- {fastmcp-2.2.2 → fastmcp-2.2.3}/docs/servers/resources.mdx +65 -3
- {fastmcp-2.2.2 → fastmcp-2.2.3}/docs/servers/tools.mdx +10 -3
- fastmcp-2.2.3/docs/snippets/version-badge.mdx +13 -0
- {fastmcp-2.2.2 → fastmcp-2.2.3}/docs/style.css +15 -18
- {fastmcp-2.2.2 → fastmcp-2.2.3}/src/fastmcp/resources/template.py +5 -2
- {fastmcp-2.2.2 → fastmcp-2.2.3}/src/fastmcp/tools/tool.py +6 -1
- {fastmcp-2.2.2 → fastmcp-2.2.3}/tests/resources/test_resource_template.py +69 -2
- {fastmcp-2.2.2 → fastmcp-2.2.3}/tests/tools/test_tool_manager.py +14 -20
- fastmcp-2.2.2/docs/snippets/version-badge.mdx +0 -8
- {fastmcp-2.2.2 → fastmcp-2.2.3}/.cursor/rules/core-mcp-objects.mdc +0 -0
- {fastmcp-2.2.2 → fastmcp-2.2.3}/.github/ISSUE_TEMPLATE/bug.yml +0 -0
- {fastmcp-2.2.2 → fastmcp-2.2.3}/.github/ISSUE_TEMPLATE/config.yml +0 -0
- {fastmcp-2.2.2 → fastmcp-2.2.3}/.github/ISSUE_TEMPLATE/enhancement.yml +0 -0
- {fastmcp-2.2.2 → fastmcp-2.2.3}/.github/release.yml +0 -0
- {fastmcp-2.2.2 → fastmcp-2.2.3}/.github/workflows/publish.yml +0 -0
- {fastmcp-2.2.2 → fastmcp-2.2.3}/.github/workflows/run-static.yml +0 -0
- {fastmcp-2.2.2 → fastmcp-2.2.3}/.github/workflows/run-tests.yml +0 -0
- {fastmcp-2.2.2 → fastmcp-2.2.3}/.gitignore +0 -0
- {fastmcp-2.2.2 → fastmcp-2.2.3}/.pre-commit-config.yaml +0 -0
- {fastmcp-2.2.2 → fastmcp-2.2.3}/LICENSE +0 -0
- {fastmcp-2.2.2 → fastmcp-2.2.3}/Windows_Notes.md +0 -0
- {fastmcp-2.2.2 → fastmcp-2.2.3}/docs/assets/demo-inspector.png +0 -0
- {fastmcp-2.2.2 → fastmcp-2.2.3}/docs/clients/client.mdx +0 -0
- {fastmcp-2.2.2 → fastmcp-2.2.3}/docs/docs.json +0 -0
- {fastmcp-2.2.2 → fastmcp-2.2.3}/docs/getting-started/installation.mdx +0 -0
- {fastmcp-2.2.2 → fastmcp-2.2.3}/docs/getting-started/quickstart.mdx +0 -0
- {fastmcp-2.2.2 → fastmcp-2.2.3}/docs/getting-started/welcome.mdx +0 -0
- {fastmcp-2.2.2 → fastmcp-2.2.3}/docs/patterns/composition.mdx +0 -0
- {fastmcp-2.2.2 → fastmcp-2.2.3}/docs/patterns/decorating-methods.mdx +0 -0
- {fastmcp-2.2.2 → fastmcp-2.2.3}/docs/patterns/fastapi.mdx +0 -0
- {fastmcp-2.2.2 → fastmcp-2.2.3}/docs/patterns/openapi.mdx +0 -0
- {fastmcp-2.2.2 → fastmcp-2.2.3}/docs/servers/context.mdx +0 -0
- {fastmcp-2.2.2 → fastmcp-2.2.3}/examples/complex_inputs.py +0 -0
- {fastmcp-2.2.2 → fastmcp-2.2.3}/examples/desktop.py +0 -0
- {fastmcp-2.2.2 → fastmcp-2.2.3}/examples/echo.py +0 -0
- {fastmcp-2.2.2 → fastmcp-2.2.3}/examples/memory.py +0 -0
- {fastmcp-2.2.2 → fastmcp-2.2.3}/examples/mount_example.py +0 -0
- {fastmcp-2.2.2 → fastmcp-2.2.3}/examples/readme-quickstart.py +0 -0
- {fastmcp-2.2.2 → fastmcp-2.2.3}/examples/sampling.py +0 -0
- {fastmcp-2.2.2 → fastmcp-2.2.3}/examples/screenshot.py +0 -0
- {fastmcp-2.2.2 → fastmcp-2.2.3}/examples/simple_echo.py +0 -0
- {fastmcp-2.2.2 → fastmcp-2.2.3}/examples/smart_home/README.md +0 -0
- {fastmcp-2.2.2 → fastmcp-2.2.3}/examples/smart_home/pyproject.toml +0 -0
- {fastmcp-2.2.2 → fastmcp-2.2.3}/examples/smart_home/src/smart_home/__init__.py +0 -0
- {fastmcp-2.2.2 → fastmcp-2.2.3}/examples/smart_home/src/smart_home/__main__.py +0 -0
- {fastmcp-2.2.2 → fastmcp-2.2.3}/examples/smart_home/src/smart_home/hub.py +0 -0
- {fastmcp-2.2.2 → fastmcp-2.2.3}/examples/smart_home/src/smart_home/lights/__init__.py +0 -0
- {fastmcp-2.2.2 → fastmcp-2.2.3}/examples/smart_home/src/smart_home/lights/hue_utils.py +0 -0
- {fastmcp-2.2.2 → fastmcp-2.2.3}/examples/smart_home/src/smart_home/lights/server.py +0 -0
- {fastmcp-2.2.2 → fastmcp-2.2.3}/examples/smart_home/src/smart_home/py.typed +0 -0
- {fastmcp-2.2.2 → fastmcp-2.2.3}/examples/smart_home/src/smart_home/settings.py +0 -0
- {fastmcp-2.2.2 → fastmcp-2.2.3}/examples/smart_home/uv.lock +0 -0
- {fastmcp-2.2.2 → fastmcp-2.2.3}/examples/text_me.py +0 -0
- {fastmcp-2.2.2 → fastmcp-2.2.3}/justfile +0 -0
- {fastmcp-2.2.2 → fastmcp-2.2.3}/pyproject.toml +0 -0
- {fastmcp-2.2.2 → fastmcp-2.2.3}/src/fastmcp/__init__.py +0 -0
- {fastmcp-2.2.2 → fastmcp-2.2.3}/src/fastmcp/cli/__init__.py +0 -0
- {fastmcp-2.2.2 → fastmcp-2.2.3}/src/fastmcp/cli/claude.py +0 -0
- {fastmcp-2.2.2 → fastmcp-2.2.3}/src/fastmcp/cli/cli.py +0 -0
- {fastmcp-2.2.2 → fastmcp-2.2.3}/src/fastmcp/client/__init__.py +0 -0
- {fastmcp-2.2.2 → fastmcp-2.2.3}/src/fastmcp/client/base.py +0 -0
- {fastmcp-2.2.2 → fastmcp-2.2.3}/src/fastmcp/client/client.py +0 -0
- {fastmcp-2.2.2 → fastmcp-2.2.3}/src/fastmcp/client/roots.py +0 -0
- {fastmcp-2.2.2 → fastmcp-2.2.3}/src/fastmcp/client/sampling.py +0 -0
- {fastmcp-2.2.2 → fastmcp-2.2.3}/src/fastmcp/client/transports.py +0 -0
- {fastmcp-2.2.2 → fastmcp-2.2.3}/src/fastmcp/contrib/README.md +0 -0
- {fastmcp-2.2.2 → fastmcp-2.2.3}/src/fastmcp/contrib/bulk_tool_caller/README.md +0 -0
- {fastmcp-2.2.2 → fastmcp-2.2.3}/src/fastmcp/contrib/bulk_tool_caller/__init__.py +0 -0
- {fastmcp-2.2.2 → fastmcp-2.2.3}/src/fastmcp/contrib/bulk_tool_caller/bulk_tool_caller.py +0 -0
- {fastmcp-2.2.2 → fastmcp-2.2.3}/src/fastmcp/contrib/bulk_tool_caller/example.py +0 -0
- {fastmcp-2.2.2 → fastmcp-2.2.3}/src/fastmcp/contrib/mcp_mixin/README.md +0 -0
- {fastmcp-2.2.2 → fastmcp-2.2.3}/src/fastmcp/contrib/mcp_mixin/__init__.py +0 -0
- {fastmcp-2.2.2 → fastmcp-2.2.3}/src/fastmcp/contrib/mcp_mixin/example.py +0 -0
- {fastmcp-2.2.2 → fastmcp-2.2.3}/src/fastmcp/contrib/mcp_mixin/mcp_mixin.py +0 -0
- {fastmcp-2.2.2 → fastmcp-2.2.3}/src/fastmcp/exceptions.py +0 -0
- {fastmcp-2.2.2 → fastmcp-2.2.3}/src/fastmcp/prompts/__init__.py +0 -0
- {fastmcp-2.2.2 → fastmcp-2.2.3}/src/fastmcp/prompts/prompt.py +0 -0
- {fastmcp-2.2.2 → fastmcp-2.2.3}/src/fastmcp/prompts/prompt_manager.py +0 -0
- {fastmcp-2.2.2 → fastmcp-2.2.3}/src/fastmcp/py.typed +0 -0
- {fastmcp-2.2.2 → fastmcp-2.2.3}/src/fastmcp/resources/__init__.py +0 -0
- {fastmcp-2.2.2 → fastmcp-2.2.3}/src/fastmcp/resources/resource.py +0 -0
- {fastmcp-2.2.2 → fastmcp-2.2.3}/src/fastmcp/resources/resource_manager.py +0 -0
- {fastmcp-2.2.2 → fastmcp-2.2.3}/src/fastmcp/resources/types.py +0 -0
- {fastmcp-2.2.2 → fastmcp-2.2.3}/src/fastmcp/server/__init__.py +0 -0
- {fastmcp-2.2.2 → fastmcp-2.2.3}/src/fastmcp/server/context.py +0 -0
- {fastmcp-2.2.2 → fastmcp-2.2.3}/src/fastmcp/server/openapi.py +0 -0
- {fastmcp-2.2.2 → fastmcp-2.2.3}/src/fastmcp/server/proxy.py +0 -0
- {fastmcp-2.2.2 → fastmcp-2.2.3}/src/fastmcp/server/server.py +0 -0
- {fastmcp-2.2.2 → fastmcp-2.2.3}/src/fastmcp/settings.py +0 -0
- {fastmcp-2.2.2 → fastmcp-2.2.3}/src/fastmcp/tools/__init__.py +0 -0
- {fastmcp-2.2.2 → fastmcp-2.2.3}/src/fastmcp/tools/tool_manager.py +0 -0
- {fastmcp-2.2.2 → fastmcp-2.2.3}/src/fastmcp/utilities/__init__.py +0 -0
- {fastmcp-2.2.2 → fastmcp-2.2.3}/src/fastmcp/utilities/decorators.py +0 -0
- {fastmcp-2.2.2 → fastmcp-2.2.3}/src/fastmcp/utilities/func_metadata.py +0 -0
- {fastmcp-2.2.2 → fastmcp-2.2.3}/src/fastmcp/utilities/logging.py +0 -0
- {fastmcp-2.2.2 → fastmcp-2.2.3}/src/fastmcp/utilities/openapi.py +0 -0
- {fastmcp-2.2.2 → fastmcp-2.2.3}/src/fastmcp/utilities/types.py +0 -0
- {fastmcp-2.2.2 → fastmcp-2.2.3}/tests/__init__.py +0 -0
- {fastmcp-2.2.2 → fastmcp-2.2.3}/tests/cli/test_run.py +0 -0
- {fastmcp-2.2.2 → fastmcp-2.2.3}/tests/client/__init__.py +0 -0
- {fastmcp-2.2.2 → fastmcp-2.2.3}/tests/client/test_client.py +0 -0
- {fastmcp-2.2.2 → fastmcp-2.2.3}/tests/client/test_roots.py +0 -0
- {fastmcp-2.2.2 → fastmcp-2.2.3}/tests/client/test_sampling.py +0 -0
- {fastmcp-2.2.2 → fastmcp-2.2.3}/tests/conftest.py +0 -0
- {fastmcp-2.2.2 → fastmcp-2.2.3}/tests/contrib/__init__.py +0 -0
- {fastmcp-2.2.2 → fastmcp-2.2.3}/tests/contrib/test_bulk_tool_caller.py +0 -0
- {fastmcp-2.2.2 → fastmcp-2.2.3}/tests/contrib/test_mcp_mixin.py +0 -0
- {fastmcp-2.2.2 → fastmcp-2.2.3}/tests/prompts/__init__.py +0 -0
- {fastmcp-2.2.2 → fastmcp-2.2.3}/tests/prompts/test_base.py +0 -0
- {fastmcp-2.2.2 → fastmcp-2.2.3}/tests/prompts/test_prompt_manager.py +0 -0
- {fastmcp-2.2.2 → fastmcp-2.2.3}/tests/resources/__init__.py +0 -0
- {fastmcp-2.2.2 → fastmcp-2.2.3}/tests/resources/test_file_resources.py +0 -0
- {fastmcp-2.2.2 → fastmcp-2.2.3}/tests/resources/test_function_resources.py +0 -0
- {fastmcp-2.2.2 → fastmcp-2.2.3}/tests/resources/test_resource_manager.py +0 -0
- {fastmcp-2.2.2 → fastmcp-2.2.3}/tests/resources/test_resources.py +0 -0
- {fastmcp-2.2.2 → fastmcp-2.2.3}/tests/server/__init__.py +0 -0
- {fastmcp-2.2.2 → fastmcp-2.2.3}/tests/server/test_file_server.py +0 -0
- {fastmcp-2.2.2 → fastmcp-2.2.3}/tests/server/test_import_server.py +0 -0
- {fastmcp-2.2.2 → fastmcp-2.2.3}/tests/server/test_lifespan.py +0 -0
- {fastmcp-2.2.2 → fastmcp-2.2.3}/tests/server/test_mount.py +0 -0
- {fastmcp-2.2.2 → fastmcp-2.2.3}/tests/server/test_openapi.py +0 -0
- {fastmcp-2.2.2 → fastmcp-2.2.3}/tests/server/test_proxy.py +0 -0
- {fastmcp-2.2.2 → fastmcp-2.2.3}/tests/server/test_run_server.py +0 -0
- {fastmcp-2.2.2 → fastmcp-2.2.3}/tests/server/test_server.py +0 -0
- {fastmcp-2.2.2 → fastmcp-2.2.3}/tests/test_servers/fastmcp_server.py +0 -0
- {fastmcp-2.2.2 → fastmcp-2.2.3}/tests/test_servers/sse.py +0 -0
- {fastmcp-2.2.2 → fastmcp-2.2.3}/tests/test_servers/stdio.py +0 -0
- {fastmcp-2.2.2 → fastmcp-2.2.3}/tests/tools/__init__.py +0 -0
- {fastmcp-2.2.2 → fastmcp-2.2.3}/tests/utilities/__init__.py +0 -0
- {fastmcp-2.2.2 → fastmcp-2.2.3}/tests/utilities/openapi/__init__.py +0 -0
- {fastmcp-2.2.2 → fastmcp-2.2.3}/tests/utilities/openapi/conftest.py +0 -0
- {fastmcp-2.2.2 → fastmcp-2.2.3}/tests/utilities/openapi/test_openapi.py +0 -0
- {fastmcp-2.2.2 → fastmcp-2.2.3}/tests/utilities/openapi/test_openapi_advanced.py +0 -0
- {fastmcp-2.2.2 → fastmcp-2.2.3}/tests/utilities/openapi/test_openapi_fastapi.py +0 -0
- {fastmcp-2.2.2 → fastmcp-2.2.3}/tests/utilities/test_decorated_function.py +0 -0
- {fastmcp-2.2.2 → fastmcp-2.2.3}/tests/utilities/test_func_metadata.py +0 -0
- {fastmcp-2.2.2 → fastmcp-2.2.3}/tests/utilities/test_logging.py +0 -0
- {fastmcp-2.2.2 → fastmcp-2.2.3}/uv.lock +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: fastmcp
|
|
3
|
-
Version: 2.2.
|
|
3
|
+
Version: 2.2.3
|
|
4
4
|
Summary: The fast, Pythonic way to build MCP servers.
|
|
5
5
|
Project-URL: Homepage, https://gofastmcp.com
|
|
6
6
|
Project-URL: Repository, https://github.com/jlowin/fastmcp
|
|
@@ -361,32 +361,32 @@ The `Context` object provides:
|
|
|
361
361
|
|
|
362
362
|
### Images
|
|
363
363
|
|
|
364
|
-
Easily handle image
|
|
364
|
+
Easily handle image outputs using the `fastmcp.Image` helper class.
|
|
365
|
+
|
|
366
|
+
<Tip>
|
|
367
|
+
The below code requires the `pillow` library to be installed.
|
|
368
|
+
</Tip>
|
|
365
369
|
|
|
366
370
|
```python
|
|
367
|
-
from fastmcp import FastMCP, Image
|
|
368
|
-
from
|
|
369
|
-
|
|
371
|
+
from mcp.server.fastmcp import FastMCP, Image
|
|
372
|
+
from io import BytesIO
|
|
373
|
+
try:
|
|
374
|
+
from PIL import Image as PILImage
|
|
375
|
+
except ImportError:
|
|
376
|
+
raise ImportError("Please install the `pillow` library to run this example.")
|
|
370
377
|
|
|
371
|
-
mcp = FastMCP("
|
|
378
|
+
mcp = FastMCP("My App")
|
|
372
379
|
|
|
373
380
|
@mcp.tool()
|
|
374
|
-
def create_thumbnail(
|
|
375
|
-
"""
|
|
376
|
-
img = PILImage.open(
|
|
377
|
-
img.thumbnail((100, 100))
|
|
378
|
-
buffer =
|
|
381
|
+
def create_thumbnail(image_path: str) -> Image:
|
|
382
|
+
"""Create a thumbnail from an image"""
|
|
383
|
+
img = PILImage.open(image_path)
|
|
384
|
+
img.thumbnail((100, 100))
|
|
385
|
+
buffer = BytesIO()
|
|
379
386
|
img.save(buffer, format="PNG")
|
|
380
|
-
# Return a new Image object with the thumbnail data
|
|
381
387
|
return Image(data=buffer.getvalue(), format="png")
|
|
382
|
-
|
|
383
|
-
@mcp.tool()
|
|
384
|
-
def load_image_from_disk(path: str) -> Image:
|
|
385
|
-
"""Loads an image from the specified path."""
|
|
386
|
-
# Handles reading file and detecting format based on extension
|
|
387
|
-
return Image(path=path)
|
|
388
388
|
```
|
|
389
|
-
|
|
389
|
+
Return the `Image` helper class from your tool to send an image to the client. The `Image` helper class handles the conversion to/from the base64-encoded format required by the MCP protocol. It works with either a path to an image file, or a bytes object.
|
|
390
390
|
|
|
391
391
|
|
|
392
392
|
### MCP Clients
|
|
@@ -332,32 +332,32 @@ The `Context` object provides:
|
|
|
332
332
|
|
|
333
333
|
### Images
|
|
334
334
|
|
|
335
|
-
Easily handle image
|
|
335
|
+
Easily handle image outputs using the `fastmcp.Image` helper class.
|
|
336
|
+
|
|
337
|
+
<Tip>
|
|
338
|
+
The below code requires the `pillow` library to be installed.
|
|
339
|
+
</Tip>
|
|
336
340
|
|
|
337
341
|
```python
|
|
338
|
-
from fastmcp import FastMCP, Image
|
|
339
|
-
from
|
|
340
|
-
|
|
342
|
+
from mcp.server.fastmcp import FastMCP, Image
|
|
343
|
+
from io import BytesIO
|
|
344
|
+
try:
|
|
345
|
+
from PIL import Image as PILImage
|
|
346
|
+
except ImportError:
|
|
347
|
+
raise ImportError("Please install the `pillow` library to run this example.")
|
|
341
348
|
|
|
342
|
-
mcp = FastMCP("
|
|
349
|
+
mcp = FastMCP("My App")
|
|
343
350
|
|
|
344
351
|
@mcp.tool()
|
|
345
|
-
def create_thumbnail(
|
|
346
|
-
"""
|
|
347
|
-
img = PILImage.open(
|
|
348
|
-
img.thumbnail((100, 100))
|
|
349
|
-
buffer =
|
|
352
|
+
def create_thumbnail(image_path: str) -> Image:
|
|
353
|
+
"""Create a thumbnail from an image"""
|
|
354
|
+
img = PILImage.open(image_path)
|
|
355
|
+
img.thumbnail((100, 100))
|
|
356
|
+
buffer = BytesIO()
|
|
350
357
|
img.save(buffer, format="PNG")
|
|
351
|
-
# Return a new Image object with the thumbnail data
|
|
352
358
|
return Image(data=buffer.getvalue(), format="png")
|
|
353
|
-
|
|
354
|
-
@mcp.tool()
|
|
355
|
-
def load_image_from_disk(path: str) -> Image:
|
|
356
|
-
"""Loads an image from the specified path."""
|
|
357
|
-
# Handles reading file and detecting format based on extension
|
|
358
|
-
return Image(path=path)
|
|
359
359
|
```
|
|
360
|
-
|
|
360
|
+
Return the `Image` helper class from your tool to send an image to the client. The `Image` helper class handles the conversion to/from the base64-encoded format required by the MCP protocol. It works with either a path to an image file, or a bytes object.
|
|
361
361
|
|
|
362
362
|
|
|
363
363
|
### MCP Clients
|
|
@@ -5,6 +5,10 @@ description: Understand the different ways FastMCP Clients can connect to server
|
|
|
5
5
|
icon: link
|
|
6
6
|
---
|
|
7
7
|
|
|
8
|
+
import { VersionBadge } from "/snippets/version-badge.mdx"
|
|
9
|
+
|
|
10
|
+
<VersionBadge version="2.0.0" />
|
|
11
|
+
|
|
8
12
|
The FastMCP `Client` relies on a `ClientTransport` object to handle the specifics of connecting to and communicating with an MCP server. FastMCP provides several built-in transport implementations for common connection methods.
|
|
9
13
|
|
|
10
14
|
While the `Client` often infers the correct transport automatically (see [Client Overview](/clients/client#transport-inference)), you can also instantiate transports explicitly for more control.
|
|
@@ -4,6 +4,9 @@ description: "Community-contributed modules extending FastMCP"
|
|
|
4
4
|
icon: "cubes"
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
+
import { VersionBadge } from "/snippets/version-badge.mdx"
|
|
8
|
+
|
|
9
|
+
<VersionBadge version="2.2.1" />
|
|
7
10
|
|
|
8
11
|
FastMCP includes a `contrib` package that holds community-contributed modules. These modules extend FastMCP's functionality but aren't officially maintained by the core team.
|
|
9
12
|
|
|
@@ -14,18 +14,20 @@ FastMCP provides a powerful proxying capability that allows one FastMCP server i
|
|
|
14
14
|
|
|
15
15
|
Proxying means setting up a FastMCP server that doesn't implement its own tools or resources directly. Instead, when it receives a request (like `tools/call` or `resources/read`), it forwards that request to a *backend* MCP server, receives the response, and then relays that response back to the original client.
|
|
16
16
|
|
|
17
|
+
|
|
17
18
|
```mermaid
|
|
18
19
|
sequenceDiagram
|
|
19
|
-
participant Client
|
|
20
|
-
participant
|
|
21
|
-
participant BackendServer as Backend MCP Server
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
BackendServer
|
|
26
|
-
|
|
20
|
+
participant ClientApp as Your Client (e.g., Claude Desktop)
|
|
21
|
+
participant FastMCPProxy as FastMCP Proxy Server
|
|
22
|
+
participant BackendServer as Backend MCP Server (e.g., remote SSE)
|
|
23
|
+
|
|
24
|
+
ClientApp->>FastMCPProxy: MCP Request (e.g. stdio)
|
|
25
|
+
Note over FastMCPProxy, BackendServer: Proxy forwards the request
|
|
26
|
+
FastMCPProxy->>BackendServer: MCP Request (e.g. sse)
|
|
27
|
+
BackendServer-->>FastMCPProxy: MCP Response (e.g. sse)
|
|
28
|
+
Note over ClientApp, FastMCPProxy: Proxy relays the response
|
|
29
|
+
FastMCPProxy-->>ClientApp: MCP Response (e.g. stdio)
|
|
27
30
|
```
|
|
28
|
-
|
|
29
31
|
### Use Cases
|
|
30
32
|
|
|
31
33
|
- **Transport Bridging**: Expose a server running on one transport (e.g., a remote SSE server) via a different transport (e.g., local Stdio for Claude Desktop).
|
|
@@ -5,6 +5,8 @@ description: Learn about the core FastMCP server class and how to run it.
|
|
|
5
5
|
icon: server
|
|
6
6
|
---
|
|
7
7
|
|
|
8
|
+
import { VersionBadge } from "/snippets/version-badge.mdx"
|
|
9
|
+
|
|
8
10
|
The central piece of a FastMCP application is the `FastMCP` server class. This class acts as the main container for your application's tools, resources, and prompts, and manages communication with MCP clients.
|
|
9
11
|
|
|
10
12
|
## Creating a Server
|
|
@@ -225,6 +227,8 @@ The CLI can dynamically find and run FastMCP server objects in your files, but i
|
|
|
225
227
|
|
|
226
228
|
## Composing Servers
|
|
227
229
|
|
|
230
|
+
<VersionBadge version="2.2.0" />
|
|
231
|
+
|
|
228
232
|
FastMCP supports composing multiple servers together using `import_server` (static copy) and `mount` (live link). This allows you to organize large applications into modular components or reuse existing servers.
|
|
229
233
|
|
|
230
234
|
See the [Server Composition](/patterns/composition) guide for full details, best practices, and examples.
|
|
@@ -246,6 +250,8 @@ main.mount("sub", sub)
|
|
|
246
250
|
|
|
247
251
|
## Proxying Servers
|
|
248
252
|
|
|
253
|
+
<VersionBadge version="2.0.0" />
|
|
254
|
+
|
|
249
255
|
FastMCP can act as a proxy for any MCP server (local or remote) using `FastMCP.from_client`, letting you bridge transports or add a frontend to existing servers. For example, you can expose a remote SSE server locally via stdio, or vice versa.
|
|
250
256
|
|
|
251
257
|
See the [Proxying Servers](/patterns/proxy) guide for details and advanced usage.
|
|
@@ -5,6 +5,8 @@ description: Create reusable, parameterized prompt templates for MCP clients.
|
|
|
5
5
|
icon: message-lines
|
|
6
6
|
---
|
|
7
7
|
|
|
8
|
+
import { VersionBadge } from "/snippets/version-badge.mdx"
|
|
9
|
+
|
|
8
10
|
Prompts are reusable message templates that help LLMs generate structured, purposeful responses. FastMCP simplifies defining these templates, primarily using the `@mcp.prompt` decorator.
|
|
9
11
|
|
|
10
12
|
## What Are Prompts?
|
|
@@ -201,6 +203,8 @@ Refer to the [Context documentation](/servers/context) for more details on these
|
|
|
201
203
|
|
|
202
204
|
### Duplicate Prompts
|
|
203
205
|
|
|
206
|
+
<VersionBadge version="2.1.0" />
|
|
207
|
+
|
|
204
208
|
You can configure how the FastMCP server handles attempts to register multiple prompts with the same name. Use the `on_duplicate_prompts` setting during `FastMCP` initialization.
|
|
205
209
|
|
|
206
210
|
```python
|
|
@@ -5,6 +5,8 @@ description: Expose data sources and dynamic content generators to your MCP clie
|
|
|
5
5
|
icon: database
|
|
6
6
|
---
|
|
7
7
|
|
|
8
|
+
import { VersionBadge } from "/snippets/version-badge.mdx"
|
|
9
|
+
|
|
8
10
|
Resources represent data or files that an MCP client can read, and resource templates extend this concept by allowing clients to request dynamically generated resources based on parameters passed in the URI.
|
|
9
11
|
|
|
10
12
|
FastMCP simplifies defining both static and dynamic resources, primarily using the `@mcp.resource` decorator.
|
|
@@ -183,6 +185,8 @@ Use these when the content is static or sourced directly from a file/URL, bypass
|
|
|
183
185
|
|
|
184
186
|
#### Custom Resource Keys
|
|
185
187
|
|
|
188
|
+
<VersionBadge version="2.2.0" />
|
|
189
|
+
|
|
186
190
|
When adding resources directly with `mcp.add_resource()`, you can optionally provide a custom storage key:
|
|
187
191
|
|
|
188
192
|
```python
|
|
@@ -201,6 +205,10 @@ Note that this parameter is only available when using `add_resource()` directly
|
|
|
201
205
|
|
|
202
206
|
Resource Templates allow clients to request resources whose content depends on parameters embedded in the URI. Define a template using the **same `@mcp.resource` decorator**, but include `{parameter_name}` placeholders in the URI string and add corresponding arguments to your function signature.
|
|
203
207
|
|
|
208
|
+
Resource templates generate a new resource for each unique set of parameters, which means that resources can be dynamically created on-demand. For example, if the resource template `"user://profile/{name}"` is registered, MCP clients could request `"user://profile/ford"` or `"user://profile/marvin"` to retrieve either of those two user profiles as resources, without having to register each resource individually.
|
|
209
|
+
|
|
210
|
+
Here is a complete example that shows how to define two resource templates:
|
|
211
|
+
|
|
204
212
|
```python
|
|
205
213
|
from fastmcp import FastMCP
|
|
206
214
|
|
|
@@ -233,11 +241,61 @@ def get_repo_info(owner: str, repo: str) -> dict:
|
|
|
233
241
|
}
|
|
234
242
|
```
|
|
235
243
|
|
|
236
|
-
With these templates defined, clients can request:
|
|
244
|
+
With these two templates defined, clients can request a variety of resources:
|
|
237
245
|
- `weather://london/current` → Returns weather for London
|
|
238
|
-
- `
|
|
246
|
+
- `weather://paris/current` → Returns weather for Paris
|
|
247
|
+
- `repos://jlowin/fastmcp/info` → Returns info about the jlowin/fastmcp repository
|
|
248
|
+
- `repos://prefecthq/prefect/info` → Returns info about the prefecthq/prefect repository
|
|
249
|
+
|
|
250
|
+
### Wildcard Parameters
|
|
251
|
+
|
|
252
|
+
<VersionBadge version="2.2.3" />
|
|
239
253
|
|
|
240
|
-
|
|
254
|
+
Resource templates support wildcard parameters that can match multiple path segments. While standard parameters (`{param}`) only match a single path segment and don't cross "/" boundaries, wildcard parameters (`{param*}`) can capture multiple segments including slashes. Wildcards capture all subsequent path segments *up until* the defined part of the URI template (whether literal or another parameter). This allows you to have multiple wildcard parameters in a single URI template.
|
|
255
|
+
|
|
256
|
+
```python
|
|
257
|
+
from fastmcp import FastMCP
|
|
258
|
+
|
|
259
|
+
mcp = FastMCP(name="DataServer")
|
|
260
|
+
|
|
261
|
+
# Standard parameter only matches one segment
|
|
262
|
+
@mcp.resource("files://{filename}")
|
|
263
|
+
def get_file(filename: str) -> str:
|
|
264
|
+
"""Retrieves a file by name."""
|
|
265
|
+
# Will only match files://<single-segment>
|
|
266
|
+
return f"File content for: {filename}"
|
|
267
|
+
|
|
268
|
+
# Wildcard parameter can match multiple segments
|
|
269
|
+
@mcp.resource("path://{filepath*}")
|
|
270
|
+
def get_path_content(filepath: str) -> str:
|
|
271
|
+
"""Retrieves content at a specific path."""
|
|
272
|
+
# Can match path://docs/server/resources.mdx
|
|
273
|
+
return f"Content at path: {filepath}"
|
|
274
|
+
|
|
275
|
+
# Mixing standard and wildcard parameters
|
|
276
|
+
@mcp.resource("repo://{owner}/{path*}/template.py")
|
|
277
|
+
def get_template_file(owner: str, path: str) -> dict:
|
|
278
|
+
"""Retrieves a file from a specific repository and path, but
|
|
279
|
+
only if the resource ends with `template.py`"""
|
|
280
|
+
# Can match repo://jlowin/fastmcp/src/resources/template.py
|
|
281
|
+
return {
|
|
282
|
+
"owner": owner,
|
|
283
|
+
"path": path + "/template.py",
|
|
284
|
+
"content": f"File at {path}/template.py in {owner}'s repository"
|
|
285
|
+
}
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
Wildcard parameters are useful when:
|
|
289
|
+
|
|
290
|
+
- Working with file paths or hierarchical data
|
|
291
|
+
- Creating APIs that need to capture variable-length path segments
|
|
292
|
+
- Building URL-like patterns similar to REST APIs
|
|
293
|
+
|
|
294
|
+
Note that like regular parameters, each wildcard parameter must still be a named parameter in your function signature, and all required function parameters must appear in the URI template.
|
|
295
|
+
|
|
296
|
+
### Default Values
|
|
297
|
+
|
|
298
|
+
<VersionBadge version="2.2.0" />
|
|
241
299
|
|
|
242
300
|
When creating resource templates, FastMCP enforces two rules for the relationship between URI template parameters and function parameters:
|
|
243
301
|
|
|
@@ -315,6 +373,8 @@ Templates provide a powerful way to expose parameterized data access points foll
|
|
|
315
373
|
|
|
316
374
|
### Custom Template Keys
|
|
317
375
|
|
|
376
|
+
<VersionBadge version="2.2.0" />
|
|
377
|
+
|
|
318
378
|
Similar to resources, you can provide custom keys when directly adding templates:
|
|
319
379
|
|
|
320
380
|
```python
|
|
@@ -337,6 +397,8 @@ This allows accessing the same template implementation through different URI pat
|
|
|
337
397
|
|
|
338
398
|
### Duplicate Resources
|
|
339
399
|
|
|
400
|
+
<VersionBadge version="2.1.0" />
|
|
401
|
+
|
|
340
402
|
You can configure how the FastMCP server handles attempts to register multiple resources or templates with the same URI. Use the `on_duplicate_resources` setting during `FastMCP` initialization.
|
|
341
403
|
|
|
342
404
|
```python
|
|
@@ -209,13 +209,18 @@ FastMCP automatically converts the value returned by your function into the appr
|
|
|
209
209
|
- **`str`**: Sent as `TextContent`.
|
|
210
210
|
- **`dict`, `list`, Pydantic `BaseModel`**: Serialized to a JSON string and sent as `TextContent`.
|
|
211
211
|
- **`bytes`**: Base64 encoded and sent as `BlobResourceContents` (often within an `EmbeddedResource`).
|
|
212
|
-
- **`fastmcp.
|
|
212
|
+
- **`fastmcp.Image`**: A helper class for easily returning image data. Sent as `ImageContent`.
|
|
213
213
|
- **`None`**: Results in an empty response (no content is sent back to the client).
|
|
214
214
|
|
|
215
215
|
```python
|
|
216
|
-
from fastmcp
|
|
217
|
-
from PIL import Image as PILImage
|
|
216
|
+
from fastmcp import FastMCP, Image
|
|
218
217
|
import io
|
|
218
|
+
try:
|
|
219
|
+
from PIL import Image as PILImage
|
|
220
|
+
except ImportError:
|
|
221
|
+
raise ImportError("Please install the `pillow` library to run this example.")
|
|
222
|
+
|
|
223
|
+
mcp = FastMCP("Image Demo")
|
|
219
224
|
|
|
220
225
|
@mcp.tool()
|
|
221
226
|
def generate_image(width: int, height: int, color: str) -> Image:
|
|
@@ -306,6 +311,8 @@ For full documentation on the Context object and all its capabilities, see the [
|
|
|
306
311
|
|
|
307
312
|
### Duplicate Tools
|
|
308
313
|
|
|
314
|
+
<VersionBadge version="2.1.0" />
|
|
315
|
+
|
|
309
316
|
You can control how the FastMCP server behaves if you try to register multiple tools with the same name. This is configured using the `on_duplicate_tools` argument when creating the `FastMCP` instance.
|
|
310
317
|
|
|
311
318
|
```python
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export const VersionBadge = ({ version }) => {
|
|
2
|
+
return (
|
|
3
|
+
<code className="version-badge-container">
|
|
4
|
+
<div className="version-badge">
|
|
5
|
+
<span className="version-badge-label">New in version:</span>
|
|
6
|
+
<span className="version-badge-version">{version}</span>
|
|
7
|
+
</div>
|
|
8
|
+
</code>
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
);
|
|
13
|
+
};
|
|
@@ -14,16 +14,18 @@ h6 code:not(pre code) {
|
|
|
14
14
|
|
|
15
15
|
/* Version badge -- display a badge with the current version of the documentation */
|
|
16
16
|
.version-badge {
|
|
17
|
-
display: inline-
|
|
17
|
+
display: inline-block;
|
|
18
18
|
align-items: center;
|
|
19
19
|
gap: 0.3em;
|
|
20
|
-
padding: 0.
|
|
21
|
-
font-size:
|
|
22
|
-
font-weight:
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
20
|
+
padding: 0.2em 0.8em;
|
|
21
|
+
font-size: 1.1em;
|
|
22
|
+
font-weight: 400;
|
|
23
|
+
|
|
24
|
+
font-family: "Inter", sans-serif;
|
|
25
|
+
letter-spacing: 0.025em;
|
|
26
|
+
color: #ff5400;
|
|
27
|
+
background: #ffeee6;
|
|
28
|
+
border: 1px solid rgb(255, 84, 0, 0.5);
|
|
27
29
|
border-radius: 6px;
|
|
28
30
|
box-shadow: none;
|
|
29
31
|
vertical-align: middle;
|
|
@@ -31,6 +33,11 @@ h6 code:not(pre code) {
|
|
|
31
33
|
transition: box-shadow 0.2s, transform 0.15s;
|
|
32
34
|
}
|
|
33
35
|
|
|
36
|
+
.version-badge-container {
|
|
37
|
+
margin: 0;
|
|
38
|
+
padding: 0;
|
|
39
|
+
}
|
|
40
|
+
|
|
34
41
|
.version-badge:hover {
|
|
35
42
|
box-shadow: 0 2px 8px 0 rgba(160, 132, 252, 0.1);
|
|
36
43
|
transform: translateY(-1px) scale(1.03);
|
|
@@ -41,13 +48,3 @@ h6 code:not(pre code) {
|
|
|
41
48
|
background: #312e81;
|
|
42
49
|
border: 1.5px solid #a78bfa;
|
|
43
50
|
}
|
|
44
|
-
|
|
45
|
-
.badge-emoji {
|
|
46
|
-
font-size: 1.15em;
|
|
47
|
-
line-height: 1;
|
|
48
|
-
text-shadow: 0 1px 2px #fff, 0 0px 2px #c084fc;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
.dark .badge-emoji {
|
|
52
|
-
text-shadow: 0 1px 2px #312e81, 0 0px 2px #a78bfa;
|
|
53
|
-
}
|
|
@@ -24,13 +24,16 @@ from fastmcp.utilities.types import _convert_set_defaults
|
|
|
24
24
|
|
|
25
25
|
|
|
26
26
|
def build_regex(template: str) -> re.Pattern:
|
|
27
|
-
# Escape all non-brace characters, then restore {var} placeholders
|
|
28
27
|
parts = re.split(r"(\{[^}]+\})", template)
|
|
29
28
|
pattern = ""
|
|
30
29
|
for part in parts:
|
|
31
30
|
if part.startswith("{") and part.endswith("}"):
|
|
32
31
|
name = part[1:-1]
|
|
33
|
-
|
|
32
|
+
if name.endswith("*"):
|
|
33
|
+
name = name[:-1]
|
|
34
|
+
pattern += f"(?P<{name}>.+)"
|
|
35
|
+
else:
|
|
36
|
+
pattern += f"(?P<{name}>[^/]+)"
|
|
34
37
|
else:
|
|
35
38
|
pattern += re.escape(part)
|
|
36
39
|
return re.compile(f"^{pattern}$")
|
|
@@ -76,7 +76,12 @@ class Tool(BaseModel):
|
|
|
76
76
|
fn_callable,
|
|
77
77
|
skip_names=[context_kwarg] if context_kwarg is not None else [],
|
|
78
78
|
)
|
|
79
|
-
|
|
79
|
+
try:
|
|
80
|
+
parameters = func_arg_metadata.arg_model.model_json_schema()
|
|
81
|
+
except Exception as e:
|
|
82
|
+
raise TypeError(
|
|
83
|
+
f'Unable to parse parameters for function "{fn.__name__}": {e}'
|
|
84
|
+
) from e
|
|
80
85
|
|
|
81
86
|
return cls(
|
|
82
87
|
fn=fn_callable,
|
|
@@ -301,6 +301,23 @@ class TestResourceTemplate:
|
|
|
301
301
|
class TestMatchUriTemplate:
|
|
302
302
|
"""Test match_uri_template function."""
|
|
303
303
|
|
|
304
|
+
@pytest.mark.parametrize(
|
|
305
|
+
"uri, expected_params",
|
|
306
|
+
[
|
|
307
|
+
("test://a/b", None),
|
|
308
|
+
("test://a/b/c", None),
|
|
309
|
+
("test://a/x/b", {"x": "x"}),
|
|
310
|
+
("test://a/x/y/b", None),
|
|
311
|
+
],
|
|
312
|
+
)
|
|
313
|
+
def test_match_uri_template_single_param(
|
|
314
|
+
self, uri: str, expected_params: dict[str, str]
|
|
315
|
+
):
|
|
316
|
+
"""Test that match_uri_template uses the slash delimiter."""
|
|
317
|
+
uri_template = "test://a/{x}/b"
|
|
318
|
+
result = match_uri_template(uri=uri, uri_template=uri_template)
|
|
319
|
+
assert result == expected_params
|
|
320
|
+
|
|
304
321
|
@pytest.mark.parametrize(
|
|
305
322
|
"uri, expected_params",
|
|
306
323
|
[
|
|
@@ -361,7 +378,7 @@ class TestMatchUriTemplate:
|
|
|
361
378
|
("other+prefix+test://foo/test/123", None),
|
|
362
379
|
],
|
|
363
380
|
)
|
|
364
|
-
def
|
|
381
|
+
def test_match_uri_template_with_prefix(
|
|
365
382
|
self, uri: str, expected_params: dict[str, str] | None
|
|
366
383
|
):
|
|
367
384
|
"""Test matching URIs against a template with a prefix."""
|
|
@@ -369,10 +386,60 @@ class TestMatchUriTemplate:
|
|
|
369
386
|
result = match_uri_template(uri=uri, uri_template=uri_template)
|
|
370
387
|
assert result == expected_params
|
|
371
388
|
|
|
372
|
-
def
|
|
389
|
+
def test_match_uri_template_quoted_params(self):
|
|
373
390
|
uri_template = "user://{name}/{email}"
|
|
374
391
|
quoted_name = quote("John Doe", safe="")
|
|
375
392
|
quoted_email = quote("john@example.com", safe="")
|
|
376
393
|
uri = f"user://{quoted_name}/{quoted_email}"
|
|
377
394
|
result = match_uri_template(uri=uri, uri_template=uri_template)
|
|
378
395
|
assert result == {"name": "John Doe", "email": "john@example.com"}
|
|
396
|
+
|
|
397
|
+
@pytest.mark.parametrize(
|
|
398
|
+
"uri, expected_params",
|
|
399
|
+
[
|
|
400
|
+
("test://a/b", None),
|
|
401
|
+
("test://a/b/c", None),
|
|
402
|
+
("test://a/x/b", {"x": "x"}),
|
|
403
|
+
("test://a/x/y/b", {"x": "x/y"}),
|
|
404
|
+
("bad-prefix://a/x/y/b", None),
|
|
405
|
+
("test://a/x/y/z", None),
|
|
406
|
+
],
|
|
407
|
+
)
|
|
408
|
+
def test_match_uri_template_wildcard_param(
|
|
409
|
+
self, uri: str, expected_params: dict[str, str]
|
|
410
|
+
):
|
|
411
|
+
"""Test that match_uri_template uses the slash delimiter."""
|
|
412
|
+
uri_template = "test://a/{x*}/b"
|
|
413
|
+
result = match_uri_template(uri=uri, uri_template=uri_template)
|
|
414
|
+
assert result == expected_params
|
|
415
|
+
|
|
416
|
+
@pytest.mark.parametrize(
|
|
417
|
+
"uri, expected_params",
|
|
418
|
+
[
|
|
419
|
+
("test://a/x/y/b/c/d", {"x": "x/y", "y": "c/d"}),
|
|
420
|
+
("bad-prefix://a/x/y/b/c/d", None),
|
|
421
|
+
("test://a/x/y/c/d", None),
|
|
422
|
+
("test://a/x/b/y", {"x": "x", "y": "y"}),
|
|
423
|
+
],
|
|
424
|
+
)
|
|
425
|
+
def test_match_uri_template_multiple_wildcard_params(
|
|
426
|
+
self, uri: str, expected_params: dict[str, str]
|
|
427
|
+
):
|
|
428
|
+
"""Test that match_uri_template uses the slash delimiter."""
|
|
429
|
+
uri_template = "test://a/{x*}/b/{y*}"
|
|
430
|
+
result = match_uri_template(uri=uri, uri_template=uri_template)
|
|
431
|
+
assert result == expected_params
|
|
432
|
+
|
|
433
|
+
def test_match_uri_template_wildcard_and_literal_param(self):
|
|
434
|
+
"""Test that match_uri_template uses the slash delimiter."""
|
|
435
|
+
uri = "test://a/x/y/b"
|
|
436
|
+
uri_template = "test://a/{x*}/{y}"
|
|
437
|
+
result = match_uri_template(uri=uri, uri_template=uri_template)
|
|
438
|
+
assert result == {"x": "x/y", "y": "b"}
|
|
439
|
+
|
|
440
|
+
def test_match_consecutive_params(self):
|
|
441
|
+
"""Test that consecutive parameters without a / are not matched."""
|
|
442
|
+
uri = "test://a/x/y"
|
|
443
|
+
uri_template = "test://a/{x}{y}"
|
|
444
|
+
result = match_uri_template(uri=uri, uri_template=uri_template)
|
|
445
|
+
assert result is None
|
|
@@ -2,8 +2,10 @@ import json
|
|
|
2
2
|
import logging
|
|
3
3
|
|
|
4
4
|
import pytest
|
|
5
|
+
from mcp.types import ImageContent, TextContent
|
|
5
6
|
from pydantic import BaseModel
|
|
6
7
|
|
|
8
|
+
from fastmcp import Context, FastMCP, Image
|
|
7
9
|
from fastmcp.exceptions import NotFoundError, ToolError
|
|
8
10
|
from fastmcp.tools import ToolManager
|
|
9
11
|
from fastmcp.tools.tool import Tool
|
|
@@ -68,6 +70,18 @@ class TestAddTools:
|
|
|
68
70
|
assert "age" in tool.parameters["$defs"]["UserInput"]["properties"]
|
|
69
71
|
assert "flag" in tool.parameters["properties"]
|
|
70
72
|
|
|
73
|
+
async def test_tool_with_image_return(self):
|
|
74
|
+
def image_tool(data: bytes) -> Image:
|
|
75
|
+
return Image(data=data)
|
|
76
|
+
|
|
77
|
+
manager = ToolManager()
|
|
78
|
+
manager.add_tool_from_fn(image_tool)
|
|
79
|
+
|
|
80
|
+
tool = manager.get_tool("image_tool")
|
|
81
|
+
result = await tool.run({"data": "test.png"})
|
|
82
|
+
assert tool.parameters["properties"]["data"]["type"] == "string"
|
|
83
|
+
assert isinstance(result[0], ImageContent)
|
|
84
|
+
|
|
71
85
|
def test_add_invalid_tool(self):
|
|
72
86
|
manager = ToolManager()
|
|
73
87
|
with pytest.raises(AttributeError):
|
|
@@ -263,7 +277,6 @@ class TestCallTools:
|
|
|
263
277
|
result = await manager.call_tool("double", {"n": 5})
|
|
264
278
|
assert isinstance(result, list)
|
|
265
279
|
assert len(result) == 1
|
|
266
|
-
from mcp.types import TextContent
|
|
267
280
|
|
|
268
281
|
assert isinstance(result[0], TextContent)
|
|
269
282
|
assert result[0].text == "10"
|
|
@@ -279,7 +292,6 @@ class TestCallTools:
|
|
|
279
292
|
result = await manager.call_tool("add", {"a": 1})
|
|
280
293
|
assert isinstance(result, list)
|
|
281
294
|
assert len(result) == 1
|
|
282
|
-
from mcp.types import TextContent
|
|
283
295
|
|
|
284
296
|
assert isinstance(result[0], TextContent)
|
|
285
297
|
assert result[0].text == "2"
|
|
@@ -307,7 +319,6 @@ class TestCallTools:
|
|
|
307
319
|
manager = ToolManager()
|
|
308
320
|
manager.add_tool_from_fn(sum_vals)
|
|
309
321
|
# Try both with plain list and with JSON list
|
|
310
|
-
from mcp.types import TextContent
|
|
311
322
|
|
|
312
323
|
result = await manager.call_tool("sum_vals", {"vals": "[1, 2, 3]"})
|
|
313
324
|
assert isinstance(result, list)
|
|
@@ -329,7 +340,6 @@ class TestCallTools:
|
|
|
329
340
|
|
|
330
341
|
manager = ToolManager()
|
|
331
342
|
manager.add_tool_from_fn(concat_strs)
|
|
332
|
-
from mcp.types import TextContent
|
|
333
343
|
|
|
334
344
|
# Try both with plain python object and with JSON list
|
|
335
345
|
result = await manager.call_tool("concat_strs", {"vals": ["a", "b", "c"]})
|
|
@@ -357,10 +367,6 @@ class TestCallTools:
|
|
|
357
367
|
assert result[0].text == '"a"'
|
|
358
368
|
|
|
359
369
|
async def test_call_tool_with_complex_model(self):
|
|
360
|
-
from mcp.types import TextContent
|
|
361
|
-
|
|
362
|
-
from fastmcp import Context
|
|
363
|
-
|
|
364
370
|
class MyShrimpTank(BaseModel):
|
|
365
371
|
class Shrimp(BaseModel):
|
|
366
372
|
name: str
|
|
@@ -397,8 +403,6 @@ class TestCallTools:
|
|
|
397
403
|
|
|
398
404
|
class TestToolSchema:
|
|
399
405
|
async def test_context_arg_excluded_from_schema(self):
|
|
400
|
-
from fastmcp import Context
|
|
401
|
-
|
|
402
406
|
def something(a: int, ctx: Context) -> int:
|
|
403
407
|
return a
|
|
404
408
|
|
|
@@ -415,7 +419,6 @@ class TestContextHandling:
|
|
|
415
419
|
def test_context_parameter_detection(self):
|
|
416
420
|
"""Test that context parameters are properly detected in
|
|
417
421
|
Tool.from_function()."""
|
|
418
|
-
from fastmcp import Context
|
|
419
422
|
|
|
420
423
|
def tool_with_context(x: int, ctx: Context) -> str:
|
|
421
424
|
return str(x)
|
|
@@ -432,9 +435,6 @@ class TestContextHandling:
|
|
|
432
435
|
|
|
433
436
|
async def test_context_injection(self):
|
|
434
437
|
"""Test that context is properly injected during tool execution."""
|
|
435
|
-
from mcp.types import TextContent
|
|
436
|
-
|
|
437
|
-
from fastmcp import Context, FastMCP
|
|
438
438
|
|
|
439
439
|
def tool_with_context(x: int, ctx: Context) -> str:
|
|
440
440
|
assert isinstance(ctx, Context)
|
|
@@ -453,9 +453,6 @@ class TestContextHandling:
|
|
|
453
453
|
|
|
454
454
|
async def test_context_injection_async(self):
|
|
455
455
|
"""Test that context is properly injected in async tools."""
|
|
456
|
-
from mcp.types import TextContent
|
|
457
|
-
|
|
458
|
-
from fastmcp import Context, FastMCP
|
|
459
456
|
|
|
460
457
|
async def async_tool(x: int, ctx: Context) -> str:
|
|
461
458
|
assert isinstance(ctx, Context)
|
|
@@ -476,8 +473,6 @@ class TestContextHandling:
|
|
|
476
473
|
"""Test that context is optional when calling tools."""
|
|
477
474
|
from mcp.types import TextContent
|
|
478
475
|
|
|
479
|
-
from fastmcp import Context
|
|
480
|
-
|
|
481
476
|
def tool_with_context(x: int, ctx: Context | None = None) -> str:
|
|
482
477
|
return str(x)
|
|
483
478
|
|
|
@@ -492,7 +487,6 @@ class TestContextHandling:
|
|
|
492
487
|
|
|
493
488
|
async def test_context_error_handling(self):
|
|
494
489
|
"""Test error handling when context injection fails."""
|
|
495
|
-
from fastmcp import Context, FastMCP
|
|
496
490
|
|
|
497
491
|
def tool_with_context(x: int, ctx: Context) -> str:
|
|
498
492
|
raise ValueError("Test error")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|