From 26969e80c3302934e05f5853ea8836607a3f557b Mon Sep 17 00:00:00 2001 From: Jeremiah Lowin <153965+jlowin@users.noreply.github.com> Date: Mon, 18 Aug 2025 13:42:33 -0400 Subject: [PATCH] Add mounted_components_raise_on_load_error setting for debugging (#1534) Co-authored-by: Jeremiah Lowin Co-authored-by: marvin-context-protocol[bot] <225465937+marvin-context-protocol[bot]@users.noreply.github.com> --- src/fastmcp/prompts/prompt_manager.py | 2 ++ src/fastmcp/resources/resource_manager.py | 4 +++ src/fastmcp/settings.py | 14 ++++++++ src/fastmcp/tools/tool_manager.py | 2 ++ tests/tools/test_tool_manager.py | 41 ++++++++++++++++++++++- 5 files changed, 62 insertions(+), 1 deletion(-) diff --git a/src/fastmcp/prompts/prompt_manager.py b/src/fastmcp/prompts/prompt_manager.py index a7fb12159..ab1a944e2 100644 --- a/src/fastmcp/prompts/prompt_manager.py +++ b/src/fastmcp/prompts/prompt_manager.py @@ -80,6 +80,8 @@ class PromptManager: logger.warning( f"Failed to get prompts from server: {mounted.server.name!r}, mounted at: {mounted.prefix!r}: {e}" ) + if settings.mounted_components_raise_on_load_error: + raise continue # Finally, add local prompts, which always take precedence diff --git a/src/fastmcp/resources/resource_manager.py b/src/fastmcp/resources/resource_manager.py index 7e031a414..c646c71aa 100644 --- a/src/fastmcp/resources/resource_manager.py +++ b/src/fastmcp/resources/resource_manager.py @@ -114,6 +114,8 @@ class ResourceManager: logger.warning( f"Failed to get resources from server: {mounted.server.name!r}, mounted at: {mounted.prefix!r}: {e}" ) + if settings.mounted_components_raise_on_load_error: + raise continue # Finally, add local resources, which always take precedence @@ -165,6 +167,8 @@ class ResourceManager: logger.warning( f"Failed to get templates from server: {mounted.server.name!r}, mounted at: {mounted.prefix!r}: {e}" ) + if settings.mounted_components_raise_on_load_error: + raise continue # Finally, add local templates, which always take precedence diff --git a/src/fastmcp/settings.py b/src/fastmcp/settings.py index ae6adff94..1ecda5a08 100644 --- a/src/fastmcp/settings.py +++ b/src/fastmcp/settings.py @@ -320,6 +320,20 @@ class Settings(BaseSettings): ), ] = True + mounted_components_raise_on_load_error: Annotated[ + bool, + Field( + default=False, + description=inspect.cleandoc( + """ + If True, errors encountered when loading mounted components (tools, resources, prompts) + will be raised instead of logged as warnings. This is useful for debugging + but will interrupt normal operation. + """ + ), + ), + ] = False + def __getattr__(name: str): """ diff --git a/src/fastmcp/tools/tool_manager.py b/src/fastmcp/tools/tool_manager.py index b460c2b22..a40a9877c 100644 --- a/src/fastmcp/tools/tool_manager.py +++ b/src/fastmcp/tools/tool_manager.py @@ -86,6 +86,8 @@ class ToolManager: logger.warning( f"Failed to get tools from server: {mounted.server.name!r}, mounted at: {mounted.prefix!r}: {e}" ) + if settings.mounted_components_raise_on_load_error: + raise continue # Finally, add local tools, which always take precedence diff --git a/tests/tools/test_tool_manager.py b/tests/tools/test_tool_manager.py index 13989276f..0707cf7f3 100644 --- a/tests/tools/test_tool_manager.py +++ b/tests/tools/test_tool_manager.py @@ -13,7 +13,7 @@ from fastmcp.exceptions import NotFoundError, ToolError from fastmcp.tools import FunctionTool, ToolManager from fastmcp.tools.tool import Tool from fastmcp.tools.tool_transform import ArgTransformConfig, ToolTransformConfig -from fastmcp.utilities.tests import caplog_for_fastmcp +from fastmcp.utilities.tests import caplog_for_fastmcp, temporary_settings from fastmcp.utilities.types import Image @@ -996,3 +996,42 @@ class TestToolErrorHandling: # Exception message should contain the tool name but not the internal details assert "Error calling tool 'async_buggy_tool'" in str(excinfo.value) assert "Internal async error details" not in str(excinfo.value) + + +class TestMountedComponentsRaiseOnLoadError: + """Test the mounted_components_raise_on_load_error setting.""" + + async def test_mounted_components_raise_on_load_error_default_false(self): + """Test that by default, mounted component load errors are warned and not raised.""" + import fastmcp + + # Ensure default setting is False + assert fastmcp.settings.mounted_components_raise_on_load_error is False + + parent_mcp = FastMCP("ParentServer") + child_mcp = FastMCP("FailingChildServer") + + # Create a failing mounted server by corrupting it + parent_mcp.mount(child_mcp, prefix="child") + # Corrupt the child server to make it fail during tool loading + child_mcp._tool_manager._mounted_servers.append("invalid") # type: ignore + + # Should not raise, just warn + tools = await parent_mcp._tool_manager.list_tools() + assert isinstance(tools, list) # Should return empty list, not raise + + async def test_mounted_components_raise_on_load_error_true(self): + """Test that when enabled, mounted component load errors are raised.""" + parent_mcp = FastMCP("ParentServer") + child_mcp = FastMCP("FailingChildServer") + + # Create a failing mounted server + parent_mcp.mount(child_mcp, prefix="child") + # Corrupt the child server to make it fail during tool loading + child_mcp._tool_manager._mounted_servers.append("invalid") # type: ignore + + # Use temporary settings context manager + with temporary_settings(mounted_components_raise_on_load_error=True): + # Should raise the exception + with pytest.raises(AttributeError, match=""): + await parent_mcp._tool_manager.list_tools()