mirror of
https://github.com/PrefectHQ/fastmcp.git
synced 2026-08-20 12:34:17 +02:00
Let FastMCPError propagate from dependencies
When a dependency raises ToolError or other FastMCPError subclasses, they were getting wrapped in RuntimeError with a generic "Failed to resolve dependency" message. This made it hard to use ToolError for validation in dependencies. Now FastMCPError subclasses propagate unchanged, matching the pattern used elsewhere in the codebase. Closes #2633 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
ede8ff6703
commit
63bc9cb921
2 changed files with 49 additions and 0 deletions
|
|
@ -21,6 +21,7 @@ from mcp.server.auth.provider import (
|
|||
from mcp.server.lowlevel.server import request_ctx
|
||||
from starlette.requests import Request
|
||||
|
||||
from fastmcp.exceptions import FastMCPError
|
||||
from fastmcp.server.auth import AccessToken
|
||||
from fastmcp.server.http import _current_http_request
|
||||
from fastmcp.utilities.types import is_class_member_of_type
|
||||
|
|
@ -188,6 +189,10 @@ async def _resolve_fastmcp_dependencies(
|
|||
resolved[parameter] = await stack.enter_async_context(
|
||||
dependency
|
||||
)
|
||||
except FastMCPError:
|
||||
# Let FastMCPError subclasses (ToolError, ResourceError, etc.)
|
||||
# propagate unchanged so they can be handled appropriately
|
||||
raise
|
||||
except Exception as error:
|
||||
fn_name = getattr(fn, "__name__", repr(fn))
|
||||
raise RuntimeError(
|
||||
|
|
|
|||
|
|
@ -733,3 +733,47 @@ async def test_resource_template_uri_cannot_match_dependency_name(mcp: FastMCP):
|
|||
@mcp.resource("auth://{token}/validate")
|
||||
async def validate(token: str = Depends(get_token)) -> str:
|
||||
return f"Validating with: {token}"
|
||||
|
||||
|
||||
async def test_toolerror_propagates_from_dependency(mcp: FastMCP):
|
||||
"""ToolError raised in a dependency should propagate unchanged (issue #2633).
|
||||
|
||||
When a dependency raises ToolError, it should not be wrapped in RuntimeError.
|
||||
This allows developers to use ToolError for validation in dependencies.
|
||||
"""
|
||||
from fastmcp.exceptions import ToolError
|
||||
|
||||
def validate_client_id() -> str:
|
||||
raise ToolError("Client ID is required - select a client first")
|
||||
|
||||
@mcp.tool()
|
||||
async def my_tool(client_id: str = Depends(validate_client_id)) -> str:
|
||||
return f"Working with client: {client_id}"
|
||||
|
||||
async with Client(mcp) as client:
|
||||
# ToolError is converted to an error result by the server
|
||||
result = await client.call_tool("my_tool", {}, raise_on_error=False)
|
||||
assert result.is_error
|
||||
# The original error message should be preserved (not wrapped in RuntimeError)
|
||||
assert result.content[0].text == "Client ID is required - select a client first" # type: ignore[attr-defined]
|
||||
|
||||
|
||||
async def test_validation_error_propagates_from_dependency(mcp: FastMCP):
|
||||
"""ValidationError raised in a dependency should propagate unchanged."""
|
||||
from fastmcp.exceptions import ValidationError
|
||||
|
||||
def validate_input() -> str:
|
||||
raise ValidationError("Invalid input format")
|
||||
|
||||
@mcp.tool()
|
||||
async def tool_with_validation(val: str = Depends(validate_input)) -> str:
|
||||
return val
|
||||
|
||||
async with Client(mcp) as client:
|
||||
# ValidationError is re-raised by the server and becomes an error result
|
||||
# The original error message should be preserved (not wrapped in RuntimeError)
|
||||
result = await client.call_tool(
|
||||
"tool_with_validation", {}, raise_on_error=False
|
||||
)
|
||||
assert result.is_error
|
||||
assert result.content[0].text == "Invalid input format" # type: ignore[attr-defined]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue