|
1 | 1 | use rmcp::{ |
2 | | - ClientHandler, ErrorData, RoleClient, ServiceExt, |
| 2 | + ClientHandler, ClientLifecycleMode, ClientServiceExt, ErrorData, RoleClient, ServiceExt, |
3 | 3 | model::*, |
4 | | - service::{RequestContext, serve_directly}, |
| 4 | + service::RequestContext, |
5 | 5 | transport::{ |
6 | 6 | AuthClient, AuthorizationManager, StreamableHttpClientTransport, |
7 | 7 | auth::{AuthorizationCallback, OAuthState}, |
@@ -846,96 +846,29 @@ async fn run_elicitation_defaults_client(server_url: &str) -> anyhow::Result<()> |
846 | 846 | Ok(()) |
847 | 847 | } |
848 | 848 |
|
849 | | -/// A minimal stateless client transport: every outgoing message is one HTTP |
850 | | -/// POST and the JSON response body (if any) is queued for `receive()`. |
851 | | -/// |
852 | | -/// The SEP-2322 client scenario's mock server speaks the stateless lifecycle |
853 | | -/// (no `initialize` handshake, plain JSON responses), which the session-based |
854 | | -/// `StreamableHttpClientTransport` cannot do. The transport is harness |
855 | | -/// plumbing; the behavior under test — the SDK's MRTR retry driver — runs |
856 | | -/// unchanged on top of it. |
857 | | -struct StatelessHttpTransport { |
858 | | - http: reqwest::Client, |
859 | | - uri: std::sync::Arc<str>, |
860 | | - tx: tokio::sync::mpsc::Sender<ServerJsonRpcMessage>, |
861 | | - rx: tokio::sync::mpsc::Receiver<ServerJsonRpcMessage>, |
862 | | -} |
863 | | - |
864 | | -impl StatelessHttpTransport { |
865 | | - fn new(uri: &str) -> Self { |
866 | | - let (tx, rx) = tokio::sync::mpsc::channel(16); |
867 | | - Self { |
868 | | - http: reqwest::Client::new(), |
869 | | - uri: uri.into(), |
870 | | - tx, |
871 | | - rx, |
872 | | - } |
873 | | - } |
874 | | -} |
875 | | - |
876 | 849 | fn conformance_protocol_version() -> ProtocolVersion { |
877 | 850 | std::env::var("MCP_CONFORMANCE_PROTOCOL_VERSION") |
878 | 851 | .ok() |
879 | 852 | .and_then(|version| serde_json::from_value(Value::String(version)).ok()) |
880 | 853 | .unwrap_or(ProtocolVersion::V_2026_07_28) |
881 | 854 | } |
882 | 855 |
|
883 | | -impl rmcp::transport::Transport<RoleClient> for StatelessHttpTransport { |
884 | | - type Error = std::io::Error; |
885 | | - |
886 | | - fn send( |
887 | | - &mut self, |
888 | | - item: rmcp::model::ClientJsonRpcMessage, |
889 | | - ) -> impl std::future::Future<Output = Result<(), Self::Error>> + Send + 'static { |
890 | | - let http = self.http.clone(); |
891 | | - let uri = self.uri.clone(); |
892 | | - let tx = self.tx.clone(); |
893 | | - async move { |
894 | | - let response = http |
895 | | - .post(uri.as_ref()) |
896 | | - .header( |
897 | | - "MCP-Protocol-Version", |
898 | | - conformance_protocol_version().as_str(), |
899 | | - ) |
900 | | - .json(&item) |
901 | | - .send() |
902 | | - .await |
903 | | - .map_err(std::io::Error::other)?; |
904 | | - match response.json::<ServerJsonRpcMessage>().await { |
905 | | - Ok(message) => { |
906 | | - let _ = tx.send(message).await; |
907 | | - } |
908 | | - Err(_) => { |
909 | | - // No JSON-RPC body (e.g. 202/204 for notifications). |
910 | | - } |
911 | | - } |
912 | | - Ok(()) |
| 856 | +/// Runs draft stateless scenarios through the public discover lifecycle and |
| 857 | +/// Streamable HTTP transport. |
| 858 | +async fn run_discover_client(server_url: &str) -> anyhow::Result<()> { |
| 859 | + let mut preferred_versions = vec![conformance_protocol_version()]; |
| 860 | + for version in ProtocolVersion::KNOWN_VERSIONS.iter().rev() { |
| 861 | + if !preferred_versions.contains(version) { |
| 862 | + preferred_versions.push(version.clone()); |
913 | 863 | } |
914 | 864 | } |
915 | | - |
916 | | - async fn receive(&mut self) -> Option<ServerJsonRpcMessage> { |
917 | | - self.rx.recv().await |
918 | | - } |
919 | | - |
920 | | - async fn close(&mut self) -> Result<(), Self::Error> { |
921 | | - Ok(()) |
922 | | - } |
923 | | -} |
924 | | - |
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. |
934 | | -async fn run_stateless_client(server_url: &str) -> anyhow::Result<()> { |
935 | | - let transport = StatelessHttpTransport::new(server_url); |
936 | | - let peer_info = InitializeResult::new(ServerCapabilities::builder().enable_tools().build()) |
937 | | - .with_protocol_version(conformance_protocol_version()); |
938 | | - let client = serve_directly(FullClientHandler, transport, Some(peer_info)); |
| 865 | + let transport = StreamableHttpClientTransport::from_uri(server_url); |
| 866 | + let client = FullClientHandler |
| 867 | + .serve_with_lifecycle( |
| 868 | + transport, |
| 869 | + ClientLifecycleMode::Discover { preferred_versions }, |
| 870 | + ) |
| 871 | + .await?; |
939 | 872 |
|
940 | 873 | let tools = client.list_tools(Default::default()).await?; |
941 | 874 | tracing::debug!("Listed {} tools", tools.tools.len()); |
@@ -989,14 +922,14 @@ async fn main() -> anyhow::Result<()> { |
989 | 922 | match scenario.as_str() { |
990 | 923 | // Non-auth scenarios |
991 | 924 | "initialize" => run_basic_client(&server_url).await?, |
992 | | - "json-schema-ref-no-deref" => run_stateless_client(&server_url).await?, |
| 925 | + "json-schema-ref-no-deref" => run_discover_client(&server_url).await?, |
993 | 926 | "tools_call" => run_tools_call_client(&server_url, &ctx).await?, |
994 | 927 | "elicitation-sep1034-client-defaults" => { |
995 | 928 | run_elicitation_defaults_client(&server_url).await? |
996 | 929 | } |
997 | 930 | "sse-retry" => run_sse_retry_client(&server_url).await?, |
998 | 931 | "request-metadata" | "sep-2322-client-request-state" => { |
999 | | - run_stateless_client(&server_url).await? |
| 932 | + run_discover_client(&server_url).await? |
1000 | 933 | } |
1001 | 934 | "http-standard-headers" | "http-custom-headers" | "http-invalid-tool-headers" => { |
1002 | 935 | run_tools_call_client(&server_url, &ctx).await? |
|
0 commit comments