Skip to content

Commit 26969e8

Browse files
Add mounted_components_raise_on_load_error setting for debugging (#1534)
Co-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>
1 parent ec015de commit 26969e8

5 files changed

Lines changed: 62 additions & 1 deletion

File tree

src/fastmcp/prompts/prompt_manager.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,8 @@ async def _load_prompts(self, *, via_server: bool = False) -> dict[str, Prompt]:
8080
logger.warning(
8181
f"Failed to get prompts from server: {mounted.server.name!r}, mounted at: {mounted.prefix!r}: {e}"
8282
)
83+
if settings.mounted_components_raise_on_load_error:
84+
raise
8385
continue
8486

8587
# Finally, add local prompts, which always take precedence

src/fastmcp/resources/resource_manager.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,8 @@ async def _load_resources(self, *, via_server: bool = False) -> dict[str, Resour
114114
logger.warning(
115115
f"Failed to get resources from server: {mounted.server.name!r}, mounted at: {mounted.prefix!r}: {e}"
116116
)
117+
if settings.mounted_components_raise_on_load_error:
118+
raise
117119
continue
118120

119121
# Finally, add local resources, which always take precedence
@@ -165,6 +167,8 @@ async def _load_resource_templates(
165167
logger.warning(
166168
f"Failed to get templates from server: {mounted.server.name!r}, mounted at: {mounted.prefix!r}: {e}"
167169
)
170+
if settings.mounted_components_raise_on_load_error:
171+
raise
168172
continue
169173

170174
# Finally, add local templates, which always take precedence

src/fastmcp/settings.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -320,6 +320,20 @@ def normalize_log_level(cls, v):
320320
),
321321
] = True
322322

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+
323337

324338
def __getattr__(name: str):
325339
"""

src/fastmcp/tools/tool_manager.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,8 @@ async def _load_tools(self, *, via_server: bool = False) -> dict[str, Tool]:
8686
logger.warning(
8787
f"Failed to get tools from server: {mounted.server.name!r}, mounted at: {mounted.prefix!r}: {e}"
8888
)
89+
if settings.mounted_components_raise_on_load_error:
90+
raise
8991
continue
9092

9193
# Finally, add local tools, which always take precedence

tests/tools/test_tool_manager.py

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
from fastmcp.tools import FunctionTool, ToolManager
1414
from fastmcp.tools.tool import Tool
1515
from fastmcp.tools.tool_transform import ArgTransformConfig, ToolTransformConfig
16-
from fastmcp.utilities.tests import caplog_for_fastmcp
16+
from fastmcp.utilities.tests import caplog_for_fastmcp, temporary_settings
1717
from fastmcp.utilities.types import Image
1818

1919

@@ -996,3 +996,42 @@ async def async_buggy_tool(x: int) -> int:
996996
# Exception message should contain the tool name but not the internal details
997997
assert "Error calling tool 'async_buggy_tool'" in str(excinfo.value)
998998
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()

0 commit comments

Comments
 (0)