Skip to content

Commit 2fe90a2

Browse files
authored
chore: update client conformance test scenarios (#991)
1 parent 98bb263 commit 2fe90a2

1 file changed

Lines changed: 58 additions & 27 deletions

File tree

conformance/src/bin/client.rs

Lines changed: 58 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -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)]
1724
struct 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+
854883
impl 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.
900934
async 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

Comments
 (0)