fastmcp 2.3.3__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.
Files changed (179) hide show
  1. {fastmcp-2.3.3 → fastmcp-2.3.4}/.github/workflows/run-tests.yml +3 -11
  2. {fastmcp-2.3.3 → fastmcp-2.3.4}/PKG-INFO +2 -2
  3. {fastmcp-2.3.3 → fastmcp-2.3.4}/docs/clients/client.mdx +44 -1
  4. {fastmcp-2.3.3 → fastmcp-2.3.4}/docs/servers/resources.mdx +38 -9
  5. {fastmcp-2.3.3 → fastmcp-2.3.4}/docs/servers/tools.mdx +34 -12
  6. {fastmcp-2.3.3 → fastmcp-2.3.4}/pyproject.toml +1 -1
  7. {fastmcp-2.3.3 → fastmcp-2.3.4}/src/fastmcp/client/__init__.py +2 -0
  8. {fastmcp-2.3.3 → fastmcp-2.3.4}/src/fastmcp/client/client.py +84 -21
  9. {fastmcp-2.3.3 → fastmcp-2.3.4}/src/fastmcp/client/transports.py +53 -28
  10. {fastmcp-2.3.3 → fastmcp-2.3.4}/src/fastmcp/exceptions.py +2 -0
  11. {fastmcp-2.3.3 → fastmcp-2.3.4}/src/fastmcp/prompts/prompt.py +12 -6
  12. {fastmcp-2.3.3 → fastmcp-2.3.4}/src/fastmcp/resources/resource_manager.py +22 -1
  13. {fastmcp-2.3.3 → fastmcp-2.3.4}/src/fastmcp/resources/template.py +21 -17
  14. {fastmcp-2.3.3 → fastmcp-2.3.4}/src/fastmcp/resources/types.py +25 -27
  15. {fastmcp-2.3.3 → fastmcp-2.3.4}/src/fastmcp/server/openapi.py +14 -1
  16. {fastmcp-2.3.3 → fastmcp-2.3.4}/src/fastmcp/server/proxy.py +4 -4
  17. {fastmcp-2.3.3 → fastmcp-2.3.4}/src/fastmcp/server/server.py +73 -53
  18. fastmcp-2.3.4/src/fastmcp/settings.py +131 -0
  19. {fastmcp-2.3.3 → fastmcp-2.3.4}/src/fastmcp/tools/tool.py +45 -45
  20. {fastmcp-2.3.3 → fastmcp-2.3.4}/src/fastmcp/tools/tool_manager.py +27 -2
  21. fastmcp-2.3.4/src/fastmcp/utilities/exceptions.py +49 -0
  22. fastmcp-2.3.4/src/fastmcp/utilities/json_schema.py +120 -0
  23. {fastmcp-2.3.3 → fastmcp-2.3.4}/src/fastmcp/utilities/logging.py +11 -6
  24. {fastmcp-2.3.3 → fastmcp-2.3.4}/src/fastmcp/utilities/openapi.py +122 -7
  25. {fastmcp-2.3.3 → fastmcp-2.3.4}/tests/client/test_client.py +143 -4
  26. {fastmcp-2.3.3 → fastmcp-2.3.4}/tests/client/test_sse.py +56 -2
  27. {fastmcp-2.3.3 → fastmcp-2.3.4}/tests/client/test_streamable_http.py +49 -2
  28. {fastmcp-2.3.3 → fastmcp-2.3.4}/tests/contrib/test_bulk_tool_caller.py +1 -2
  29. {fastmcp-2.3.3 → fastmcp-2.3.4}/tests/resources/test_file_resources.py +3 -2
  30. {fastmcp-2.3.3 → fastmcp-2.3.4}/tests/resources/test_function_resources.py +1 -1
  31. {fastmcp-2.3.3 → fastmcp-2.3.4}/tests/resources/test_resource_manager.py +85 -1
  32. {fastmcp-2.3.3 → fastmcp-2.3.4}/tests/resources/test_resource_template.py +0 -15
  33. {fastmcp-2.3.3 → fastmcp-2.3.4}/tests/server/test_openapi.py +121 -2
  34. {fastmcp-2.3.3 → fastmcp-2.3.4}/tests/server/test_proxy.py +4 -5
  35. {fastmcp-2.3.3 → fastmcp-2.3.4}/tests/server/test_server.py +22 -2
  36. {fastmcp-2.3.3 → fastmcp-2.3.4}/tests/server/test_server_interactions.py +29 -28
  37. {fastmcp-2.3.3 → fastmcp-2.3.4}/tests/test_deprecated.py +11 -0
  38. {fastmcp-2.3.3 → fastmcp-2.3.4}/tests/tools/test_tool.py +3 -3
  39. {fastmcp-2.3.3 → fastmcp-2.3.4}/tests/tools/test_tool_manager.py +85 -1
  40. {fastmcp-2.3.3 → fastmcp-2.3.4}/tests/utilities/openapi/test_openapi_fastapi.py +17 -0
  41. fastmcp-2.3.4/tests/utilities/test_json_schema.py +304 -0
  42. {fastmcp-2.3.3 → fastmcp-2.3.4}/tests/utilities/test_typeadapter.py +2 -2
  43. {fastmcp-2.3.3 → fastmcp-2.3.4}/uv.lock +4 -4
  44. fastmcp-2.3.3/src/fastmcp/settings.py +0 -105
  45. fastmcp-2.3.3/src/fastmcp/utilities/json_schema.py +0 -59
  46. fastmcp-2.3.3/tests/utilities/test_json_schema.py +0 -110
  47. {fastmcp-2.3.3 → fastmcp-2.3.4}/.cursor/rules/core-mcp-objects.mdc +0 -0
  48. {fastmcp-2.3.3 → fastmcp-2.3.4}/.github/ISSUE_TEMPLATE/bug.yml +0 -0
  49. {fastmcp-2.3.3 → fastmcp-2.3.4}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  50. {fastmcp-2.3.3 → fastmcp-2.3.4}/.github/ISSUE_TEMPLATE/enhancement.yml +0 -0
  51. {fastmcp-2.3.3 → fastmcp-2.3.4}/.github/release.yml +0 -0
  52. {fastmcp-2.3.3 → fastmcp-2.3.4}/.github/workflows/publish.yml +0 -0
  53. {fastmcp-2.3.3 → fastmcp-2.3.4}/.github/workflows/run-static.yml +0 -0
  54. {fastmcp-2.3.3 → fastmcp-2.3.4}/.gitignore +0 -0
  55. {fastmcp-2.3.3 → fastmcp-2.3.4}/.pre-commit-config.yaml +0 -0
  56. {fastmcp-2.3.3 → fastmcp-2.3.4}/LICENSE +0 -0
  57. {fastmcp-2.3.3 → fastmcp-2.3.4}/README.md +0 -0
  58. {fastmcp-2.3.3 → fastmcp-2.3.4}/Windows_Notes.md +0 -0
  59. {fastmcp-2.3.3 → fastmcp-2.3.4}/docs/assets/demo-inspector.png +0 -0
  60. {fastmcp-2.3.3 → fastmcp-2.3.4}/docs/clients/transports.mdx +0 -0
  61. {fastmcp-2.3.3 → fastmcp-2.3.4}/docs/deployment/asgi.mdx +0 -0
  62. {fastmcp-2.3.3 → fastmcp-2.3.4}/docs/deployment/authentication.mdx +0 -0
  63. {fastmcp-2.3.3 → fastmcp-2.3.4}/docs/deployment/cli.mdx +0 -0
  64. {fastmcp-2.3.3 → fastmcp-2.3.4}/docs/deployment/running-server.mdx +0 -0
  65. {fastmcp-2.3.3 → fastmcp-2.3.4}/docs/docs.json +0 -0
  66. {fastmcp-2.3.3 → fastmcp-2.3.4}/docs/getting-started/installation.mdx +0 -0
  67. {fastmcp-2.3.3 → fastmcp-2.3.4}/docs/getting-started/quickstart.mdx +0 -0
  68. {fastmcp-2.3.3 → fastmcp-2.3.4}/docs/getting-started/welcome.mdx +0 -0
  69. {fastmcp-2.3.3 → fastmcp-2.3.4}/docs/patterns/contrib.mdx +0 -0
  70. {fastmcp-2.3.3 → fastmcp-2.3.4}/docs/patterns/decorating-methods.mdx +0 -0
  71. {fastmcp-2.3.3 → fastmcp-2.3.4}/docs/patterns/fastapi.mdx +0 -0
  72. {fastmcp-2.3.3 → fastmcp-2.3.4}/docs/patterns/http-requests.mdx +0 -0
  73. {fastmcp-2.3.3 → fastmcp-2.3.4}/docs/patterns/openapi.mdx +0 -0
  74. {fastmcp-2.3.3 → fastmcp-2.3.4}/docs/patterns/testing.mdx +0 -0
  75. {fastmcp-2.3.3 → fastmcp-2.3.4}/docs/servers/composition.mdx +0 -0
  76. {fastmcp-2.3.3 → fastmcp-2.3.4}/docs/servers/context.mdx +0 -0
  77. {fastmcp-2.3.3 → fastmcp-2.3.4}/docs/servers/fastmcp.mdx +0 -0
  78. {fastmcp-2.3.3 → fastmcp-2.3.4}/docs/servers/prompts.mdx +0 -0
  79. {fastmcp-2.3.3 → fastmcp-2.3.4}/docs/servers/proxy.mdx +0 -0
  80. {fastmcp-2.3.3 → fastmcp-2.3.4}/docs/snippets/version-badge.mdx +0 -0
  81. {fastmcp-2.3.3 → fastmcp-2.3.4}/docs/style.css +0 -0
  82. {fastmcp-2.3.3 → fastmcp-2.3.4}/examples/complex_inputs.py +0 -0
  83. {fastmcp-2.3.3 → fastmcp-2.3.4}/examples/desktop.py +0 -0
  84. {fastmcp-2.3.3 → fastmcp-2.3.4}/examples/echo.py +0 -0
  85. {fastmcp-2.3.3 → fastmcp-2.3.4}/examples/memory.py +0 -0
  86. {fastmcp-2.3.3 → fastmcp-2.3.4}/examples/mount_example.py +0 -0
  87. {fastmcp-2.3.3 → fastmcp-2.3.4}/examples/sampling.py +0 -0
  88. {fastmcp-2.3.3 → fastmcp-2.3.4}/examples/screenshot.py +0 -0
  89. {fastmcp-2.3.3 → fastmcp-2.3.4}/examples/serializer.py +0 -0
  90. {fastmcp-2.3.3 → fastmcp-2.3.4}/examples/simple_echo.py +0 -0
  91. {fastmcp-2.3.3 → fastmcp-2.3.4}/examples/smart_home/README.md +0 -0
  92. {fastmcp-2.3.3 → fastmcp-2.3.4}/examples/smart_home/pyproject.toml +0 -0
  93. {fastmcp-2.3.3 → fastmcp-2.3.4}/examples/smart_home/src/smart_home/__init__.py +0 -0
  94. {fastmcp-2.3.3 → fastmcp-2.3.4}/examples/smart_home/src/smart_home/__main__.py +0 -0
  95. {fastmcp-2.3.3 → fastmcp-2.3.4}/examples/smart_home/src/smart_home/hub.py +0 -0
  96. {fastmcp-2.3.3 → fastmcp-2.3.4}/examples/smart_home/src/smart_home/lights/__init__.py +0 -0
  97. {fastmcp-2.3.3 → fastmcp-2.3.4}/examples/smart_home/src/smart_home/lights/hue_utils.py +0 -0
  98. {fastmcp-2.3.3 → fastmcp-2.3.4}/examples/smart_home/src/smart_home/lights/server.py +0 -0
  99. {fastmcp-2.3.3 → fastmcp-2.3.4}/examples/smart_home/src/smart_home/py.typed +0 -0
  100. {fastmcp-2.3.3 → fastmcp-2.3.4}/examples/smart_home/src/smart_home/settings.py +0 -0
  101. {fastmcp-2.3.3 → fastmcp-2.3.4}/examples/smart_home/uv.lock +0 -0
  102. {fastmcp-2.3.3 → fastmcp-2.3.4}/examples/text_me.py +0 -0
  103. {fastmcp-2.3.3 → fastmcp-2.3.4}/justfile +0 -0
  104. {fastmcp-2.3.3 → fastmcp-2.3.4}/src/fastmcp/__init__.py +0 -0
  105. {fastmcp-2.3.3 → fastmcp-2.3.4}/src/fastmcp/cli/__init__.py +0 -0
  106. {fastmcp-2.3.3 → fastmcp-2.3.4}/src/fastmcp/cli/claude.py +0 -0
  107. {fastmcp-2.3.3 → fastmcp-2.3.4}/src/fastmcp/cli/cli.py +0 -0
  108. {fastmcp-2.3.3 → fastmcp-2.3.4}/src/fastmcp/client/base.py +0 -0
  109. {fastmcp-2.3.3 → fastmcp-2.3.4}/src/fastmcp/client/logging.py +0 -0
  110. {fastmcp-2.3.3 → fastmcp-2.3.4}/src/fastmcp/client/roots.py +0 -0
  111. {fastmcp-2.3.3 → fastmcp-2.3.4}/src/fastmcp/client/sampling.py +0 -0
  112. {fastmcp-2.3.3 → fastmcp-2.3.4}/src/fastmcp/contrib/README.md +0 -0
  113. {fastmcp-2.3.3 → fastmcp-2.3.4}/src/fastmcp/contrib/bulk_tool_caller/README.md +0 -0
  114. {fastmcp-2.3.3 → fastmcp-2.3.4}/src/fastmcp/contrib/bulk_tool_caller/__init__.py +0 -0
  115. {fastmcp-2.3.3 → fastmcp-2.3.4}/src/fastmcp/contrib/bulk_tool_caller/bulk_tool_caller.py +0 -0
  116. {fastmcp-2.3.3 → fastmcp-2.3.4}/src/fastmcp/contrib/bulk_tool_caller/example.py +0 -0
  117. {fastmcp-2.3.3 → fastmcp-2.3.4}/src/fastmcp/contrib/mcp_mixin/README.md +0 -0
  118. {fastmcp-2.3.3 → fastmcp-2.3.4}/src/fastmcp/contrib/mcp_mixin/__init__.py +0 -0
  119. {fastmcp-2.3.3 → fastmcp-2.3.4}/src/fastmcp/contrib/mcp_mixin/example.py +0 -0
  120. {fastmcp-2.3.3 → fastmcp-2.3.4}/src/fastmcp/contrib/mcp_mixin/mcp_mixin.py +0 -0
  121. {fastmcp-2.3.3 → fastmcp-2.3.4}/src/fastmcp/low_level/README.md +0 -0
  122. {fastmcp-2.3.3 → fastmcp-2.3.4}/src/fastmcp/low_level/__init__.py +0 -0
  123. {fastmcp-2.3.3 → fastmcp-2.3.4}/src/fastmcp/low_level/sse_server_transport.py +0 -0
  124. {fastmcp-2.3.3 → fastmcp-2.3.4}/src/fastmcp/prompts/__init__.py +0 -0
  125. {fastmcp-2.3.3 → fastmcp-2.3.4}/src/fastmcp/prompts/prompt_manager.py +0 -0
  126. {fastmcp-2.3.3 → fastmcp-2.3.4}/src/fastmcp/py.typed +0 -0
  127. {fastmcp-2.3.3 → fastmcp-2.3.4}/src/fastmcp/resources/__init__.py +0 -0
  128. {fastmcp-2.3.3 → fastmcp-2.3.4}/src/fastmcp/resources/resource.py +0 -0
  129. {fastmcp-2.3.3 → fastmcp-2.3.4}/src/fastmcp/server/__init__.py +0 -0
  130. {fastmcp-2.3.3 → fastmcp-2.3.4}/src/fastmcp/server/context.py +0 -0
  131. {fastmcp-2.3.3 → fastmcp-2.3.4}/src/fastmcp/server/dependencies.py +0 -0
  132. {fastmcp-2.3.3 → fastmcp-2.3.4}/src/fastmcp/server/http.py +0 -0
  133. {fastmcp-2.3.3 → fastmcp-2.3.4}/src/fastmcp/tools/__init__.py +0 -0
  134. {fastmcp-2.3.3 → fastmcp-2.3.4}/src/fastmcp/utilities/__init__.py +0 -0
  135. {fastmcp-2.3.3 → fastmcp-2.3.4}/src/fastmcp/utilities/cache.py +0 -0
  136. {fastmcp-2.3.3 → fastmcp-2.3.4}/src/fastmcp/utilities/decorators.py +0 -0
  137. {fastmcp-2.3.3 → fastmcp-2.3.4}/src/fastmcp/utilities/tests.py +0 -0
  138. {fastmcp-2.3.3 → fastmcp-2.3.4}/src/fastmcp/utilities/types.py +0 -0
  139. {fastmcp-2.3.3 → fastmcp-2.3.4}/tests/__init__.py +0 -0
  140. {fastmcp-2.3.3 → fastmcp-2.3.4}/tests/cli/test_cli.py +0 -0
  141. {fastmcp-2.3.3 → fastmcp-2.3.4}/tests/cli/test_run.py +0 -0
  142. {fastmcp-2.3.3 → fastmcp-2.3.4}/tests/client/__init__.py +0 -0
  143. {fastmcp-2.3.3 → fastmcp-2.3.4}/tests/client/test_logs.py +0 -0
  144. {fastmcp-2.3.3 → fastmcp-2.3.4}/tests/client/test_roots.py +0 -0
  145. {fastmcp-2.3.3 → fastmcp-2.3.4}/tests/client/test_sampling.py +0 -0
  146. {fastmcp-2.3.3 → fastmcp-2.3.4}/tests/conftest.py +0 -0
  147. {fastmcp-2.3.3 → fastmcp-2.3.4}/tests/contrib/__init__.py +0 -0
  148. {fastmcp-2.3.3 → fastmcp-2.3.4}/tests/contrib/test_mcp_mixin.py +0 -0
  149. {fastmcp-2.3.3 → fastmcp-2.3.4}/tests/prompts/__init__.py +0 -0
  150. {fastmcp-2.3.3 → fastmcp-2.3.4}/tests/prompts/test_prompt.py +0 -0
  151. {fastmcp-2.3.3 → fastmcp-2.3.4}/tests/prompts/test_prompt_manager.py +0 -0
  152. {fastmcp-2.3.3 → fastmcp-2.3.4}/tests/resources/__init__.py +0 -0
  153. {fastmcp-2.3.3 → fastmcp-2.3.4}/tests/resources/test_resources.py +0 -0
  154. {fastmcp-2.3.3 → fastmcp-2.3.4}/tests/server/__init__.py +0 -0
  155. {fastmcp-2.3.3 → fastmcp-2.3.4}/tests/server/test_auth_integration.py +0 -0
  156. {fastmcp-2.3.3 → fastmcp-2.3.4}/tests/server/test_context.py +0 -0
  157. {fastmcp-2.3.3 → fastmcp-2.3.4}/tests/server/test_file_server.py +0 -0
  158. {fastmcp-2.3.3 → fastmcp-2.3.4}/tests/server/test_http_dependencies.py +0 -0
  159. {fastmcp-2.3.3 → fastmcp-2.3.4}/tests/server/test_http_middleware.py +0 -0
  160. {fastmcp-2.3.3 → fastmcp-2.3.4}/tests/server/test_import_server.py +0 -0
  161. {fastmcp-2.3.3 → fastmcp-2.3.4}/tests/server/test_lifespan.py +0 -0
  162. {fastmcp-2.3.3 → fastmcp-2.3.4}/tests/server/test_mount.py +0 -0
  163. {fastmcp-2.3.3 → fastmcp-2.3.4}/tests/server/test_run_server.py +0 -0
  164. {fastmcp-2.3.3 → fastmcp-2.3.4}/tests/server/test_tool_annotations.py +0 -0
  165. {fastmcp-2.3.3 → fastmcp-2.3.4}/tests/test_examples.py +0 -0
  166. {fastmcp-2.3.3 → fastmcp-2.3.4}/tests/test_servers/fastmcp_server.py +0 -0
  167. {fastmcp-2.3.3 → fastmcp-2.3.4}/tests/test_servers/sse.py +0 -0
  168. {fastmcp-2.3.3 → fastmcp-2.3.4}/tests/test_servers/stdio.py +0 -0
  169. {fastmcp-2.3.3 → fastmcp-2.3.4}/tests/tools/__init__.py +0 -0
  170. {fastmcp-2.3.3 → fastmcp-2.3.4}/tests/utilities/__init__.py +0 -0
  171. {fastmcp-2.3.3 → fastmcp-2.3.4}/tests/utilities/openapi/__init__.py +0 -0
  172. {fastmcp-2.3.3 → fastmcp-2.3.4}/tests/utilities/openapi/conftest.py +0 -0
  173. {fastmcp-2.3.3 → fastmcp-2.3.4}/tests/utilities/openapi/test_openapi.py +0 -0
  174. {fastmcp-2.3.3 → fastmcp-2.3.4}/tests/utilities/openapi/test_openapi_advanced.py +0 -0
  175. {fastmcp-2.3.3 → fastmcp-2.3.4}/tests/utilities/test_cache.py +0 -0
  176. {fastmcp-2.3.3 → fastmcp-2.3.4}/tests/utilities/test_decorated_function.py +0 -0
  177. {fastmcp-2.3.3 → fastmcp-2.3.4}/tests/utilities/test_logging.py +0 -0
  178. {fastmcp-2.3.3 → fastmcp-2.3.4}/tests/utilities/test_tests.py +0 -0
  179. {fastmcp-2.3.3 → 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 --frozen pytest
54
+ run: uv run pytest
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fastmcp
3
- Version: 2.3.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.0
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
 
@@ -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
- **How Templates Work:**
407
+ Templates provide a powerful way to expose parameterized data access points following REST-like principles.
408
+
409
+ ## Error Handling
407
410
 
408
- 1. **Definition:** When FastMCP sees `{...}` placeholders in the `@resource` URI and matching function parameters, it registers a `ResourceTemplate`.
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
- Templates provide a powerful way to expose parameterized data access points following REST-like principles.
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
- If your tool encounters an error, simply raise a standard Python exception (`ValueError`, `TypeError`, `FileNotFoundError`, custom exceptions, etc.).
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
- if b == 0:
258
- # Raise a standard exception
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" />
@@ -7,7 +7,7 @@ dependencies = [
7
7
  "python-dotenv>=1.1.0",
8
8
  "exceptiongroup>=1.2.2",
9
9
  "httpx>=0.28.1",
10
- "mcp>=1.8.0,<2.0.0",
10
+ "mcp>=1.8.1,<2.0.0",
11
11
  "openapi-pydantic>=0.5.1",
12
12
  "rich>=13.9.4",
13
13
  "typer>=0.15.2",
@@ -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
  ]
@@ -1,9 +1,10 @@
1
1
  import datetime
2
- from contextlib import AbstractAsyncContextManager
2
+ from contextlib import AsyncExitStack
3
3
  from pathlib import Path
4
4
  from typing import Any, cast
5
5
 
6
6
  import mcp.types
7
+ from exceptiongroup import catch
7
8
  from mcp import ClientSession
8
9
  from pydantic import AnyUrl
9
10
 
@@ -14,8 +15,9 @@ from fastmcp.client.roots import (
14
15
  create_roots_callback,
15
16
  )
16
17
  from fastmcp.client.sampling import SamplingHandler, create_sampling_callback
17
- from fastmcp.exceptions import ClientError
18
+ from fastmcp.exceptions import ToolError
18
19
  from fastmcp.server import FastMCP
20
+ from fastmcp.utilities.exceptions import get_catch_handlers
19
21
 
20
22
  from .transports import ClientTransport, SessionKwargs, infer_transport
21
23
 
@@ -33,8 +35,35 @@ class Client:
33
35
  """
34
36
  MCP client that delegates connection management to a Transport instance.
35
37
 
36
- The Client class is primarily concerned with MCP protocol logic,
37
- while the Transport handles connection establishment and management.
38
+ The Client class is responsible for MCP protocol logic, while the Transport
39
+ handles connection establishment and management. Client provides methods
40
+ for working with resources, prompts, tools and other MCP capabilities.
41
+
42
+ Args:
43
+ transport: Connection source specification, which can be:
44
+ - ClientTransport: Direct transport instance
45
+ - FastMCP: In-process FastMCP server
46
+ - AnyUrl | str: URL to connect to
47
+ - Path: File path for local socket
48
+ - dict: Transport configuration
49
+ roots: Optional RootsList or RootsHandler for filesystem access
50
+ sampling_handler: Optional handler for sampling requests
51
+ log_handler: Optional handler for log messages
52
+ message_handler: Optional handler for protocol messages
53
+ timeout: Optional timeout for requests (seconds or timedelta)
54
+
55
+ Examples:
56
+ ```python
57
+ # Connect to FastMCP server
58
+ client = Client("http://localhost:8080")
59
+
60
+ async with client:
61
+ # List available resources
62
+ resources = await client.list_resources()
63
+
64
+ # Call a tool
65
+ result = await client.call_tool("my_tool", {"param": "value"})
66
+ ```
38
67
  """
39
68
 
40
69
  def __init__(
@@ -45,19 +74,22 @@ class Client:
45
74
  sampling_handler: SamplingHandler | None = None,
46
75
  log_handler: LogHandler | None = None,
47
76
  message_handler: MessageHandler | None = None,
48
- read_timeout_seconds: datetime.timedelta | None = None,
77
+ timeout: datetime.timedelta | float | int | None = None,
49
78
  ):
50
79
  self.transport = infer_transport(transport)
51
80
  self._session: ClientSession | None = None
52
- self._session_cm: AbstractAsyncContextManager[ClientSession] | None = None
81
+ self._exit_stack: AsyncExitStack | None = None
53
82
  self._nesting_counter: int = 0
54
83
 
84
+ if isinstance(timeout, int | float):
85
+ timeout = datetime.timedelta(seconds=timeout)
86
+
55
87
  self._session_kwargs: SessionKwargs = {
56
88
  "sampling_callback": None,
57
89
  "list_roots_callback": None,
58
90
  "logging_callback": log_handler,
59
91
  "message_handler": message_handler,
60
- "read_timeout_seconds": read_timeout_seconds,
92
+ "read_timeout_seconds": timeout,
61
93
  }
62
94
 
63
95
  if roots is not None:
@@ -91,9 +123,23 @@ class Client:
91
123
 
92
124
  async def __aenter__(self):
93
125
  if self._nesting_counter == 0:
94
- # create new session
95
- self._session_cm = self.transport.connect_session(**self._session_kwargs)
96
- self._session = await self._session_cm.__aenter__()
126
+ # Create exit stack to manage both context managers
127
+ stack = AsyncExitStack()
128
+ await stack.__aenter__()
129
+
130
+ # Add the exception handling context
131
+ stack.enter_context(catch(get_catch_handlers()))
132
+
133
+ # the above catch will only apply once this __aenter__ finishes so
134
+ # we need to wrap the session creation in a new context in case it
135
+ # raises errors itself
136
+ with catch(get_catch_handlers()):
137
+ # Create and enter the transport session using the exit stack
138
+ session_cm = self.transport.connect_session(**self._session_kwargs)
139
+ self._session = await stack.enter_async_context(session_cm)
140
+
141
+ # Store the stack for cleanup in __aexit__
142
+ self._exit_stack = stack
97
143
 
98
144
  self._nesting_counter += 1
99
145
  return self
@@ -101,10 +147,14 @@ class Client:
101
147
  async def __aexit__(self, exc_type, exc_val, exc_tb):
102
148
  self._nesting_counter -= 1
103
149
 
104
- if self._nesting_counter == 0 and self._session_cm is not None:
105
- await self._session_cm.__aexit__(exc_type, exc_val, exc_tb)
106
- self._session_cm = None
107
- self._session = None
150
+ if self._nesting_counter == 0:
151
+ # Exit the stack which will handle cleaning up the session
152
+ if self._exit_stack is not None:
153
+ try:
154
+ await self._exit_stack.__aexit__(exc_type, exc_val, exc_tb)
155
+ finally:
156
+ self._exit_stack = None
157
+ self._session = None
108
158
 
109
159
  # --- MCP Client Methods ---
110
160
 
@@ -377,7 +427,10 @@ class Client:
377
427
  # --- Call Tool ---
378
428
 
379
429
  async def call_tool_mcp(
380
- self, name: str, arguments: dict[str, Any]
430
+ self,
431
+ name: str,
432
+ arguments: dict[str, Any],
433
+ timeout: datetime.timedelta | float | int | None = None,
381
434
  ) -> mcp.types.CallToolResult:
382
435
  """Send a tools/call request and return the complete MCP protocol result.
383
436
 
@@ -387,7 +440,7 @@ class Client:
387
440
  Args:
388
441
  name (str): The name of the tool to call.
389
442
  arguments (dict[str, Any]): Arguments to pass to the tool.
390
-
443
+ timeout (datetime.timedelta | float | int | None, optional): The timeout for the tool call. Defaults to None.
391
444
  Returns:
392
445
  mcp.types.CallToolResult: The complete response object from the protocol,
393
446
  containing the tool result and any additional metadata.
@@ -395,19 +448,25 @@ class Client:
395
448
  Raises:
396
449
  RuntimeError: If called while the client is not connected.
397
450
  """
398
- result = await self.session.call_tool(name=name, arguments=arguments)
451
+
452
+ if isinstance(timeout, int | float):
453
+ timeout = datetime.timedelta(seconds=timeout)
454
+ result = await self.session.call_tool(
455
+ name=name, arguments=arguments, read_timeout_seconds=timeout
456
+ )
399
457
  return result
400
458
 
401
459
  async def call_tool(
402
460
  self,
403
461
  name: str,
404
462
  arguments: dict[str, Any] | None = None,
463
+ timeout: datetime.timedelta | float | int | None = None,
405
464
  ) -> list[
406
465
  mcp.types.TextContent | mcp.types.ImageContent | mcp.types.EmbeddedResource
407
466
  ]:
408
467
  """Call a tool on the server.
409
468
 
410
- Unlike call_tool_mcp, this method raises a ClientError if the tool call results in an error.
469
+ Unlike call_tool_mcp, this method raises a ToolError if the tool call results in an error.
411
470
 
412
471
  Args:
413
472
  name (str): The name of the tool to call.
@@ -418,11 +477,15 @@ class Client:
418
477
  The content returned by the tool.
419
478
 
420
479
  Raises:
421
- ClientError: If the tool call results in an error.
480
+ ToolError: If the tool call results in an error.
422
481
  RuntimeError: If called while the client is not connected.
423
482
  """
424
- result = await self.call_tool_mcp(name=name, arguments=arguments or {})
483
+ result = await self.call_tool_mcp(
484
+ name=name,
485
+ arguments=arguments or {},
486
+ timeout=timeout,
487
+ )
425
488
  if result.isError:
426
489
  msg = cast(mcp.types.TextContent, result.content[0]).text
427
- raise ClientError(msg)
490
+ raise ToolError(msg)
428
491
  return result.content
@@ -8,10 +8,9 @@ import sys
8
8
  import warnings
9
9
  from collections.abc import AsyncIterator
10
10
  from pathlib import Path
11
- from typing import Any, TypedDict
11
+ from typing import Any, TypedDict, cast
12
12
 
13
- from exceptiongroup import BaseExceptionGroup, catch
14
- from mcp import ClientSession, McpError, StdioServerParameters
13
+ from mcp import ClientSession, StdioServerParameters
15
14
  from mcp.client.session import (
16
15
  ListRootsFnT,
17
16
  LoggingFnT,
@@ -26,7 +25,6 @@ from mcp.shared.memory import create_connected_server_and_client_session
26
25
  from pydantic import AnyUrl
27
26
  from typing_extensions import Unpack
28
27
 
29
- from fastmcp.exceptions import ClientError
30
28
  from fastmcp.server import FastMCP as FastMCPServer
31
29
 
32
30
 
@@ -104,7 +102,12 @@ class WSTransport(ClientTransport):
104
102
  class SSETransport(ClientTransport):
105
103
  """Transport implementation that connects to an MCP server via Server-Sent Events."""
106
104
 
107
- def __init__(self, url: str | AnyUrl, headers: dict[str, str] | None = None):
105
+ def __init__(
106
+ self,
107
+ url: str | AnyUrl,
108
+ headers: dict[str, str] | None = None,
109
+ sse_read_timeout: datetime.timedelta | float | int | None = None,
110
+ ):
108
111
  if isinstance(url, AnyUrl):
109
112
  url = str(url)
110
113
  if not isinstance(url, str) or not url.startswith("http"):
@@ -112,11 +115,28 @@ class SSETransport(ClientTransport):
112
115
  self.url = url
113
116
  self.headers = headers or {}
114
117
 
118
+ if isinstance(sse_read_timeout, int | float):
119
+ sse_read_timeout = datetime.timedelta(seconds=sse_read_timeout)
120
+ self.sse_read_timeout = sse_read_timeout
121
+
115
122
  @contextlib.asynccontextmanager
116
123
  async def connect_session(
117
124
  self, **session_kwargs: Unpack[SessionKwargs]
118
125
  ) -> AsyncIterator[ClientSession]:
119
- async with sse_client(self.url, headers=self.headers) as transport:
126
+ client_kwargs = {}
127
+ # sse_read_timeout has a default value set, so we can't pass None without overriding it
128
+ # instead we simply leave the kwarg out if it's not provided
129
+ if self.sse_read_timeout is not None:
130
+ client_kwargs["sse_read_timeout"] = self.sse_read_timeout.total_seconds()
131
+ if session_kwargs.get("read_timeout_seconds", None) is not None:
132
+ read_timeout_seconds = cast(
133
+ datetime.timedelta, session_kwargs.get("read_timeout_seconds")
134
+ )
135
+ client_kwargs["timeout"] = read_timeout_seconds.total_seconds()
136
+
137
+ async with sse_client(
138
+ self.url, headers=self.headers, **client_kwargs
139
+ ) as transport:
120
140
  read_stream, write_stream = transport
121
141
  async with ClientSession(
122
142
  read_stream, write_stream, **session_kwargs
@@ -131,7 +151,12 @@ class SSETransport(ClientTransport):
131
151
  class StreamableHttpTransport(ClientTransport):
132
152
  """Transport implementation that connects to an MCP server via Streamable HTTP Requests."""
133
153
 
134
- def __init__(self, url: str | AnyUrl, headers: dict[str, str] | None = None):
154
+ def __init__(
155
+ self,
156
+ url: str | AnyUrl,
157
+ headers: dict[str, str] | None = None,
158
+ sse_read_timeout: datetime.timedelta | float | int | None = None,
159
+ ):
135
160
  if isinstance(url, AnyUrl):
136
161
  url = str(url)
137
162
  if not isinstance(url, str) or not url.startswith("http"):
@@ -139,11 +164,25 @@ class StreamableHttpTransport(ClientTransport):
139
164
  self.url = url
140
165
  self.headers = headers or {}
141
166
 
167
+ if isinstance(sse_read_timeout, int | float):
168
+ sse_read_timeout = datetime.timedelta(seconds=sse_read_timeout)
169
+ self.sse_read_timeout = sse_read_timeout
170
+
142
171
  @contextlib.asynccontextmanager
143
172
  async def connect_session(
144
173
  self, **session_kwargs: Unpack[SessionKwargs]
145
174
  ) -> AsyncIterator[ClientSession]:
146
- async with streamablehttp_client(self.url, headers=self.headers) as transport:
175
+ client_kwargs = {}
176
+ # sse_read_timeout has a default value set, so we can't pass None without overriding it
177
+ # instead we simply leave the kwarg out if it's not provided
178
+ if self.sse_read_timeout is not None:
179
+ client_kwargs["sse_read_timeout"] = self.sse_read_timeout
180
+ if session_kwargs.get("read_timeout_seconds", None) is not None:
181
+ client_kwargs["timeout"] = session_kwargs.get("read_timeout_seconds")
182
+
183
+ async with streamablehttp_client(
184
+ self.url, headers=self.headers, **client_kwargs
185
+ ) as transport:
147
186
  read_stream, write_stream, _ = transport
148
187
  async with ClientSession(
149
188
  read_stream, write_stream, **session_kwargs
@@ -418,26 +457,12 @@ class FastMCPTransport(ClientTransport):
418
457
  async def connect_session(
419
458
  self, **session_kwargs: Unpack[SessionKwargs]
420
459
  ) -> AsyncIterator[ClientSession]:
421
- def exception_handler(excgroup: BaseExceptionGroup):
422
- for exc in excgroup.exceptions:
423
- if isinstance(exc, BaseExceptionGroup):
424
- exception_handler(exc)
425
- raise exc
426
-
427
- def mcperror_handler(excgroup: BaseExceptionGroup):
428
- for exc in excgroup.exceptions:
429
- if isinstance(exc, BaseExceptionGroup):
430
- mcperror_handler(exc)
431
- raise ClientError(exc)
432
-
433
- # backport of 3.11's except* syntax
434
- with catch({McpError: mcperror_handler, Exception: exception_handler}):
435
- # create_connected_server_and_client_session manages the session lifecycle itself
436
- async with create_connected_server_and_client_session(
437
- server=self._fastmcp._mcp_server,
438
- **session_kwargs,
439
- ) as session:
440
- yield session
460
+ # create_connected_server_and_client_session manages the session lifecycle itself
461
+ async with create_connected_server_and_client_session(
462
+ server=self._fastmcp._mcp_server,
463
+ **session_kwargs,
464
+ ) as session:
465
+ yield session
441
466
 
442
467
  def __repr__(self) -> str:
443
468
  return f"<FastMCP(server='{self._fastmcp.name}')>"
@@ -1,5 +1,7 @@
1
1
  """Custom exceptions for FastMCP."""
2
2
 
3
+ from mcp import McpError # noqa: F401
4
+
3
5
 
4
6
  class FastMCPError(Exception):
5
7
  """Base error for FastMCP."""
@@ -13,7 +13,8 @@ from mcp.types import PromptArgument as MCPPromptArgument
13
13
  from pydantic import BaseModel, BeforeValidator, Field, TypeAdapter, validate_call
14
14
 
15
15
  from fastmcp.server.dependencies import get_context
16
- from fastmcp.utilities.json_schema import prune_params
16
+ from fastmcp.utilities.json_schema import compress_schema
17
+ from fastmcp.utilities.logging import get_logger
17
18
  from fastmcp.utilities.types import (
18
19
  _convert_set_defaults,
19
20
  find_kwarg_by_type,
@@ -25,6 +26,8 @@ if TYPE_CHECKING:
25
26
 
26
27
  CONTENT_TYPES = TextContent | ImageContent | EmbeddedResource
27
28
 
29
+ logger = get_logger(__name__)
30
+
28
31
 
29
32
  def Message(
30
33
  content: str | CONTENT_TYPES, role: Role | None = None, **kwargs: Any
@@ -112,7 +115,11 @@ class Prompt(BaseModel):
112
115
 
113
116
  context_kwarg = find_kwarg_by_type(fn, kwarg_type=Context)
114
117
  if context_kwarg:
115
- parameters = prune_params(parameters, params=[context_kwarg])
118
+ prune_params = [context_kwarg]
119
+ else:
120
+ prune_params = None
121
+
122
+ parameters = compress_schema(parameters, prune_params=prune_params)
116
123
 
117
124
  # Convert parameters to PromptArguments
118
125
  arguments: list[PromptArgument] = []
@@ -192,13 +199,12 @@ class Prompt(BaseModel):
192
199
  )
193
200
  )
194
201
  except Exception:
195
- raise ValueError(
196
- f"Could not convert prompt result to message: {msg}"
197
- )
202
+ raise ValueError("Could not convert prompt result to message.")
198
203
 
199
204
  return messages
200
205
  except Exception as e:
201
- raise ValueError(f"Error rendering prompt {self.name}: {e}")
206
+ logger.exception(f"Error rendering prompt {self.name}: {e}")
207
+ raise ValueError(f"Error rendering prompt {self.name}.")
202
208
 
203
209
  def __eq__(self, other: object) -> bool:
204
210
  if not isinstance(other, Prompt):