@@ -13,8 +13,17 @@ use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
1313
1414// ─── Context parsed from MCP_CONFORMANCE_CONTEXT ────────────────────────────
1515
16+ #[ derive( Debug , Default , serde:: Deserialize ) ]
17+ struct ConformanceToolCall {
18+ name : String ,
19+ #[ serde( default ) ]
20+ arguments : Option < serde_json:: Map < String , Value > > ,
21+ }
22+
1623#[ derive( Debug , Default , serde:: Deserialize ) ]
1724struct ConformanceContext {
25+ #[ serde( default ) ]
26+ tool_calls : Vec < ConformanceToolCall > ,
1827 #[ serde( default ) ]
1928 client_id : Option < String > ,
2029 #[ serde( default ) ]
@@ -793,16 +802,29 @@ async fn run_basic_client(server_url: &str) -> anyhow::Result<()> {
793802 Ok ( ( ) )
794803}
795804
796- async fn run_tools_call_client ( server_url : & str ) -> anyhow:: Result < ( ) > {
805+ async fn run_tools_call_client ( server_url : & str , ctx : & ConformanceContext ) -> anyhow:: Result < ( ) > {
797806 let transport = StreamableHttpClientTransport :: from_uri ( server_url) ;
798807 let client = FullClientHandler . serve ( transport) . await ?;
799808 let tools = client. list_tools ( Default :: default ( ) ) . await ?;
800- for tool in & tools. tools {
801- let args = build_tool_arguments ( tool) ;
802- let _ = client
803- . call_tool ( call_tool_params ( tool. name . clone ( ) , args) )
804- . await ?;
809+
810+ if ctx. tool_calls . is_empty ( ) {
811+ for tool in & tools. tools {
812+ let args = build_tool_arguments ( tool) ;
813+ client
814+ . call_tool ( call_tool_params ( tool. name . clone ( ) , args) )
815+ . await ?;
816+ }
817+ } else {
818+ for tool_call in & ctx. tool_calls {
819+ client
820+ . call_tool ( call_tool_params (
821+ tool_call. name . clone ( ) . into ( ) ,
822+ tool_call. arguments . clone ( ) ,
823+ ) )
824+ . await ?;
825+ }
805826 }
827+
806828 client. cancel ( ) . await ?;
807829 Ok ( ( ) )
808830}
@@ -851,6 +873,13 @@ impl StatelessHttpTransport {
851873 }
852874}
853875
876+ fn conformance_protocol_version ( ) -> ProtocolVersion {
877+ std:: env:: var ( "MCP_CONFORMANCE_PROTOCOL_VERSION" )
878+ . ok ( )
879+ . and_then ( |version| serde_json:: from_value ( Value :: String ( version) ) . ok ( ) )
880+ . unwrap_or ( ProtocolVersion :: V_2026_07_28 )
881+ }
882+
854883impl rmcp:: transport:: Transport < RoleClient > for StatelessHttpTransport {
855884 type Error = std:: io:: Error ;
856885
@@ -864,7 +893,10 @@ impl rmcp::transport::Transport<RoleClient> for StatelessHttpTransport {
864893 async move {
865894 let response = http
866895 . post ( uri. as_ref ( ) )
867- . header ( "MCP-Protocol-Version" , "2026-07-28" )
896+ . header (
897+ "MCP-Protocol-Version" ,
898+ conformance_protocol_version ( ) . as_str ( ) ,
899+ )
868900 . json ( & item)
869901 . send ( )
870902 . await
@@ -890,17 +922,19 @@ impl rmcp::transport::Transport<RoleClient> for StatelessHttpTransport {
890922 }
891923}
892924
893- /// A stateless-lifecycle client: the scenario's server has no `initialize`
894- /// handler, so skip the handshake with `serve_directly`, list the tools, and
895- /// call each one via the high-level `call_tool` helper (which drives SEP-2322
896- /// `input_required` retry rounds when the server requests them). Used by the
897- /// `sep-2322-client-request-state` scenario, whose mock server verifies
898- /// requestState echo, fresh JSON-RPC ids on retry, state omission, isolation
899- /// between tools, and the `resultType` default.
925+ /// Runs a client using the draft stateless lifecycle.
926+ ///
927+ /// Stateless servers do not implement the `initialize` handshake, so this
928+ /// uses `serve_directly`. The protocol version comes from
929+ /// `MCP_CONFORMANCE_PROTOCOL_VERSION` (defaulting to `2026-07-28`) and is
930+ /// used for both peer configuration and outgoing HTTP request headers.
931+ ///
932+ /// Lists available tools and calls each one, allowing the SDK's high-level
933+ /// tool-call handling to process any request retries.
900934async fn run_stateless_client ( server_url : & str ) -> anyhow:: Result < ( ) > {
901935 let transport = StatelessHttpTransport :: new ( server_url) ;
902936 let peer_info = InitializeResult :: new ( ServerCapabilities :: builder ( ) . enable_tools ( ) . build ( ) )
903- . with_protocol_version ( ProtocolVersion :: V_2026_07_28 ) ;
937+ . with_protocol_version ( conformance_protocol_version ( ) ) ;
904938 let client = serve_directly ( FullClientHandler , transport, Some ( peer_info) ) ;
905939
906940 let tools = client. list_tools ( Default :: default ( ) ) . await ?;
@@ -955,12 +989,18 @@ async fn main() -> anyhow::Result<()> {
955989 match scenario. as_str ( ) {
956990 // Non-auth scenarios
957991 "initialize" => run_basic_client ( & server_url) . await ?,
958- "tools_call" => run_tools_call_client ( & server_url) . await ?,
992+ "json-schema-ref-no-deref" => run_stateless_client ( & server_url) . await ?,
993+ "tools_call" => run_tools_call_client ( & server_url, & ctx) . await ?,
959994 "elicitation-sep1034-client-defaults" => {
960995 run_elicitation_defaults_client ( & server_url) . await ?
961996 }
962997 "sse-retry" => run_sse_retry_client ( & server_url) . await ?,
963- "sep-2322-client-request-state" => run_stateless_client ( & server_url) . await ?,
998+ "request-metadata" | "sep-2322-client-request-state" => {
999+ run_stateless_client ( & server_url) . await ?
1000+ }
1001+ "http-standard-headers" | "http-custom-headers" | "http-invalid-tool-headers" => {
1002+ run_tools_call_client ( & server_url, & ctx) . await ?
1003+ }
9641004
9651005 // Auth scenarios - standard OAuth flow
9661006 "auth/metadata-default"
@@ -1008,16 +1048,7 @@ async fn main() -> anyhow::Result<()> {
10081048 run_cross_app_access_client ( & server_url, & ctx) . await ?
10091049 }
10101050
1011- _ => {
1012- tracing:: warn!( "Unknown scenario '{}', trying auth flow" , scenario) ;
1013- match run_auth_client ( & server_url, & ctx) . await {
1014- Ok ( _) => { }
1015- Err ( e) => {
1016- tracing:: debug!( "Auth flow failed for unknown scenario: {e}" ) ;
1017- run_basic_client ( & server_url) . await ?
1018- }
1019- }
1020- }
1051+ unknown => anyhow:: bail!( "Unsupported conformance scenario: {unknown}" ) ,
10211052 }
10221053
10231054 Ok ( ( ) )
0 commit comments