| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,113 @@ | ||
| """Test that UrlElicitationRequiredError is properly propagated as MCP error.""" | ||
|
|
||
| import pytest | ||
|
|
||
| from mcp import types | ||
| from mcp.server.fastmcp import Context, FastMCP | ||
| from mcp.server.session import ServerSession | ||
| from mcp.shared.exceptions import McpError, UrlElicitationRequiredError | ||
| from mcp.shared.memory import create_connected_server_and_client_session | ||
|
|
||
|
|
||
| @pytest.mark.anyio | ||
| async def test_url_elicitation_error_thrown_from_tool(): | ||
| """Test that UrlElicitationRequiredError raised from a tool is received as McpError by client.""" | ||
| mcp = FastMCP(name="UrlElicitationErrorServer") | ||
|
|
||
| @mcp.tool(description="A tool that raises UrlElicitationRequiredError") | ||
| async def connect_service(service_name: str, ctx: Context[ServerSession, None]) -> str: | ||
| # This tool cannot proceed without authorization | ||
| raise UrlElicitationRequiredError( | ||
| [ | ||
| types.ElicitRequestURLParams( | ||
| mode="url", | ||
| message=f"Authorization required to connect to {service_name}", | ||
| url=f"https://{service_name}.example.com/oauth/authorize", | ||
| elicitationId=f"{service_name}-auth-001", | ||
| ) | ||
| ] | ||
| ) | ||
|
|
||
| async with create_connected_server_and_client_session(mcp._mcp_server) as client_session: | ||
| await client_session.initialize() | ||
|
|
||
| # Call the tool - it should raise McpError with URL_ELICITATION_REQUIRED code | ||
| with pytest.raises(McpError) as exc_info: | ||
| await client_session.call_tool("connect_service", {"service_name": "github"}) | ||
|
|
||
| # Verify the error details | ||
| error = exc_info.value.error | ||
| assert error.code == types.URL_ELICITATION_REQUIRED | ||
| assert error.message == "URL elicitation required" | ||
|
|
||
| # Verify the error data contains elicitations | ||
| assert error.data is not None | ||
| assert "elicitations" in error.data | ||
| elicitations = error.data["elicitations"] | ||
| assert len(elicitations) == 1 | ||
| assert elicitations[0]["mode"] == "url" | ||
| assert elicitations[0]["url"] == "https://github.example.com/oauth/authorize" | ||
| assert elicitations[0]["elicitationId"] == "github-auth-001" | ||
|
|
||
|
|
||
| @pytest.mark.anyio | ||
| async def test_url_elicitation_error_from_error(): | ||
| """Test that client can reconstruct UrlElicitationRequiredError from McpError.""" | ||
| mcp = FastMCP(name="UrlElicitationErrorServer") | ||
|
|
||
| @mcp.tool(description="A tool that raises UrlElicitationRequiredError with multiple elicitations") | ||
| async def multi_auth(ctx: Context[ServerSession, None]) -> str: | ||
| raise UrlElicitationRequiredError( | ||
| [ | ||
| types.ElicitRequestURLParams( | ||
| mode="url", | ||
| message="GitHub authorization required", | ||
| url="https://github.example.com/oauth", | ||
| elicitationId="github-auth", | ||
| ), | ||
| types.ElicitRequestURLParams( | ||
| mode="url", | ||
| message="Google Drive authorization required", | ||
| url="https://drive.google.com/oauth", | ||
| elicitationId="gdrive-auth", | ||
| ), | ||
| ] | ||
| ) | ||
|
|
||
| async with create_connected_server_and_client_session(mcp._mcp_server) as client_session: | ||
| await client_session.initialize() | ||
|
|
||
| # Call the tool and catch the error | ||
| with pytest.raises(McpError) as exc_info: | ||
| await client_session.call_tool("multi_auth", {}) | ||
|
|
||
| # Reconstruct the typed error | ||
| mcp_error = exc_info.value | ||
| assert mcp_error.error.code == types.URL_ELICITATION_REQUIRED | ||
|
|
||
| url_error = UrlElicitationRequiredError.from_error(mcp_error.error) | ||
|
|
||
| # Verify the reconstructed error has both elicitations | ||
| assert len(url_error.elicitations) == 2 | ||
| assert url_error.elicitations[0].elicitationId == "github-auth" | ||
| assert url_error.elicitations[1].elicitationId == "gdrive-auth" | ||
|
|
||
|
|
||
| @pytest.mark.anyio | ||
| async def test_normal_exceptions_still_return_error_result(): | ||
| """Test that normal exceptions still return CallToolResult with isError=True.""" | ||
| mcp = FastMCP(name="NormalErrorServer") | ||
|
|
||
| @mcp.tool(description="A tool that raises a normal exception") | ||
| async def failing_tool(ctx: Context[ServerSession, None]) -> str: | ||
| raise ValueError("Something went wrong") | ||
|
|
||
| async with create_connected_server_and_client_session(mcp._mcp_server) as client_session: | ||
| await client_session.initialize() | ||
|
|
||
| # Normal exceptions should be returned as error results, not McpError | ||
| result = await client_session.call_tool("failing_tool", {}) | ||
| assert result.isError is True | ||
| assert len(result.content) == 1 | ||
| assert isinstance(result.content[0], types.TextContent) | ||
| assert "Something went wrong" in result.content[0].text |
| Back | FazBrowse Home | New Git URL |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low QualityFeels slightly awkward to have this feature specific exception at such a generic level - but can't think of a better way to achieve the intended design of 1036 right now (and also matches what TS SDK does).
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low QualityCreated an issue to potentially find a better way to raise this error, but don't want to block on this: #1788
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low QualityThanks @felixweinberger . I considered raising it as McpError (with the specific url elicitation error code info) but thought that could break existing clients.
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low QualityYeah that wouldn't be the right approach - I was thinking more something along the lines of having a propagate_error: bool on McpError for example and then have some gating inside that on whether to raise or pass, but that's definitely overengineering for one exception case imo.
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.