Spaces:
Running
Running
Jeremiah Lowin Jeremiah Lowin marvin-context-protocol[bot] commited on
Add mounted_components_raise_on_load_error setting for debugging (#1534)
Browse filesCo-authored-by: Jeremiah Lowin <jlowin@users.noreply.github.com>
Co-authored-by: marvin-context-protocol[bot] <225465937+marvin-context-protocol[bot]@users.noreply.github.com>
src/fastmcp/prompts/prompt_manager.py
CHANGED
|
@@ -80,6 +80,8 @@ class PromptManager:
|
|
| 80 |
logger.warning(
|
| 81 |
f"Failed to get prompts from server: {mounted.server.name!r}, mounted at: {mounted.prefix!r}: {e}"
|
| 82 |
)
|
|
|
|
|
|
|
| 83 |
continue
|
| 84 |
|
| 85 |
# Finally, add local prompts, which always take precedence
|
|
|
|
| 80 |
logger.warning(
|
| 81 |
f"Failed to get prompts from server: {mounted.server.name!r}, mounted at: {mounted.prefix!r}: {e}"
|
| 82 |
)
|
| 83 |
+
if settings.mounted_components_raise_on_load_error:
|
| 84 |
+
raise
|
| 85 |
continue
|
| 86 |
|
| 87 |
# Finally, add local prompts, which always take precedence
|
src/fastmcp/resources/resource_manager.py
CHANGED
|
@@ -114,6 +114,8 @@ class ResourceManager:
|
|
| 114 |
logger.warning(
|
| 115 |
f"Failed to get resources from server: {mounted.server.name!r}, mounted at: {mounted.prefix!r}: {e}"
|
| 116 |
)
|
|
|
|
|
|
|
| 117 |
continue
|
| 118 |
|
| 119 |
# Finally, add local resources, which always take precedence
|
|
@@ -165,6 +167,8 @@ class ResourceManager:
|
|
| 165 |
logger.warning(
|
| 166 |
f"Failed to get templates from server: {mounted.server.name!r}, mounted at: {mounted.prefix!r}: {e}"
|
| 167 |
)
|
|
|
|
|
|
|
| 168 |
continue
|
| 169 |
|
| 170 |
# Finally, add local templates, which always take precedence
|
|
|
|
| 114 |
logger.warning(
|
| 115 |
f"Failed to get resources from server: {mounted.server.name!r}, mounted at: {mounted.prefix!r}: {e}"
|
| 116 |
)
|
| 117 |
+
if settings.mounted_components_raise_on_load_error:
|
| 118 |
+
raise
|
| 119 |
continue
|
| 120 |
|
| 121 |
# Finally, add local resources, which always take precedence
|
|
|
|
| 167 |
logger.warning(
|
| 168 |
f"Failed to get templates from server: {mounted.server.name!r}, mounted at: {mounted.prefix!r}: {e}"
|
| 169 |
)
|
| 170 |
+
if settings.mounted_components_raise_on_load_error:
|
| 171 |
+
raise
|
| 172 |
continue
|
| 173 |
|
| 174 |
# Finally, add local templates, which always take precedence
|
src/fastmcp/settings.py
CHANGED
|
@@ -320,6 +320,20 @@ class Settings(BaseSettings):
|
|
| 320 |
),
|
| 321 |
] = True
|
| 322 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 323 |
|
| 324 |
def __getattr__(name: str):
|
| 325 |
"""
|
|
|
|
| 320 |
),
|
| 321 |
] = True
|
| 322 |
|
| 323 |
+
mounted_components_raise_on_load_error: Annotated[
|
| 324 |
+
bool,
|
| 325 |
+
Field(
|
| 326 |
+
default=False,
|
| 327 |
+
description=inspect.cleandoc(
|
| 328 |
+
"""
|
| 329 |
+
If True, errors encountered when loading mounted components (tools, resources, prompts)
|
| 330 |
+
will be raised instead of logged as warnings. This is useful for debugging
|
| 331 |
+
but will interrupt normal operation.
|
| 332 |
+
"""
|
| 333 |
+
),
|
| 334 |
+
),
|
| 335 |
+
] = False
|
| 336 |
+
|
| 337 |
|
| 338 |
def __getattr__(name: str):
|
| 339 |
"""
|
src/fastmcp/tools/tool_manager.py
CHANGED
|
@@ -86,6 +86,8 @@ class ToolManager:
|
|
| 86 |
logger.warning(
|
| 87 |
f"Failed to get tools from server: {mounted.server.name!r}, mounted at: {mounted.prefix!r}: {e}"
|
| 88 |
)
|
|
|
|
|
|
|
| 89 |
continue
|
| 90 |
|
| 91 |
# Finally, add local tools, which always take precedence
|
|
|
|
| 86 |
logger.warning(
|
| 87 |
f"Failed to get tools from server: {mounted.server.name!r}, mounted at: {mounted.prefix!r}: {e}"
|
| 88 |
)
|
| 89 |
+
if settings.mounted_components_raise_on_load_error:
|
| 90 |
+
raise
|
| 91 |
continue
|
| 92 |
|
| 93 |
# Finally, add local tools, which always take precedence
|
tests/tools/test_tool_manager.py
CHANGED
|
@@ -13,7 +13,7 @@ from fastmcp.exceptions import NotFoundError, ToolError
|
|
| 13 |
from fastmcp.tools import FunctionTool, ToolManager
|
| 14 |
from fastmcp.tools.tool import Tool
|
| 15 |
from fastmcp.tools.tool_transform import ArgTransformConfig, ToolTransformConfig
|
| 16 |
-
from fastmcp.utilities.tests import caplog_for_fastmcp
|
| 17 |
from fastmcp.utilities.types import Image
|
| 18 |
|
| 19 |
|
|
@@ -996,3 +996,42 @@ class TestToolErrorHandling:
|
|
| 996 |
# Exception message should contain the tool name but not the internal details
|
| 997 |
assert "Error calling tool 'async_buggy_tool'" in str(excinfo.value)
|
| 998 |
assert "Internal async error details" not in str(excinfo.value)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 13 |
from fastmcp.tools import FunctionTool, ToolManager
|
| 14 |
from fastmcp.tools.tool import Tool
|
| 15 |
from fastmcp.tools.tool_transform import ArgTransformConfig, ToolTransformConfig
|
| 16 |
+
from fastmcp.utilities.tests import caplog_for_fastmcp, temporary_settings
|
| 17 |
from fastmcp.utilities.types import Image
|
| 18 |
|
| 19 |
|
|
|
|
| 996 |
# Exception message should contain the tool name but not the internal details
|
| 997 |
assert "Error calling tool 'async_buggy_tool'" in str(excinfo.value)
|
| 998 |
assert "Internal async error details" not in str(excinfo.value)
|
| 999 |
+
|
| 1000 |
+
|
| 1001 |
+
class TestMountedComponentsRaiseOnLoadError:
|
| 1002 |
+
"""Test the mounted_components_raise_on_load_error setting."""
|
| 1003 |
+
|
| 1004 |
+
async def test_mounted_components_raise_on_load_error_default_false(self):
|
| 1005 |
+
"""Test that by default, mounted component load errors are warned and not raised."""
|
| 1006 |
+
import fastmcp
|
| 1007 |
+
|
| 1008 |
+
# Ensure default setting is False
|
| 1009 |
+
assert fastmcp.settings.mounted_components_raise_on_load_error is False
|
| 1010 |
+
|
| 1011 |
+
parent_mcp = FastMCP("ParentServer")
|
| 1012 |
+
child_mcp = FastMCP("FailingChildServer")
|
| 1013 |
+
|
| 1014 |
+
# Create a failing mounted server by corrupting it
|
| 1015 |
+
parent_mcp.mount(child_mcp, prefix="child")
|
| 1016 |
+
# Corrupt the child server to make it fail during tool loading
|
| 1017 |
+
child_mcp._tool_manager._mounted_servers.append("invalid") # type: ignore
|
| 1018 |
+
|
| 1019 |
+
# Should not raise, just warn
|
| 1020 |
+
tools = await parent_mcp._tool_manager.list_tools()
|
| 1021 |
+
assert isinstance(tools, list) # Should return empty list, not raise
|
| 1022 |
+
|
| 1023 |
+
async def test_mounted_components_raise_on_load_error_true(self):
|
| 1024 |
+
"""Test that when enabled, mounted component load errors are raised."""
|
| 1025 |
+
parent_mcp = FastMCP("ParentServer")
|
| 1026 |
+
child_mcp = FastMCP("FailingChildServer")
|
| 1027 |
+
|
| 1028 |
+
# Create a failing mounted server
|
| 1029 |
+
parent_mcp.mount(child_mcp, prefix="child")
|
| 1030 |
+
# Corrupt the child server to make it fail during tool loading
|
| 1031 |
+
child_mcp._tool_manager._mounted_servers.append("invalid") # type: ignore
|
| 1032 |
+
|
| 1033 |
+
# Use temporary settings context manager
|
| 1034 |
+
with temporary_settings(mounted_components_raise_on_load_error=True):
|
| 1035 |
+
# Should raise the exception
|
| 1036 |
+
with pytest.raises(AttributeError, match=""):
|
| 1037 |
+
await parent_mcp._tool_manager.list_tools()
|