Skip to content

fix(adk): stop leaking Authorization headers in MCP tool error responses - #2439

Open
voltagebots wants to merge 1 commit into
kagent-dev:mainfrom
voltagebots:ofrank/mcp-tool-http-status-error-handling
Open

fix(adk): stop leaking Authorization headers in MCP tool error responses#2439
voltagebots wants to merge 1 commit into
kagent-dev:mainfrom
voltagebots:ofrank/mcp-tool-http-status-error-handling

Conversation

@voltagebots

Copy link
Copy Markdown

What

Reworks ConnectionSafeMcpTool error handling so failure responses and logs never stringify an exception that may carry request metadata:

  • _connection_error_response no longer interpolates str(error) into the model-visible message and no longer passes exc_info=error to the logger; it reports only the exception type.
  • New _extract_http_status walks the exception chain and any BaseExceptionGroup (via __cause__/__context__/.exceptions, with cycle protection) to find an httpx.HTTPStatusError without stringifying it, and surfaces a sanitized HTTP-status message.
  • run_async now handles bare httpx.HTTPStatusError, McpError wrapping an HTTP status, and BaseExceptionGroup wrapping an HTTP status.

Why

An MCP transport failure could previously place str(error) — which may include the outbound Authorization header — into both the LLM-visible error text and the logs. This is a credential-leak path. The fix keeps error responses actionable (type name / HTTP status / "do not retry") while removing the leak surface.

Verified against the pinned transport (mcp==1.29.0)

The credential-leak sanitization is proven by tests to hold regardless of how the error is wrapped (bare HTTPStatusError, McpError-wrapped, ExceptionGroup-wrapped, or a raw ConnectError carrying an Authorization header) — in every case the secret and header name are absent from both the returned dict and the logged output. The connection-error classification itself was checked against the actual pinned mcp package source: streamable_http's POST handler runs inside an anyio task group, which propagates a non-2xx raise_for_status() failure as a BaseExceptionGroup wrapping httpx.HTTPStatusError — exactly the shape the group-walking branch above handles.

Testing

uv run pytest packages/kagent-adk/tests/unittests/test_mcp_connection_error_handling.py — 16/16 passed (python 3.13.15), including all 4 new tests and all 12 pre-existing tests with zero regressions.

Reworks ConnectionSafeMcpTool error handling so failure responses and
logs never stringify an exception that may carry request metadata.
_connection_error_response no longer interpolates str(error) or passes
exc_info=error; a new _extract_http_status walks the exception chain
and any BaseExceptionGroup to find an httpx.HTTPStatusError without
stringifying it, surfacing a sanitized HTTP-status message instead.

Covers bare httpx.HTTPStatusError, McpError wrapping an HTTP status,
and BaseExceptionGroup wrapping an HTTP status (confirmed against the
pinned mcp==1.29.0 transport, which propagates via an anyio task group).

Signed-off-by: Franklin Okpako <franklin.okpako075@gmail.com>
@github-actions github-actions Bot added the bug Something isn't working label Aug 13, 2026
@voltagebots
voltagebots marked this pull request as ready for review August 13, 2026 20:47
@voltagebots
voltagebots requested a review from a team as a code owner August 13, 2026 20:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant