fastmcp 2.3.2__tar.gz → 2.3.4__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.3.2 → fastmcp-2.3.4}/.github/workflows/run-tests.yml +3 -11
- {fastmcp-2.3.2 → fastmcp-2.3.4}/PKG-INFO +2 -2
- {fastmcp-2.3.2 → fastmcp-2.3.4}/docs/clients/client.mdx +44 -1
- fastmcp-2.3.4/docs/servers/composition.mdx +212 -0
- {fastmcp-2.3.2 → fastmcp-2.3.4}/docs/servers/resources.mdx +38 -9
- {fastmcp-2.3.2 → fastmcp-2.3.4}/docs/servers/tools.mdx +34 -12
- {fastmcp-2.3.2 → fastmcp-2.3.4}/examples/complex_inputs.py +1 -1
- {fastmcp-2.3.2 → fastmcp-2.3.4}/examples/desktop.py +1 -1
- {fastmcp-2.3.2 → fastmcp-2.3.4}/pyproject.toml +1 -1
- {fastmcp-2.3.2 → fastmcp-2.3.4}/src/fastmcp/client/__init__.py +2 -0
- {fastmcp-2.3.2 → fastmcp-2.3.4}/src/fastmcp/client/client.py +84 -21
- {fastmcp-2.3.2 → fastmcp-2.3.4}/src/fastmcp/client/transports.py +53 -28
- {fastmcp-2.3.2 → fastmcp-2.3.4}/src/fastmcp/exceptions.py +2 -0
- {fastmcp-2.3.2 → fastmcp-2.3.4}/src/fastmcp/prompts/prompt.py +12 -6
- {fastmcp-2.3.2 → fastmcp-2.3.4}/src/fastmcp/resources/resource_manager.py +22 -1
- {fastmcp-2.3.2 → fastmcp-2.3.4}/src/fastmcp/resources/template.py +21 -17
- {fastmcp-2.3.2 → fastmcp-2.3.4}/src/fastmcp/resources/types.py +25 -27
- {fastmcp-2.3.2 → fastmcp-2.3.4}/src/fastmcp/server/openapi.py +14 -1
- {fastmcp-2.3.2 → fastmcp-2.3.4}/src/fastmcp/server/proxy.py +4 -4
- {fastmcp-2.3.2 → fastmcp-2.3.4}/src/fastmcp/server/server.py +78 -56
- fastmcp-2.3.4/src/fastmcp/settings.py +131 -0
- {fastmcp-2.3.2 → fastmcp-2.3.4}/src/fastmcp/tools/tool.py +45 -45
- {fastmcp-2.3.2 → fastmcp-2.3.4}/src/fastmcp/tools/tool_manager.py +27 -2
- fastmcp-2.3.4/src/fastmcp/utilities/exceptions.py +49 -0
- fastmcp-2.3.4/src/fastmcp/utilities/json_schema.py +120 -0
- {fastmcp-2.3.2 → fastmcp-2.3.4}/src/fastmcp/utilities/logging.py +11 -6
- {fastmcp-2.3.2 → fastmcp-2.3.4}/src/fastmcp/utilities/openapi.py +122 -7
- {fastmcp-2.3.2 → fastmcp-2.3.4}/src/fastmcp/utilities/tests.py +4 -2
- {fastmcp-2.3.2 → fastmcp-2.3.4}/tests/client/test_client.py +143 -4
- {fastmcp-2.3.2 → fastmcp-2.3.4}/tests/client/test_sse.py +65 -4
- {fastmcp-2.3.2 → fastmcp-2.3.4}/tests/client/test_streamable_http.py +49 -2
- {fastmcp-2.3.2 → fastmcp-2.3.4}/tests/contrib/test_bulk_tool_caller.py +1 -2
- {fastmcp-2.3.2 → fastmcp-2.3.4}/tests/resources/test_file_resources.py +3 -2
- {fastmcp-2.3.2 → fastmcp-2.3.4}/tests/resources/test_function_resources.py +1 -1
- {fastmcp-2.3.2 → fastmcp-2.3.4}/tests/resources/test_resource_manager.py +85 -1
- {fastmcp-2.3.2 → fastmcp-2.3.4}/tests/resources/test_resource_template.py +0 -15
- {fastmcp-2.3.2 → fastmcp-2.3.4}/tests/server/test_mount.py +15 -0
- {fastmcp-2.3.2 → fastmcp-2.3.4}/tests/server/test_openapi.py +121 -2
- {fastmcp-2.3.2 → fastmcp-2.3.4}/tests/server/test_proxy.py +4 -5
- {fastmcp-2.3.2 → fastmcp-2.3.4}/tests/server/test_server.py +22 -2
- {fastmcp-2.3.2 → fastmcp-2.3.4}/tests/server/test_server_interactions.py +29 -28
- {fastmcp-2.3.2 → fastmcp-2.3.4}/tests/test_deprecated.py +11 -0
- {fastmcp-2.3.2 → fastmcp-2.3.4}/tests/tools/test_tool.py +3 -3
- {fastmcp-2.3.2 → fastmcp-2.3.4}/tests/tools/test_tool_manager.py +85 -1
- {fastmcp-2.3.2 → fastmcp-2.3.4}/tests/utilities/openapi/test_openapi_fastapi.py +17 -0
- fastmcp-2.3.4/tests/utilities/test_json_schema.py +304 -0
- {fastmcp-2.3.2 → fastmcp-2.3.4}/tests/utilities/test_typeadapter.py +2 -2
- {fastmcp-2.3.2 → fastmcp-2.3.4}/uv.lock +4 -4
- fastmcp-2.3.2/docs/servers/composition.mdx +0 -340
- fastmcp-2.3.2/src/fastmcp/settings.py +0 -105
- fastmcp-2.3.2/src/fastmcp/utilities/json_schema.py +0 -59
- fastmcp-2.3.2/tests/utilities/test_json_schema.py +0 -110
- {fastmcp-2.3.2 → fastmcp-2.3.4}/.cursor/rules/core-mcp-objects.mdc +0 -0
- {fastmcp-2.3.2 → fastmcp-2.3.4}/.github/ISSUE_TEMPLATE/bug.yml +0 -0
- {fastmcp-2.3.2 → fastmcp-2.3.4}/.github/ISSUE_TEMPLATE/config.yml +0 -0
- {fastmcp-2.3.2 → fastmcp-2.3.4}/.github/ISSUE_TEMPLATE/enhancement.yml +0 -0
- {fastmcp-2.3.2 → fastmcp-2.3.4}/.github/release.yml +0 -0
- {fastmcp-2.3.2 → fastmcp-2.3.4}/.github/workflows/publish.yml +0 -0
- {fastmcp-2.3.2 → fastmcp-2.3.4}/.github/workflows/run-static.yml +0 -0
- {fastmcp-2.3.2 → fastmcp-2.3.4}/.gitignore +0 -0
- {fastmcp-2.3.2 → fastmcp-2.3.4}/.pre-commit-config.yaml +0 -0
- {fastmcp-2.3.2 → fastmcp-2.3.4}/LICENSE +0 -0
- {fastmcp-2.3.2 → fastmcp-2.3.4}/README.md +0 -0
- {fastmcp-2.3.2 → fastmcp-2.3.4}/Windows_Notes.md +0 -0
- {fastmcp-2.3.2 → fastmcp-2.3.4}/docs/assets/demo-inspector.png +0 -0
- {fastmcp-2.3.2 → fastmcp-2.3.4}/docs/clients/transports.mdx +0 -0
- {fastmcp-2.3.2 → fastmcp-2.3.4}/docs/deployment/asgi.mdx +0 -0
- {fastmcp-2.3.2 → fastmcp-2.3.4}/docs/deployment/authentication.mdx +0 -0
- {fastmcp-2.3.2 → fastmcp-2.3.4}/docs/deployment/cli.mdx +0 -0
- {fastmcp-2.3.2 → fastmcp-2.3.4}/docs/deployment/running-server.mdx +0 -0
- {fastmcp-2.3.2 → fastmcp-2.3.4}/docs/docs.json +0 -0
- {fastmcp-2.3.2 → fastmcp-2.3.4}/docs/getting-started/installation.mdx +0 -0
- {fastmcp-2.3.2 → fastmcp-2.3.4}/docs/getting-started/quickstart.mdx +0 -0
- {fastmcp-2.3.2 → fastmcp-2.3.4}/docs/getting-started/welcome.mdx +0 -0
- {fastmcp-2.3.2 → fastmcp-2.3.4}/docs/patterns/contrib.mdx +0 -0
- {fastmcp-2.3.2 → fastmcp-2.3.4}/docs/patterns/decorating-methods.mdx +0 -0
- {fastmcp-2.3.2 → fastmcp-2.3.4}/docs/patterns/fastapi.mdx +0 -0
- {fastmcp-2.3.2 → fastmcp-2.3.4}/docs/patterns/http-requests.mdx +0 -0
- {fastmcp-2.3.2 → fastmcp-2.3.4}/docs/patterns/openapi.mdx +0 -0
- {fastmcp-2.3.2 → fastmcp-2.3.4}/docs/patterns/testing.mdx +0 -0
- {fastmcp-2.3.2 → fastmcp-2.3.4}/docs/servers/context.mdx +0 -0
- {fastmcp-2.3.2 → fastmcp-2.3.4}/docs/servers/fastmcp.mdx +0 -0
- {fastmcp-2.3.2 → fastmcp-2.3.4}/docs/servers/prompts.mdx +0 -0
- {fastmcp-2.3.2 → fastmcp-2.3.4}/docs/servers/proxy.mdx +0 -0
- {fastmcp-2.3.2 → fastmcp-2.3.4}/docs/snippets/version-badge.mdx +0 -0
- {fastmcp-2.3.2 → fastmcp-2.3.4}/docs/style.css +0 -0
- {fastmcp-2.3.2 → fastmcp-2.3.4}/examples/echo.py +0 -0
- {fastmcp-2.3.2 → fastmcp-2.3.4}/examples/memory.py +0 -0
- {fastmcp-2.3.2 → fastmcp-2.3.4}/examples/mount_example.py +0 -0
- {fastmcp-2.3.2 → fastmcp-2.3.4}/examples/sampling.py +0 -0
- {fastmcp-2.3.2 → fastmcp-2.3.4}/examples/screenshot.py +0 -0
- {fastmcp-2.3.2 → fastmcp-2.3.4}/examples/serializer.py +0 -0
- {fastmcp-2.3.2 → fastmcp-2.3.4}/examples/simple_echo.py +0 -0
- {fastmcp-2.3.2 → fastmcp-2.3.4}/examples/smart_home/README.md +0 -0
- {fastmcp-2.3.2 → fastmcp-2.3.4}/examples/smart_home/pyproject.toml +0 -0
- {fastmcp-2.3.2 → fastmcp-2.3.4}/examples/smart_home/src/smart_home/__init__.py +0 -0
- {fastmcp-2.3.2 → fastmcp-2.3.4}/examples/smart_home/src/smart_home/__main__.py +0 -0
- {fastmcp-2.3.2 → fastmcp-2.3.4}/examples/smart_home/src/smart_home/hub.py +0 -0
- {fastmcp-2.3.2 → fastmcp-2.3.4}/examples/smart_home/src/smart_home/lights/__init__.py +0 -0
- {fastmcp-2.3.2 → fastmcp-2.3.4}/examples/smart_home/src/smart_home/lights/hue_utils.py +0 -0
- {fastmcp-2.3.2 → fastmcp-2.3.4}/examples/smart_home/src/smart_home/lights/server.py +0 -0
- {fastmcp-2.3.2 → fastmcp-2.3.4}/examples/smart_home/src/smart_home/py.typed +0 -0
- {fastmcp-2.3.2 → fastmcp-2.3.4}/examples/smart_home/src/smart_home/settings.py +0 -0
- {fastmcp-2.3.2 → fastmcp-2.3.4}/examples/smart_home/uv.lock +0 -0
- {fastmcp-2.3.2 → fastmcp-2.3.4}/examples/text_me.py +0 -0
- {fastmcp-2.3.2 → fastmcp-2.3.4}/justfile +0 -0
- {fastmcp-2.3.2 → fastmcp-2.3.4}/src/fastmcp/__init__.py +0 -0
- {fastmcp-2.3.2 → fastmcp-2.3.4}/src/fastmcp/cli/__init__.py +0 -0
- {fastmcp-2.3.2 → fastmcp-2.3.4}/src/fastmcp/cli/claude.py +0 -0
- {fastmcp-2.3.2 → fastmcp-2.3.4}/src/fastmcp/cli/cli.py +0 -0
- {fastmcp-2.3.2 → fastmcp-2.3.4}/src/fastmcp/client/base.py +0 -0
- {fastmcp-2.3.2 → fastmcp-2.3.4}/src/fastmcp/client/logging.py +0 -0
- {fastmcp-2.3.2 → fastmcp-2.3.4}/src/fastmcp/client/roots.py +0 -0
- {fastmcp-2.3.2 → fastmcp-2.3.4}/src/fastmcp/client/sampling.py +0 -0
- {fastmcp-2.3.2 → fastmcp-2.3.4}/src/fastmcp/contrib/README.md +0 -0
- {fastmcp-2.3.2 → fastmcp-2.3.4}/src/fastmcp/contrib/bulk_tool_caller/README.md +0 -0
- {fastmcp-2.3.2 → fastmcp-2.3.4}/src/fastmcp/contrib/bulk_tool_caller/__init__.py +0 -0
- {fastmcp-2.3.2 → fastmcp-2.3.4}/src/fastmcp/contrib/bulk_tool_caller/bulk_tool_caller.py +0 -0
- {fastmcp-2.3.2 → fastmcp-2.3.4}/src/fastmcp/contrib/bulk_tool_caller/example.py +0 -0
- {fastmcp-2.3.2 → fastmcp-2.3.4}/src/fastmcp/contrib/mcp_mixin/README.md +0 -0
- {fastmcp-2.3.2 → fastmcp-2.3.4}/src/fastmcp/contrib/mcp_mixin/__init__.py +0 -0
- {fastmcp-2.3.2 → fastmcp-2.3.4}/src/fastmcp/contrib/mcp_mixin/example.py +0 -0
- {fastmcp-2.3.2 → fastmcp-2.3.4}/src/fastmcp/contrib/mcp_mixin/mcp_mixin.py +0 -0
- {fastmcp-2.3.2 → fastmcp-2.3.4}/src/fastmcp/low_level/README.md +0 -0
- {fastmcp-2.3.2 → fastmcp-2.3.4}/src/fastmcp/low_level/__init__.py +0 -0
- {fastmcp-2.3.2 → fastmcp-2.3.4}/src/fastmcp/low_level/sse_server_transport.py +0 -0
- {fastmcp-2.3.2 → fastmcp-2.3.4}/src/fastmcp/prompts/__init__.py +0 -0
- {fastmcp-2.3.2 → fastmcp-2.3.4}/src/fastmcp/prompts/prompt_manager.py +0 -0
- {fastmcp-2.3.2 → fastmcp-2.3.4}/src/fastmcp/py.typed +0 -0
- {fastmcp-2.3.2 → fastmcp-2.3.4}/src/fastmcp/resources/__init__.py +0 -0
- {fastmcp-2.3.2 → fastmcp-2.3.4}/src/fastmcp/resources/resource.py +0 -0
- {fastmcp-2.3.2 → fastmcp-2.3.4}/src/fastmcp/server/__init__.py +0 -0
- {fastmcp-2.3.2 → fastmcp-2.3.4}/src/fastmcp/server/context.py +0 -0
- {fastmcp-2.3.2 → fastmcp-2.3.4}/src/fastmcp/server/dependencies.py +0 -0
- {fastmcp-2.3.2 → fastmcp-2.3.4}/src/fastmcp/server/http.py +0 -0
- {fastmcp-2.3.2 → fastmcp-2.3.4}/src/fastmcp/tools/__init__.py +0 -0
- {fastmcp-2.3.2 → fastmcp-2.3.4}/src/fastmcp/utilities/__init__.py +0 -0
- {fastmcp-2.3.2 → fastmcp-2.3.4}/src/fastmcp/utilities/cache.py +0 -0
- {fastmcp-2.3.2 → fastmcp-2.3.4}/src/fastmcp/utilities/decorators.py +0 -0
- {fastmcp-2.3.2 → fastmcp-2.3.4}/src/fastmcp/utilities/types.py +0 -0
- {fastmcp-2.3.2 → fastmcp-2.3.4}/tests/__init__.py +0 -0
- {fastmcp-2.3.2 → fastmcp-2.3.4}/tests/cli/test_cli.py +0 -0
- {fastmcp-2.3.2 → fastmcp-2.3.4}/tests/cli/test_run.py +0 -0
- {fastmcp-2.3.2 → fastmcp-2.3.4}/tests/client/__init__.py +0 -0
- {fastmcp-2.3.2 → fastmcp-2.3.4}/tests/client/test_logs.py +0 -0
- {fastmcp-2.3.2 → fastmcp-2.3.4}/tests/client/test_roots.py +0 -0
- {fastmcp-2.3.2 → fastmcp-2.3.4}/tests/client/test_sampling.py +0 -0
- {fastmcp-2.3.2 → fastmcp-2.3.4}/tests/conftest.py +0 -0
- {fastmcp-2.3.2 → fastmcp-2.3.4}/tests/contrib/__init__.py +0 -0
- {fastmcp-2.3.2 → fastmcp-2.3.4}/tests/contrib/test_mcp_mixin.py +0 -0
- {fastmcp-2.3.2 → fastmcp-2.3.4}/tests/prompts/__init__.py +0 -0
- {fastmcp-2.3.2 → fastmcp-2.3.4}/tests/prompts/test_prompt.py +0 -0
- {fastmcp-2.3.2 → fastmcp-2.3.4}/tests/prompts/test_prompt_manager.py +0 -0
- {fastmcp-2.3.2 → fastmcp-2.3.4}/tests/resources/__init__.py +0 -0
- {fastmcp-2.3.2 → fastmcp-2.3.4}/tests/resources/test_resources.py +0 -0
- {fastmcp-2.3.2 → fastmcp-2.3.4}/tests/server/__init__.py +0 -0
- {fastmcp-2.3.2 → fastmcp-2.3.4}/tests/server/test_auth_integration.py +0 -0
- {fastmcp-2.3.2 → fastmcp-2.3.4}/tests/server/test_context.py +0 -0
- {fastmcp-2.3.2 → fastmcp-2.3.4}/tests/server/test_file_server.py +0 -0
- {fastmcp-2.3.2 → fastmcp-2.3.4}/tests/server/test_http_dependencies.py +0 -0
- {fastmcp-2.3.2 → fastmcp-2.3.4}/tests/server/test_http_middleware.py +0 -0
- {fastmcp-2.3.2 → fastmcp-2.3.4}/tests/server/test_import_server.py +0 -0
- {fastmcp-2.3.2 → fastmcp-2.3.4}/tests/server/test_lifespan.py +0 -0
- {fastmcp-2.3.2 → fastmcp-2.3.4}/tests/server/test_run_server.py +0 -0
- {fastmcp-2.3.2 → fastmcp-2.3.4}/tests/server/test_tool_annotations.py +0 -0
- {fastmcp-2.3.2 → fastmcp-2.3.4}/tests/test_examples.py +0 -0
- {fastmcp-2.3.2 → fastmcp-2.3.4}/tests/test_servers/fastmcp_server.py +0 -0
- {fastmcp-2.3.2 → fastmcp-2.3.4}/tests/test_servers/sse.py +0 -0
- {fastmcp-2.3.2 → fastmcp-2.3.4}/tests/test_servers/stdio.py +0 -0
- {fastmcp-2.3.2 → fastmcp-2.3.4}/tests/tools/__init__.py +0 -0
- {fastmcp-2.3.2 → fastmcp-2.3.4}/tests/utilities/__init__.py +0 -0
- {fastmcp-2.3.2 → fastmcp-2.3.4}/tests/utilities/openapi/__init__.py +0 -0
- {fastmcp-2.3.2 → fastmcp-2.3.4}/tests/utilities/openapi/conftest.py +0 -0
- {fastmcp-2.3.2 → fastmcp-2.3.4}/tests/utilities/openapi/test_openapi.py +0 -0
- {fastmcp-2.3.2 → fastmcp-2.3.4}/tests/utilities/openapi/test_openapi_advanced.py +0 -0
- {fastmcp-2.3.2 → fastmcp-2.3.4}/tests/utilities/test_cache.py +0 -0
- {fastmcp-2.3.2 → fastmcp-2.3.4}/tests/utilities/test_decorated_function.py +0 -0
- {fastmcp-2.3.2 → fastmcp-2.3.4}/tests/utilities/test_logging.py +0 -0
- {fastmcp-2.3.2 → fastmcp-2.3.4}/tests/utilities/test_tests.py +0 -0
- {fastmcp-2.3.2 → fastmcp-2.3.4}/tests/utilities/test_types.py +0 -0
|
@@ -45,18 +45,10 @@ jobs:
|
|
|
45
45
|
with:
|
|
46
46
|
enable-cache: true
|
|
47
47
|
cache-dependency-glob: "uv.lock"
|
|
48
|
-
|
|
49
|
-
- name: Set up Python ${{ matrix.python-version }}
|
|
50
|
-
run: uv python install ${{ matrix.python-version }}
|
|
48
|
+
python-version: ${{ matrix.python-version }}
|
|
51
49
|
|
|
52
50
|
- name: Install FastMCP
|
|
53
|
-
run: uv sync --dev
|
|
54
|
-
|
|
55
|
-
- name: Fix pyreadline on Windows
|
|
56
|
-
if: matrix.os == 'windows-latest'
|
|
57
|
-
run: |
|
|
58
|
-
uv pip uninstall -y pyreadline
|
|
59
|
-
uv pip install pyreadline3
|
|
51
|
+
run: uv sync --dev --locked
|
|
60
52
|
|
|
61
53
|
- name: Run tests
|
|
62
|
-
run: uv run
|
|
54
|
+
run: uv run pytest
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: fastmcp
|
|
3
|
-
Version: 2.3.
|
|
3
|
+
Version: 2.3.4
|
|
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
|
|
@@ -19,7 +19,7 @@ Classifier: Typing :: Typed
|
|
|
19
19
|
Requires-Python: >=3.10
|
|
20
20
|
Requires-Dist: exceptiongroup>=1.2.2
|
|
21
21
|
Requires-Dist: httpx>=0.28.1
|
|
22
|
-
Requires-Dist: mcp<2.0.0,>=1.8.
|
|
22
|
+
Requires-Dist: mcp<2.0.0,>=1.8.1
|
|
23
23
|
Requires-Dist: openapi-pydantic>=0.5.1
|
|
24
24
|
Requires-Dist: python-dotenv>=1.1.0
|
|
25
25
|
Requires-Dist: rich>=13.9.4
|
|
@@ -115,14 +115,18 @@ The standard client methods return user-friendly representations that may change
|
|
|
115
115
|
tools = await client.list_tools()
|
|
116
116
|
# tools -> list[mcp.types.Tool]
|
|
117
117
|
```
|
|
118
|
-
* **`call_tool(name: str, arguments: dict[str, Any] | None = None)`**: Executes a tool on the server.
|
|
118
|
+
* **`call_tool(name: str, arguments: dict[str, Any] | None = None, timeout: float | None = None)`**: Executes a tool on the server.
|
|
119
119
|
```python
|
|
120
120
|
result = await client.call_tool("add", {"a": 5, "b": 3})
|
|
121
121
|
# result -> list[mcp.types.TextContent | mcp.types.ImageContent | ...]
|
|
122
122
|
print(result[0].text) # Assuming TextContent, e.g., '8'
|
|
123
|
+
|
|
124
|
+
# With timeout (aborts if execution takes longer than 2 seconds)
|
|
125
|
+
result = await client.call_tool("long_running_task", {"param": "value"}, timeout=2.0)
|
|
123
126
|
```
|
|
124
127
|
* Arguments are passed as a dictionary. FastMCP servers automatically handle JSON string parsing for complex types if needed.
|
|
125
128
|
* Returns a list of content objects (usually `TextContent` or `ImageContent`).
|
|
129
|
+
* The optional `timeout` parameter limits the maximum execution time (in seconds) for this specific call, overriding any client-level timeout.
|
|
126
130
|
|
|
127
131
|
#### Resource Operations
|
|
128
132
|
|
|
@@ -191,6 +195,45 @@ These methods are especially useful for debugging or when you need to access met
|
|
|
191
195
|
|
|
192
196
|
MCP allows servers to interact with clients in order to provide additional capabilities. The `Client` constructor accepts additional configuration to handle these server requests.
|
|
193
197
|
|
|
198
|
+
#### Timeout Control
|
|
199
|
+
|
|
200
|
+
<VersionBadge version="2.3.4" />
|
|
201
|
+
|
|
202
|
+
You can control request timeouts at both the client level and individual request level:
|
|
203
|
+
|
|
204
|
+
```python
|
|
205
|
+
from fastmcp import Client
|
|
206
|
+
from fastmcp.exceptions import McpError
|
|
207
|
+
|
|
208
|
+
# Client with a global 5-second timeout for all requests
|
|
209
|
+
client = Client(
|
|
210
|
+
my_mcp_server,
|
|
211
|
+
timeout=5.0 # Default timeout in seconds
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
async with client:
|
|
215
|
+
# This uses the global 5-second timeout
|
|
216
|
+
result1 = await client.call_tool("quick_task", {"param": "value"})
|
|
217
|
+
|
|
218
|
+
# This specifies a 10-second timeout for this specific call
|
|
219
|
+
result2 = await client.call_tool("slow_task", {"param": "value"}, timeout=10.0)
|
|
220
|
+
|
|
221
|
+
try:
|
|
222
|
+
# This will likely timeout
|
|
223
|
+
result3 = await client.call_tool("medium_task", {"param": "value"}, timeout=0.01)
|
|
224
|
+
except McpError as e:
|
|
225
|
+
# Handle timeout error
|
|
226
|
+
print(f"The task timed out: {e}")
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
<Warning>
|
|
230
|
+
Timeout behavior varies between transport types:
|
|
231
|
+
|
|
232
|
+
- With **SSE** transport, the per-request (tool call) timeout **always** takes precedence, regardless of which is lower.
|
|
233
|
+
- With **HTTP** transport, the **lower** of the two timeouts (client or tool call) takes precedence.
|
|
234
|
+
|
|
235
|
+
For consistent behavior across all transports, we recommend explicitly setting timeouts at the individual tool call level when needed, rather than relying on client-level timeouts.
|
|
236
|
+
</Warning>
|
|
194
237
|
|
|
195
238
|
#### LLM Sampling
|
|
196
239
|
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Server Composition
|
|
3
|
+
sidebarTitle: Composition
|
|
4
|
+
description: Combine multiple FastMCP servers into a single, larger application using mounting and importing.
|
|
5
|
+
icon: puzzle-piece
|
|
6
|
+
---
|
|
7
|
+
import { VersionBadge } from '/snippets/version-badge.mdx'
|
|
8
|
+
|
|
9
|
+
<VersionBadge version="2.2.0" />
|
|
10
|
+
|
|
11
|
+
As your MCP applications grow, you might want to organize your tools, resources, and prompts into logical modules or reuse existing server components. FastMCP supports composition through two methods:
|
|
12
|
+
|
|
13
|
+
- **`import_server`**: For a one-time copy of components with prefixing (static composition).
|
|
14
|
+
- **`mount`**: For creating a live link where the main server delegates requests to the subserver (dynamic composition).
|
|
15
|
+
|
|
16
|
+
## Why Compose Servers?
|
|
17
|
+
|
|
18
|
+
- **Modularity**: Break down large applications into smaller, focused servers (e.g., a `WeatherServer`, a `DatabaseServer`, a `CalendarServer`).
|
|
19
|
+
- **Reusability**: Create common utility servers (e.g., a `TextProcessingServer`) and mount them wherever needed.
|
|
20
|
+
- **Teamwork**: Different teams can work on separate FastMCP servers that are later combined.
|
|
21
|
+
- **Organization**: Keep related functionality grouped together logically.
|
|
22
|
+
|
|
23
|
+
### Importing vs Mounting
|
|
24
|
+
|
|
25
|
+
The choice of importing or mounting depends on your use case and requirements.
|
|
26
|
+
|
|
27
|
+
| Feature | Importing | Mounting |
|
|
28
|
+
|---------|----------------|---------|
|
|
29
|
+
| **Method** | `FastMCP.import_server()` | `FastMCP.mount()` |
|
|
30
|
+
| **Composition Type** | One-time copy (static) | Live link (dynamic) |
|
|
31
|
+
| **Updates** | Changes to subserver NOT reflected | Changes to subserver immediately reflected |
|
|
32
|
+
| **Best For** | Bundling finalized components | Modular runtime composition |
|
|
33
|
+
|
|
34
|
+
### Proxy Servers
|
|
35
|
+
|
|
36
|
+
FastMCP supports [MCP proxying](/patterns/proxy), which allows you to mirror a local or remote server in a local FastMCP instance. Proxies are fully compatible with both importing and mounting.
|
|
37
|
+
|
|
38
|
+
## Importing (Static Composition)
|
|
39
|
+
|
|
40
|
+
The `import_server()` method copies all components (tools, resources, templates, prompts) from one `FastMCP` instance (the *subserver*) into another (the *main server*). A `prefix` is added to avoid naming conflicts.
|
|
41
|
+
|
|
42
|
+
```python
|
|
43
|
+
from fastmcp import FastMCP
|
|
44
|
+
import asyncio
|
|
45
|
+
|
|
46
|
+
# Define subservers
|
|
47
|
+
weather_mcp = FastMCP(name="WeatherService")
|
|
48
|
+
|
|
49
|
+
@weather_mcp.tool()
|
|
50
|
+
def get_forecast(city: str) -> dict:
|
|
51
|
+
"""Get weather forecast."""
|
|
52
|
+
return {"city": city, "forecast": "Sunny"}
|
|
53
|
+
|
|
54
|
+
@weather_mcp.resource("data://cities/supported")
|
|
55
|
+
def list_supported_cities() -> list[str]:
|
|
56
|
+
"""List cities with weather support."""
|
|
57
|
+
return ["London", "Paris", "Tokyo"]
|
|
58
|
+
|
|
59
|
+
# Define main server
|
|
60
|
+
main_mcp = FastMCP(name="MainApp")
|
|
61
|
+
|
|
62
|
+
# Import subserver
|
|
63
|
+
async def setup():
|
|
64
|
+
await main_mcp.import_server("weather", weather_mcp)
|
|
65
|
+
|
|
66
|
+
# Result: main_mcp now contains prefixed components:
|
|
67
|
+
# - Tool: "weather_get_forecast"
|
|
68
|
+
# - Resource: "weather+data://cities/supported"
|
|
69
|
+
|
|
70
|
+
if __name__ == "__main__":
|
|
71
|
+
asyncio.run(setup())
|
|
72
|
+
main_mcp.run()
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### How Importing Works
|
|
76
|
+
|
|
77
|
+
When you call `await main_mcp.import_server(prefix, subserver)`:
|
|
78
|
+
|
|
79
|
+
1. **Tools**: All tools from `subserver` are added to `main_mcp` with names prefixed using `{prefix}_`.
|
|
80
|
+
- `subserver.tool(name="my_tool")` becomes `main_mcp.tool(name="{prefix}_my_tool")`.
|
|
81
|
+
2. **Resources**: All resources are added with URIs prefixed using `{prefix}+`.
|
|
82
|
+
- `subserver.resource(uri="data://info")` becomes `main_mcp.resource(uri="{prefix}+data://info")`.
|
|
83
|
+
3. **Resource Templates**: Templates are prefixed similarly to resources.
|
|
84
|
+
- `subserver.resource(uri="data://{id}")` becomes `main_mcp.resource(uri="{prefix}+data://{id}")`.
|
|
85
|
+
4. **Prompts**: All prompts are added with names prefixed like tools.
|
|
86
|
+
- `subserver.prompt(name="my_prompt")` becomes `main_mcp.prompt(name="{prefix}_my_prompt")`.
|
|
87
|
+
|
|
88
|
+
Note that `import_server` performs a **one-time copy** of components. Changes made to the `subserver` *after* importing **will not** be reflected in `main_mcp`. The `subserver`'s `lifespan` context is also **not** executed by the main server.
|
|
89
|
+
|
|
90
|
+
## Mounting (Live Linking)
|
|
91
|
+
|
|
92
|
+
The `mount()` method creates a **live link** between the `main_mcp` server and the `subserver`. Instead of copying components, requests for components matching the `prefix` are **delegated** to the `subserver` at runtime.
|
|
93
|
+
|
|
94
|
+
```python
|
|
95
|
+
import asyncio
|
|
96
|
+
from fastmcp import FastMCP, Client
|
|
97
|
+
|
|
98
|
+
# Define subserver
|
|
99
|
+
dynamic_mcp = FastMCP(name="DynamicService")
|
|
100
|
+
|
|
101
|
+
@dynamic_mcp.tool()
|
|
102
|
+
def initial_tool():
|
|
103
|
+
"""Initial tool demonstration."""
|
|
104
|
+
return "Initial Tool Exists"
|
|
105
|
+
|
|
106
|
+
# Mount subserver (synchronous operation)
|
|
107
|
+
main_mcp = FastMCP(name="MainAppLive")
|
|
108
|
+
main_mcp.mount("dynamic", dynamic_mcp)
|
|
109
|
+
|
|
110
|
+
# Add a tool AFTER mounting - it will be accessible through main_mcp
|
|
111
|
+
@dynamic_mcp.tool()
|
|
112
|
+
def added_later():
|
|
113
|
+
"""Tool added after mounting."""
|
|
114
|
+
return "Tool Added Dynamically!"
|
|
115
|
+
|
|
116
|
+
# Testing access to mounted tools
|
|
117
|
+
async def test_dynamic_mount():
|
|
118
|
+
tools = await main_mcp.get_tools()
|
|
119
|
+
print("Available tools:", list(tools.keys()))
|
|
120
|
+
# Shows: ['dynamic_initial_tool', 'dynamic_added_later']
|
|
121
|
+
|
|
122
|
+
async with Client(main_mcp) as client:
|
|
123
|
+
result = await client.call_tool("dynamic_added_later")
|
|
124
|
+
print("Result:", result[0].text)
|
|
125
|
+
# Shows: "Tool Added Dynamically!"
|
|
126
|
+
|
|
127
|
+
if __name__ == "__main__":
|
|
128
|
+
asyncio.run(test_dynamic_mount())
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### How Mounting Works
|
|
132
|
+
|
|
133
|
+
When mounting is configured:
|
|
134
|
+
|
|
135
|
+
1. **Live Link**: The parent server establishes a connection to the mounted server.
|
|
136
|
+
2. **Dynamic Updates**: Changes to the mounted server are immediately reflected when accessed through the parent.
|
|
137
|
+
3. **Prefixed Access**: The parent server uses prefixes to route requests to the mounted server.
|
|
138
|
+
4. **Delegation**: Requests for components matching the prefix are delegated to the mounted server at runtime.
|
|
139
|
+
|
|
140
|
+
The same prefixing rules apply as with `import_server` for naming tools, resources, templates, and prompts.
|
|
141
|
+
|
|
142
|
+
### Direct vs. Proxy Mounting
|
|
143
|
+
|
|
144
|
+
<VersionBadge version="2.2.7" />
|
|
145
|
+
|
|
146
|
+
FastMCP supports two mounting modes:
|
|
147
|
+
|
|
148
|
+
1. **Direct Mounting** (default): The parent server directly accesses the mounted server's objects in memory.
|
|
149
|
+
- No client lifecycle events occur on the mounted server
|
|
150
|
+
- The mounted server's lifespan context is not executed
|
|
151
|
+
- Communication is handled through direct method calls
|
|
152
|
+
|
|
153
|
+
2. **Proxy Mounting**: The parent server treats the mounted server as a separate entity and communicates with it through a client interface.
|
|
154
|
+
- Full client lifecycle events occur on the mounted server
|
|
155
|
+
- The mounted server's lifespan is executed when a client connects
|
|
156
|
+
- Communication happens via an in-memory Client transport
|
|
157
|
+
|
|
158
|
+
```python
|
|
159
|
+
# Direct mounting (default when no custom lifespan)
|
|
160
|
+
main_mcp.mount("api", api_server)
|
|
161
|
+
|
|
162
|
+
# Proxy mounting (preserves full client lifecycle)
|
|
163
|
+
main_mcp.mount("api", api_server, as_proxy=True)
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
FastMCP automatically uses proxy mounting when the mounted server has a custom lifespan, but you can override this behavior with the `as_proxy` parameter.
|
|
167
|
+
|
|
168
|
+
#### Interaction with Proxy Servers
|
|
169
|
+
|
|
170
|
+
When using `FastMCP.from_client()` to create a proxy server, mounting that server will always use proxy mounting:
|
|
171
|
+
|
|
172
|
+
```python
|
|
173
|
+
# Create a proxy for a remote server
|
|
174
|
+
remote_proxy = FastMCP.from_client(Client("http://example.com/mcp"))
|
|
175
|
+
|
|
176
|
+
# Mount the proxy (always uses proxy mounting)
|
|
177
|
+
main_server.mount("remote", remote_proxy)
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
## Customizing Separators
|
|
181
|
+
|
|
182
|
+
Both `import_server()` and `mount()` allow you to customize the separators used for prefixing components. The defaults are `_` for tools and prompts, and `+` for resources.
|
|
183
|
+
|
|
184
|
+
<CodeGroup>
|
|
185
|
+
|
|
186
|
+
```python import_server
|
|
187
|
+
await main_mcp.import_server(
|
|
188
|
+
prefix="api",
|
|
189
|
+
app=some_subserver,
|
|
190
|
+
tool_separator="_", # Tool name becomes: "api_sub_tool_name"
|
|
191
|
+
resource_separator="+", # Resource URI becomes: "api+data://sub_resource"
|
|
192
|
+
prompt_separator="_" # Prompt name becomes: "api_sub_prompt_name"
|
|
193
|
+
)
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
```python mount
|
|
197
|
+
main_mcp.mount(
|
|
198
|
+
prefix="api",
|
|
199
|
+
app=some_subserver,
|
|
200
|
+
tool_separator="_", # Tool name becomes: "api_sub_tool_name"
|
|
201
|
+
resource_separator="+", # Resource URI becomes: "api+data://sub_resource"
|
|
202
|
+
prompt_separator="_" # Prompt name becomes: "api_sub_prompt_name"
|
|
203
|
+
)
|
|
204
|
+
```
|
|
205
|
+
</CodeGroup>
|
|
206
|
+
<Warning>
|
|
207
|
+
Be cautious when choosing separators. Some MCP clients (like Claude Desktop) might have restrictions on characters allowed in tool names (e.g., `/` might not be supported). The defaults (`_` for names, `+` for URIs) are generally safe.
|
|
208
|
+
</Warning>
|
|
209
|
+
|
|
210
|
+
<Tip>
|
|
211
|
+
To "cleanly" import or mount a server, set the prefix and all separators to `""` (empty string). This is generally unecessary but could save a couple tokens at the risk of a name collision!
|
|
212
|
+
</Tip>
|
|
@@ -147,6 +147,7 @@ async def read_important_log() -> str:
|
|
|
147
147
|
return "Log file not found."
|
|
148
148
|
```
|
|
149
149
|
|
|
150
|
+
|
|
150
151
|
### Resource Classes
|
|
151
152
|
|
|
152
153
|
While `@mcp.resource` is ideal for dynamic content, you can directly register pre-defined resources (like static files or simple text) using `mcp.add_resource()` and concrete `Resource` subclasses.
|
|
@@ -403,17 +404,45 @@ In this stacked decorator pattern:
|
|
|
403
404
|
- Each parameter defaults to `None` when not included in the URI
|
|
404
405
|
- The function logic handles whichever parameter is provided
|
|
405
406
|
|
|
406
|
-
|
|
407
|
+
Templates provide a powerful way to expose parameterized data access points following REST-like principles.
|
|
408
|
+
|
|
409
|
+
## Error Handling
|
|
407
410
|
|
|
408
|
-
|
|
409
|
-
2. **Discovery:** Clients list templates via `resources/listResourceTemplates`.
|
|
410
|
-
3. **Request & Matching:** A client requests a specific URI, e.g., `weather://london/current`. FastMCP matches this to the `weather://{city}/current` template.
|
|
411
|
-
4. **Parameter Extraction:** It extracts the parameter value: `city="london"`.
|
|
412
|
-
5. **Type Conversion & Function Call:** It converts extracted values to the types hinted in the function and calls `get_weather(city="london")`.
|
|
413
|
-
6. **Default Values:** For any function parameters with default values not included in the URI template, FastMCP uses the default values.
|
|
414
|
-
7. **Response:** The function's return value is formatted (e.g., dict to JSON) and sent back as the resource content.
|
|
411
|
+
<VersionBadge version="2.3.4" />
|
|
415
412
|
|
|
416
|
-
|
|
413
|
+
If your resource function encounters an error, you can raise a standard Python exception (`ValueError`, `TypeError`, `FileNotFoundError`, custom exceptions, etc.) or a FastMCP `ResourceError`.
|
|
414
|
+
|
|
415
|
+
For security reasons, most exceptions are wrapped in a generic `ResourceError` before being sent to the client, with internal error details masked. However, if you raise a `ResourceError` directly, its contents **are** included in the response. This allows you to provide informative error messages to the client on an opt-in basis.
|
|
416
|
+
|
|
417
|
+
```python
|
|
418
|
+
from fastmcp import FastMCP
|
|
419
|
+
from fastmcp.exceptions import ResourceError
|
|
420
|
+
|
|
421
|
+
mcp = FastMCP(name="DataServer")
|
|
422
|
+
|
|
423
|
+
@mcp.resource("resource://safe-error")
|
|
424
|
+
def fail_with_details() -> str:
|
|
425
|
+
"""This resource provides detailed error information."""
|
|
426
|
+
# ResourceError contents are sent back to clients
|
|
427
|
+
raise ResourceError("Unable to retrieve data: file not found")
|
|
428
|
+
|
|
429
|
+
@mcp.resource("resource://masked-error")
|
|
430
|
+
def fail_with_masked_details() -> str:
|
|
431
|
+
"""This resource masks internal error details."""
|
|
432
|
+
# Other exceptions are converted to ResourceError with generic message
|
|
433
|
+
raise ValueError("Sensitive internal file path: /etc/secrets.conf")
|
|
434
|
+
|
|
435
|
+
@mcp.resource("data://{id}")
|
|
436
|
+
def get_data_by_id(id: str) -> dict:
|
|
437
|
+
"""Template resources also support the same error handling pattern."""
|
|
438
|
+
if id == "secure":
|
|
439
|
+
raise ValueError("Cannot access secure data")
|
|
440
|
+
elif id == "missing":
|
|
441
|
+
raise ResourceError("Data ID 'missing' not found in database")
|
|
442
|
+
return {"id": id, "value": "data"}
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
This error handling pattern applies to both regular resources and resource templates.
|
|
417
446
|
|
|
418
447
|
## Server Behavior
|
|
419
448
|
|
|
@@ -248,27 +248,30 @@ def do_nothing() -> None:
|
|
|
248
248
|
|
|
249
249
|
### Error Handling
|
|
250
250
|
|
|
251
|
-
|
|
251
|
+
<VersionBadge version="2.3.4" />
|
|
252
|
+
|
|
253
|
+
If your tool encounters an error, you can raise a standard Python exception (`ValueError`, `TypeError`, `FileNotFoundError`, custom exceptions, etc.) or a FastMCP `ToolError`.
|
|
254
|
+
|
|
255
|
+
In all cases, the exception is logged and converted into an MCP error response to be sent back to the client LLM. For security reasons, the error message is **not** included in the response by default. However, if you raise a `ToolError`, the contents of the exception **are** included in the response. This allows you to provide informative error messages to the client LLM on an opt-in basis, which can help the LLM understand failures and react appropriately.
|
|
256
|
+
|
|
257
|
+
```python {2, 10, 14}
|
|
258
|
+
from fastmcp import FastMCP
|
|
259
|
+
from fastmcp.exceptions import ToolError
|
|
252
260
|
|
|
253
|
-
```python
|
|
254
261
|
@mcp.tool()
|
|
255
262
|
def divide(a: float, b: float) -> float:
|
|
256
263
|
"""Divide a by b."""
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
raise ValueError("Division by zero is not allowed.")
|
|
264
|
+
|
|
265
|
+
# Python exceptions raise errors but the contents are not sent to clients
|
|
260
266
|
if not isinstance(a, (int, float)) or not isinstance(b, (int, float)):
|
|
261
267
|
raise TypeError("Both arguments must be numbers.")
|
|
268
|
+
|
|
269
|
+
if b == 0:
|
|
270
|
+
# ToolError contents are sent back to clients
|
|
271
|
+
raise ToolError("Division by zero is not allowed.")
|
|
262
272
|
return a / b
|
|
263
273
|
```
|
|
264
274
|
|
|
265
|
-
FastMCP automatically catches exceptions raised within your tool function:
|
|
266
|
-
1. It converts the exception into an MCP error response, typically including the exception type and message.
|
|
267
|
-
2. This error response is sent back to the client/LLM.
|
|
268
|
-
3. The LLM can then inform the user or potentially try the tool again with different arguments.
|
|
269
|
-
|
|
270
|
-
Using informative exceptions helps the LLM understand failures and react appropriately.
|
|
271
|
-
|
|
272
275
|
### Annotations
|
|
273
276
|
|
|
274
277
|
<VersionBadge version="2.2.7" />
|
|
@@ -709,6 +712,25 @@ The duplicate behavior options are:
|
|
|
709
712
|
- `"replace"`: Silently replaces the existing tool with the new one.
|
|
710
713
|
- `"ignore"`: Keeps the original tool and ignores the new registration attempt.
|
|
711
714
|
|
|
715
|
+
### Removing Tools
|
|
716
|
+
|
|
717
|
+
<VersionBadge version="2.3.4" />
|
|
718
|
+
|
|
719
|
+
You can dynamically remove tools from a server using the `remove_tool` method:
|
|
720
|
+
|
|
721
|
+
```python
|
|
722
|
+
from fastmcp import FastMCP
|
|
723
|
+
|
|
724
|
+
mcp = FastMCP(name="DynamicToolServer")
|
|
725
|
+
|
|
726
|
+
@mcp.tool()
|
|
727
|
+
def calculate_sum(a: int, b: int) -> int:
|
|
728
|
+
"""Add two numbers together."""
|
|
729
|
+
return a + b
|
|
730
|
+
|
|
731
|
+
mcp.remove_tool("calculate_sum")
|
|
732
|
+
```
|
|
733
|
+
|
|
712
734
|
### Legacy JSON Parsing
|
|
713
735
|
|
|
714
736
|
<VersionBadge version="2.2.10" />
|
|
@@ -9,6 +9,7 @@ from .transports import (
|
|
|
9
9
|
UvxStdioTransport,
|
|
10
10
|
NpxStdioTransport,
|
|
11
11
|
FastMCPTransport,
|
|
12
|
+
StreamableHttpTransport,
|
|
12
13
|
)
|
|
13
14
|
|
|
14
15
|
__all__ = [
|
|
@@ -22,4 +23,5 @@ __all__ = [
|
|
|
22
23
|
"UvxStdioTransport",
|
|
23
24
|
"NpxStdioTransport",
|
|
24
25
|
"FastMCPTransport",
|
|
26
|
+
"StreamableHttpTransport",
|
|
25
27
|
]
|