@@ -982,25 +982,49 @@ async fn main() -> anyhow::Result<()> {
982982
983983 tracing:: info!( "Running scenario '{}' against {}" , scenario, server_url) ;
984984
985- match scenario. as_str ( ) {
985+ // Safety net: some harness servers intentionally misbehave (e.g. reply
986+ // with an id-less error instead of answering a request), which would
987+ // leave the client waiting forever. Exit on our own before the harness's
988+ // 30s client timeout so it never has to kill us (which has been observed
989+ // to wedge the harness process in CI).
990+ let timeout_secs: u64 = std:: env:: var ( "MCP_CONFORMANCE_TIMEOUT_SECS" )
991+ . ok ( )
992+ . and_then ( |v| v. parse ( ) . ok ( ) )
993+ . unwrap_or ( 25 ) ;
994+ tokio:: time:: timeout (
995+ std:: time:: Duration :: from_secs ( timeout_secs) ,
996+ run_scenario ( & scenario, & server_url, & ctx) ,
997+ )
998+ . await
999+ . map_err ( |_| anyhow:: anyhow!( "Scenario '{scenario}' timed out after {timeout_secs}s" ) ) ??;
1000+
1001+ Ok ( ( ) )
1002+ }
1003+
1004+ async fn run_scenario (
1005+ scenario : & str ,
1006+ server_url : & str ,
1007+ ctx : & ConformanceContext ,
1008+ ) -> anyhow:: Result < ( ) > {
1009+ match scenario {
9861010 // Non-auth scenarios
987- "initialize" => run_basic_client ( & server_url) . await ?,
1011+ "initialize" => run_basic_client ( server_url) . await ?,
9881012 // SEP-2106: the scenario serves a tool whose schema carries a network
9891013 // `$ref`; the check passes when the client lists tools without
9901014 // dereferencing (fetching) that URL. A plain connect → list_tools →
9911015 // close is sufficient; the scenario's mock server does not implement
9921016 // the discover lifecycle, so `run_discover_client` hangs against it.
993- "json-schema-ref-no-deref" => run_basic_client ( & server_url) . await ?,
994- "tools_call" => run_tools_call_client ( & server_url, & ctx) . await ?,
1017+ "json-schema-ref-no-deref" => run_basic_client ( server_url) . await ?,
1018+ "tools_call" => run_tools_call_client ( server_url, ctx) . await ?,
9951019 "elicitation-sep1034-client-defaults" => {
996- run_elicitation_defaults_client ( & server_url) . await ?
1020+ run_elicitation_defaults_client ( server_url) . await ?
9971021 }
998- "sse-retry" => run_sse_retry_client ( & server_url) . await ?,
1022+ "sse-retry" => run_sse_retry_client ( server_url) . await ?,
9991023 "request-metadata" | "sep-2322-client-request-state" => {
1000- run_discover_client ( & server_url) . await ?
1024+ run_discover_client ( server_url) . await ?
10011025 }
10021026 "http-standard-headers" | "http-custom-headers" | "http-invalid-tool-headers" => {
1003- run_tools_call_client ( & server_url, & ctx) . await ?
1027+ run_tools_call_client ( server_url, ctx) . await ?
10041028 }
10051029
10061030 // Auth scenarios - standard OAuth flow
@@ -1032,34 +1056,26 @@ async fn main() -> anyhow::Result<()> {
10321056 | "auth/iss-wrong-issuer"
10331057 | "auth/iss-unexpected"
10341058 | "auth/iss-normalized"
1035- | "auth/metadata-issuer-mismatch"
1036- | "auth/metadata-issuer-mismatch"
1037- // SEP-2352: PRM `authorization_servers` switches between calls; a
1038- // compliant client re-registers at the new AS. Known partial failure:
1039- // the SDK lacks issuer-stamped credential storage (#879), so the
1040- // `sep-2352-reregister-on-as-change` check fails. Left on the standard
1041- // flow rather than fixture-orchestrated re-registration so the
1042- // conformance result reflects real SDK behavior.
1043- | "auth/authorization-server-migration" => run_auth_client ( & server_url, & ctx) . await ?,
1059+ | "auth/metadata-issuer-mismatch" => run_auth_client ( server_url, ctx) . await ?,
10441060
10451061 // Auth - scope step-up
1046- "auth/scope-step-up" => run_auth_scope_step_up_client ( & server_url, & ctx) . await ?,
1062+ "auth/scope-step-up" => run_auth_scope_step_up_client ( server_url, ctx) . await ?,
10471063
10481064 // Auth - scope retry limit
1049- "auth/scope-retry-limit" => run_auth_scope_retry_limit_client ( & server_url, & ctx) . await ?,
1065+ "auth/scope-retry-limit" => run_auth_scope_retry_limit_client ( server_url, ctx) . await ?,
10501066
10511067 // Auth - authorization server migration (SEP-2352)
10521068 "auth/authorization-server-migration" => {
1053- run_auth_server_migration_client ( & server_url, & ctx) . await ?
1069+ run_auth_server_migration_client ( server_url, ctx) . await ?
10541070 }
10551071
10561072 // Auth - pre-registration
1057- "auth/pre-registration" => run_auth_preregistered_client ( & server_url, & ctx) . await ?,
1073+ "auth/pre-registration" => run_auth_preregistered_client ( server_url, ctx) . await ?,
10581074
10591075 // Auth - resource mismatch (should fail to auth → pass)
10601076 "auth/resource-mismatch" => {
10611077 // Try to auth; it should fail because PRM resource doesn't match
1062- match run_auth_client ( & server_url, & ctx) . await {
1078+ match run_auth_client ( server_url, ctx) . await {
10631079 Ok ( _) => {
10641080 tracing:: warn!( "Auth succeeded despite resource mismatch!" ) ;
10651081 }
@@ -1070,12 +1086,12 @@ async fn main() -> anyhow::Result<()> {
10701086 }
10711087
10721088 // Auth - client credentials
1073- "auth/client-credentials-basic" => run_client_credentials_basic ( & server_url, & ctx) . await ?,
1074- "auth/client-credentials-jwt" => run_client_credentials_jwt ( & server_url, & ctx) . await ?,
1089+ "auth/client-credentials-basic" => run_client_credentials_basic ( server_url, ctx) . await ?,
1090+ "auth/client-credentials-jwt" => run_client_credentials_jwt ( server_url, ctx) . await ?,
10751091
10761092 // Auth - cross-app access
10771093 "auth/cross-app-access-complete-flow" => {
1078- run_cross_app_access_client ( & server_url, & ctx) . await ?
1094+ run_cross_app_access_client ( server_url, ctx) . await ?
10791095 }
10801096
10811097 unknown => anyhow:: bail!( "Unsupported conformance scenario: {unknown}" ) ,
0 commit comments