From a2b6b8b0ff4b4fb6b5ba6417af665d1d65eb51f2 Mon Sep 17 00:00:00 2001 From: Jack Amadeo Date: Fri, 17 Jul 2026 21:57:24 -0400 Subject: [PATCH] fix: enumerate conformance client scenarios MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fixes #1001. Restores explicit dispatch arms for the ten client scenarios orphaned by #991: standard auth flow: - `auth/iss-*` - `auth/offline-access-*` - `auth/metadata-issuer-mismatch` - `auth/authorization-server-migration` plain connect/list client: `json-schema-ref-no-deref` `run_auth_client` now negotiates via `ClientLifecycleMode::Discover` โ€” `auth/authorization-server-migration` tracked in #879 --- conformance/src/bin/client.rs | 58 +++++++++++++++++++++++++++++++---- 1 file changed, 52 insertions(+), 6 deletions(-) diff --git a/conformance/src/bin/client.rs b/conformance/src/bin/client.rs index 0183532fa..5f7745b42 100644 --- a/conformance/src/bin/client.rs +++ b/conformance/src/bin/client.rs @@ -286,7 +286,17 @@ async fn run_auth_client(server_url: &str, ctx: &ConformanceContext) -> anyhow:: StreamableHttpClientTransportConfig::with_uri(server_url), ); - let client = BasicClientHandler.serve(transport).await?; + // The 2026-07-28 auth mocks require the modern per-request lifecycle + // (MCP-Protocol-Version header on every request), so negotiate via the + // discover lifecycle rather than the legacy initialize handshake. + let client = BasicClientHandler + .serve_with_lifecycle( + transport, + ClientLifecycleMode::Discover { + preferred_versions: preferred_protocol_versions(), + }, + ) + .await?; tracing::debug!("Connected (authenticated)"); let tools = client.list_tools(Default::default()).await?; @@ -839,15 +849,22 @@ fn conformance_protocol_version() -> ProtocolVersion { .unwrap_or(ProtocolVersion::V_2026_07_28) } -/// Runs draft stateless scenarios through the public discover lifecycle and -/// Streamable HTTP transport. -async fn run_discover_client(server_url: &str) -> anyhow::Result<()> { +/// Preferred protocol versions for discover-lifecycle negotiation: the +/// runner-provided version first, then all other known versions newest-first. +fn preferred_protocol_versions() -> Vec { let mut preferred_versions = vec![conformance_protocol_version()]; for version in ProtocolVersion::KNOWN_VERSIONS.iter().rev() { if !preferred_versions.contains(version) { preferred_versions.push(version.clone()); } } + preferred_versions +} + +/// Runs draft stateless scenarios through the public discover lifecycle and +/// Streamable HTTP transport. +async fn run_discover_client(server_url: &str) -> anyhow::Result<()> { + let preferred_versions = preferred_protocol_versions(); let transport = StreamableHttpClientTransport::from_uri(server_url); let client = FullClientHandler .serve_with_lifecycle( @@ -908,7 +925,12 @@ async fn main() -> anyhow::Result<()> { match scenario.as_str() { // Non-auth scenarios "initialize" => run_basic_client(&server_url).await?, - "json-schema-ref-no-deref" => run_discover_client(&server_url).await?, + // SEP-2106: the scenario serves a tool whose schema carries a network + // `$ref`; the check passes when the client lists tools without + // dereferencing (fetching) that URL. A plain connect โ†’ list_tools โ†’ + // close is sufficient; the scenario's mock server does not implement + // the discover lifecycle, so `run_discover_client` hangs against it. + "json-schema-ref-no-deref" => run_basic_client(&server_url).await?, "tools_call" => run_tools_call_client(&server_url, &ctx).await?, "elicitation-sep1034-client-defaults" => { run_elicitation_defaults_client(&server_url).await? @@ -934,7 +956,31 @@ async fn main() -> anyhow::Result<()> { | "auth/token-endpoint-auth-post" | "auth/token-endpoint-auth-none" | "auth/2025-03-26-oauth-metadata-backcompat" - | "auth/2025-03-26-oauth-endpoint-fallback" => run_auth_client(&server_url, &ctx).await?, + | "auth/2025-03-26-oauth-endpoint-fallback" + // Offline access scope handling: positive/negative variants both run + // the well-behaved flow; the referee inspects the requested scopes. + | "auth/offline-access-scope" + | "auth/offline-access-not-supported" + // SEP-2468 (RFC 9207 iss / RFC 8414 ยง3.3 issuer-echo). The client + // captures `iss` from the authorization redirect and passes it to the + // callback handler; the SDK validates internally. Positive scenarios + // proceed to the token endpoint; negative scenarios error out (the + // referee sets `allowClientError`). + | "auth/iss-supported" + | "auth/iss-not-advertised" + | "auth/iss-supported-missing" + | "auth/iss-wrong-issuer" + | "auth/iss-unexpected" + | "auth/iss-normalized" + | "auth/metadata-issuer-mismatch" + | "auth/metadata-issuer-mismatch" + // SEP-2352: PRM `authorization_servers` switches between calls; a + // compliant client re-registers at the new AS. Known partial failure: + // the SDK lacks issuer-stamped credential storage (#879), so the + // `sep-2352-reregister-on-as-change` check fails. Left on the standard + // flow rather than fixture-orchestrated re-registration so the + // conformance result reflects real SDK behavior. + | "auth/authorization-server-migration" => run_auth_client(&server_url, &ctx).await?, // Auth - scope step-up "auth/scope-step-up" => run_auth_scope_step_up_client(&server_url, &ctx).await?,