@@ -1006,25 +1006,49 @@ async fn main() -> anyhow::Result<()> {
10061006
10071007 tracing:: info!( "Running scenario '{}' against {}" , scenario, server_url) ;
10081008
1009- match scenario. as_str ( ) {
1009+ // Safety net: some harness servers intentionally misbehave (e.g. reply
1010+ // with an id-less error instead of answering a request), which would
1011+ // leave the client waiting forever. Exit on our own before the harness's
1012+ // 30s client timeout so it never has to kill us (which has been observed
1013+ // to wedge the harness process in CI).
1014+ let timeout_secs: u64 = std:: env:: var ( "MCP_CONFORMANCE_TIMEOUT_SECS" )
1015+ . ok ( )
1016+ . and_then ( |v| v. parse ( ) . ok ( ) )
1017+ . unwrap_or ( 25 ) ;
1018+ tokio:: time:: timeout (
1019+ std:: time:: Duration :: from_secs ( timeout_secs) ,
1020+ run_scenario ( & scenario, & server_url, & ctx) ,
1021+ )
1022+ . await
1023+ . map_err ( |_| anyhow:: anyhow!( "Scenario '{scenario}' timed out after {timeout_secs}s" ) ) ??;
1024+
1025+ Ok ( ( ) )
1026+ }
1027+
1028+ async fn run_scenario (
1029+ scenario : & str ,
1030+ server_url : & str ,
1031+ ctx : & ConformanceContext ,
1032+ ) -> anyhow:: Result < ( ) > {
1033+ match scenario {
10101034 // Non-auth scenarios
1011- "initialize" => run_basic_client ( & server_url) . await ?,
1035+ "initialize" => run_basic_client ( server_url) . await ?,
10121036 // SEP-2106: the scenario serves a tool whose schema carries a network
10131037 // `$ref`; the check passes when the client lists tools without
10141038 // dereferencing (fetching) that URL. A plain connect → list_tools →
10151039 // close is sufficient; the scenario's mock server does not implement
10161040 // the discover lifecycle, so `run_discover_client` hangs against it.
1017- "json-schema-ref-no-deref" => run_basic_client ( & server_url) . await ?,
1018- "tools_call" => run_tools_call_client ( & server_url, & ctx) . await ?,
1041+ "json-schema-ref-no-deref" => run_basic_client ( server_url) . await ?,
1042+ "tools_call" => run_tools_call_client ( server_url, ctx) . await ?,
10191043 "elicitation-sep1034-client-defaults" => {
1020- run_elicitation_defaults_client ( & server_url) . await ?
1044+ run_elicitation_defaults_client ( server_url) . await ?
10211045 }
1022- "sse-retry" => run_sse_retry_client ( & server_url) . await ?,
1046+ "sse-retry" => run_sse_retry_client ( server_url) . await ?,
10231047 "request-metadata" | "sep-2322-client-request-state" => {
1024- run_discover_client ( & server_url) . await ?
1048+ run_discover_client ( server_url) . await ?
10251049 }
10261050 "http-standard-headers" | "http-custom-headers" | "http-invalid-tool-headers" => {
1027- run_discover_tools_call_client ( & server_url, & ctx) . await ?
1051+ run_discover_tools_call_client ( server_url, ctx) . await ?
10281052 }
10291053
10301054 // Auth scenarios - standard OAuth flow
@@ -1056,34 +1080,26 @@ async fn main() -> anyhow::Result<()> {
10561080 | "auth/iss-wrong-issuer"
10571081 | "auth/iss-unexpected"
10581082 | "auth/iss-normalized"
1059- | "auth/metadata-issuer-mismatch"
1060- | "auth/metadata-issuer-mismatch"
1061- // SEP-2352: PRM `authorization_servers` switches between calls; a
1062- // compliant client re-registers at the new AS. Known partial failure:
1063- // the SDK lacks issuer-stamped credential storage (#879), so the
1064- // `sep-2352-reregister-on-as-change` check fails. Left on the standard
1065- // flow rather than fixture-orchestrated re-registration so the
1066- // conformance result reflects real SDK behavior.
1067- | "auth/authorization-server-migration" => run_auth_client ( & server_url, & ctx) . await ?,
1083+ | "auth/metadata-issuer-mismatch" => run_auth_client ( server_url, ctx) . await ?,
10681084
10691085 // Auth - scope step-up
1070- "auth/scope-step-up" => run_auth_scope_step_up_client ( & server_url, & ctx) . await ?,
1086+ "auth/scope-step-up" => run_auth_scope_step_up_client ( server_url, ctx) . await ?,
10711087
10721088 // Auth - scope retry limit
1073- "auth/scope-retry-limit" => run_auth_scope_retry_limit_client ( & server_url, & ctx) . await ?,
1089+ "auth/scope-retry-limit" => run_auth_scope_retry_limit_client ( server_url, ctx) . await ?,
10741090
10751091 // Auth - authorization server migration (SEP-2352)
10761092 "auth/authorization-server-migration" => {
1077- run_auth_server_migration_client ( & server_url, & ctx) . await ?
1093+ run_auth_server_migration_client ( server_url, ctx) . await ?
10781094 }
10791095
10801096 // Auth - pre-registration
1081- "auth/pre-registration" => run_auth_preregistered_client ( & server_url, & ctx) . await ?,
1097+ "auth/pre-registration" => run_auth_preregistered_client ( server_url, ctx) . await ?,
10821098
10831099 // Auth - resource mismatch (should fail to auth → pass)
10841100 "auth/resource-mismatch" => {
10851101 // Try to auth; it should fail because PRM resource doesn't match
1086- match run_auth_client ( & server_url, & ctx) . await {
1102+ match run_auth_client ( server_url, ctx) . await {
10871103 Ok ( _) => {
10881104 tracing:: warn!( "Auth succeeded despite resource mismatch!" ) ;
10891105 }
@@ -1094,12 +1110,12 @@ async fn main() -> anyhow::Result<()> {
10941110 }
10951111
10961112 // Auth - client credentials
1097- "auth/client-credentials-basic" => run_client_credentials_basic ( & server_url, & ctx) . await ?,
1098- "auth/client-credentials-jwt" => run_client_credentials_jwt ( & server_url, & ctx) . await ?,
1113+ "auth/client-credentials-basic" => run_client_credentials_basic ( server_url, ctx) . await ?,
1114+ "auth/client-credentials-jwt" => run_client_credentials_jwt ( server_url, ctx) . await ?,
10991115
11001116 // Auth - cross-app access
11011117 "auth/cross-app-access-complete-flow" => {
1102- run_cross_app_access_client ( & server_url, & ctx) . await ?
1118+ run_cross_app_access_client ( server_url, ctx) . await ?
11031119 }
11041120
11051121 unknown => anyhow:: bail!( "Unsupported conformance scenario: {unknown}" ) ,
0 commit comments