fix(runtime-mcp): actually extract WWW-Authenticate from rmcp AuthRequired#2429
Merged
Conversation
…uired The original code had two parallel paths to pull \`www_authenticate_header\` out of an \`rmcp::ClientInitializeError\`, and both were dead code: 1. \`extract_auth_required\` walked \`std::error::Error::source()\` hoping to downcast to \`StreamableHttpError::AuthRequired\`, but \`ClientInitializeError::TransportError::error\` is not annotated with \`#[source]\`, so \`source()\` never reaches inside \`DynamicTransportError\`. A \`TODO(rmcp)\` comment blamed the missing annotation, but (a) that's hypothetical future work and (b) it isn't the right diagnosis — see #2. 2. \`extract_www_authenticate\` scraped \`e.to_string()\` for a \`www_authenticate_header: "..."\` marker. Also dead in practice: rmcp's \`Display\` for \`StreamableHttpError::AuthRequired\` is literally \`#[error("Auth required")]\` — the field name never appears in any Display layer of the chain, only in \`Debug\`. The scraper looked busy but never returned \`Some(_)\`. Net effect: every auth-required MCP handshake silently went into OAuth discovery with \`www_authenticate = None\`, so Tier 1 discovery (resource_metadata from the header) was permanently skipped and we always fell through to Tier 2 (\`.well-known\` on the server URL). Fix: \`DynamicTransportError\` already exposes its \`pub error: Box<dyn Error + Send + Sync>\` publicly, so we can match \`ClientInitializeError::TransportError { error, .. }\` directly, then \`downcast_ref::<StreamableHttpError<reqwest::Error>>()\` on the box. The downcast reaches \`AuthRequired\` without any \`source()\` traversal or upstream annotation changes. - Replace both dead helpers with a single \`extract_auth_header_from_error\` that does the direct match + downcast. - Keep the substring check (\`"401"\` / \`"Unauthorized"\` / \`"Auth required"\`) only as a defensive fallback so we don't regress if rmcp ever reshapes its chain. - Drop the \`TODO(rmcp)\` comment (no longer blocked on upstream). - Replace the two old unit tests with \`test_extract_auth_header_from_error_returns_none_for_non_transport_variant\`. The positive case can't be constructed from outside rmcp because \`AuthRequiredError\` is \`#[non_exhaustive]\`, but the negative-path test pins the "bail out on the wrong variant" invariant. Closes #2401, #2402 — the bot issues auto-generated from the TODO comment. The TODO is gone and the functionality it promised is now actually present. Verified: \`cargo test -p librefang-runtime-mcp --lib\` — 49 passed. \`cargo clippy -p librefang-runtime-mcp --all-targets -- -D warnings\` — clean.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
The MCP auth-discovery code had two parallel dead paths for extracting the
www_authenticate_headerfrom anrmcp::ClientInitializeError, plus aTODO(rmcp):comment promising that one of them would work "once upstream adds#[source]". The bot auto-filed two issues (#2401, #2402) against the TODO. When I looked at why the TODO existed at all, I found both paths were dead and the diagnosis was wrong.What was dead
extract_auth_requiredwalkedstd::error::Error::source(). The TODO blamed a missing#[source]onClientInitializeError::TransportError::error, but the real fix isn't to wait for upstream —DynamicTransportErroralready exposes itspub error: Box<dyn Error>publicly. We don't needsource()at all; we can match on the outer variant and downcast the box directly.extract_www_authenticatescrapede.to_string()for awww_authenticate_header: "…"marker. This never matched in production because rmcp'sDisplayforStreamableHttpError::AuthRequiredis literally#[error("Auth required")]— the field name only appears inDebug, never in any Display layer. Dead since day one.Net behavioural effect: every auth-required MCP handshake fell through OAuth discovery with
www_authenticate = None, silently skipping Tier 1 (resource_metadata from the header) and always going to Tier 2 (.well-knownon the server URL). That worked for servers that host OAuth metadata at the well-known path but failed for the rest.The fix
Direct match + downcast, no
source()traversal, no string scraping:The substring check (
"401"/"Unauthorized"/"Auth required") stays only as a defensive fallback so we don't regress if rmcp reshapes its chain.Closes
[rmcp] at the call site). The helper is correct and(bot issue extracted from the TODO)[rmcp] ClientInitializeError::TransportError does not annotate(bot issue, same TODO)Both were auto-generated from the same TODO comment, which is now gone and was the wrong diagnosis.
Tests
extract_auth_requiredtests (which only exercised negative paths becauseAuthRequiredErroris#[non_exhaustive]and the positive path couldn't be constructed from outside rmcp) with a singletest_extract_auth_header_from_error_returns_none_for_non_transport_variantthat pins the "bail out on the wrong outer variant" invariant.cargo test -p librefang-runtime-mcp --lib— 49 passedcargo clippy -p librefang-runtime-mcp --all-targets -- -D warnings— cleanTest plan
cargo test -p librefang-runtime-mcp --libcargo clippy -p librefang-runtime-mcp --all-targets -- -D warnings