Skip to content

Conversation

@habema
Copy link
Contributor

@habema habema commented Jan 7, 2026

Fixes an issue where MCP server connection failures (e.g., 401 Unauthorized, connection errors, 500 errors) surfaced as confusing BaseExceptionGroup exceptions with RuntimeError about cancel scopes, masking the actual HTTP errors.

What changed:

  • Added _extract_http_error_from_exception() helper method to extract HTTP errors from exceptions or BaseExceptionGroup raised by anyio task groups
  • Added _raise_user_error_for_http_error() helper method to wrap HTTP errors in clear, actionable UserError messages with specific guidance for different error types (401, 403, 500+, connection errors, timeouts)
  • Updated connect() to extract HTTP errors from exceptions/ExceptionGroups and wrap them in UserError, while preserving non-HTTP errors (e.g., ValueError) as-is
  • Updated cleanup() to catch BaseExceptionGroup raised during cleanup, extract HTTP errors from it, and re-raise them as UserError while suppressing RuntimeError about cancel scopes that would otherwise mask the real error
  • Updated list_tools() and call_tool() to catch httpx.HTTPStatusError and httpx.ConnectError and wrap them in UserError with clear messages
  • Updated invoke_mcp_tool() in util.py to preserve UserError exceptions for better error propagation

Result:

Cryptic BaseExceptionGroup with nested exceptions and RuntimeError: Attempted to exit cancel scope in a different task now become clear, actionable errors like:

agents.exceptions.UserError: Failed to connect to MCP server 'Streamable HTTP Python Server': Authentication failed (401 Unauthorized). Please check your credentials.

The original HTTP error is preserved in the exception chain via from e, maintaining full traceback information for debugging.


Current logs examples before and after fix:

Repro Scripts:

  • main.py
    # main.py
    import asyncio
    
    from agents import Agent, Runner
    from agents.mcp import MCPServer, MCPServerStreamableHttp
    from agents.model_settings import ModelSettings
    
    
    async def run(mcp_server: MCPServer):
        agent = Agent(
            name="Assistant",
            instructions="Use the tools to answer the questions.",
            mcp_servers=[mcp_server],
            model_settings=ModelSettings(tool_choice="required"),
        )
    
        message = "Add these numbers: 7 and 22."
        print(f"Running: {message}")
        result = await Runner.run(starting_agent=agent, input=message)
        print(result.final_output)
    
    
    async def main():
        async with MCPServerStreamableHttp(
            name="Streamable HTTP Python Server",
            params={
                "url": "http://localhost:8000/mcp", # Replace with wrong port to test unreachability
                # "headers": {"Authorization": "Bearer my-secret-token"}, # Comment/uncomment to test 401
            },
        ) as server:
            await run(server)
    
    
    if __name__ == "__main__":
        asyncio.run(main())
  • server.py
    # server.py
    from mcp.server.auth.provider import AccessToken, TokenVerifier
    from mcp.server.auth.settings import AuthSettings
    from mcp.server.fastmcp import FastMCP
    
    SECRET_TOKEN = "my-secret-token"
    
    
    class SimpleTokenVerifier(TokenVerifier):
        async def verify_token(self, token: str) -> AccessToken | None:
            """
            Verifies the incoming authorization token.
            """
            # raise Exception("Server Error for testing") # Uncomment this to test 5xx error
            if token == SECRET_TOKEN:
                print("Token verified successfully.")
                return AccessToken(
                    token=token,
                    client_id="static-client-id",
                    scopes=["read", "write"],
                    expires_at=None,
                    resource="http://localhost:8000/mcp/tools",
                )
            print("Token not verified.")
            return None
    
    
    mcp = FastMCP(
        "Echo Server",
        auth=AuthSettings(
            issuer_url="http://localhost:8000/mcp/auth",
            resource_server_url="http://localhost:8000/mcp",
        ),
        token_verifier=SimpleTokenVerifier(),
    )
    
    
    @mcp.tool()
    def add(a: int, b: int) -> int:
        """Add two numbers"""
        print(f"[debug-server] add({a}, {b})")
        return a + b
    
    
    if __name__ == "__main__":
        mcp.run(transport="streamable-http")

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 65f330839d

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

@habema habema marked this pull request as draft January 7, 2026 15:33
@habema habema force-pushed the fix/mcp-server-expection-handling branch from 4b6e201 to 07d9678 Compare January 14, 2026 12:13
@habema
Copy link
Contributor Author

habema commented Jan 14, 2026

@codex review

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ef5232b1eb

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

@habema habema marked this pull request as ready for review January 14, 2026 12:29
Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 87c04c0476

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants