1mod channel;
37mod handler;
38#[cfg(feature = "http-client")]
39mod http;
40#[cfg(feature = "oauth-client")]
41mod oauth;
42#[cfg(feature = "oauth-client")]
43mod oauth_authcode;
44#[cfg(feature = "oauth-client")]
45mod oauth_flow;
46mod response_cache;
47mod stdio;
48mod transport;
49
50pub use channel::ChannelTransport;
51pub use handler::{ClientHandler, NotificationHandler, ServerNotification};
52#[cfg(feature = "http-client")]
53pub use http::{HttpClientConfig, HttpClientTransport};
54#[cfg(feature = "oauth-client")]
55pub use oauth::{
56 OAuthBearerChallenge, OAuthClientCredentials, OAuthClientCredentialsBuilder, OAuthClientError,
57 OAuthScopeChallenge, OAuthScopeEscalationConfig, OAuthScopeEscalationHandler,
58 OAuthScopeEscalationRequest, OAuthTokenEndpointAuthMethod, TokenProvider,
59};
60#[cfg(feature = "oauth-client")]
61pub use oauth_authcode::{
62 MemoryOAuthClientRegistrationStore, OAuthApplicationType, OAuthAuthCodeConfig,
63 OAuthAuthorizationCode, OAuthAuthorizationDiscovery, OAuthAuthorizationServerMetadata,
64 OAuthClientRegistration, OAuthClientRegistrationMethod, OAuthClientRegistrationOptions,
65 OAuthClientRegistrationStore, OAuthDynamicClientRegistration, OAuthProtectedResourceMetadata,
66 discover_oauth_authorization, discover_oauth_authorization_server,
67 probe_oauth_bearer_challenge, resolve_oauth_client_registration,
68 resolve_oauth_client_registration_with_store,
69};
70#[cfg(feature = "oauth-client")]
71pub use oauth_flow::{
72 MemoryOAuthAuthorizationStateStore, MemoryOAuthTokenStore, OAuthAuthorizationAction,
73 OAuthAuthorizationFlow, OAuthAuthorizationFlowBuilder, OAuthAuthorizationHandler,
74 OAuthAuthorizationRequest, OAuthAuthorizationStart, OAuthAuthorizationStateStore,
75 OAuthClientAssertionRequest, OAuthClientAssertionSigner, OAuthHttpBody, OAuthHttpClient,
76 OAuthHttpMethod, OAuthHttpRequest, OAuthHttpResponse, OAuthPendingAuthorization,
77 OAuthPendingAuthorizationState, OAuthRedirectPolicy, OAuthStoredToken, OAuthTokenBinding,
78 OAuthTokenStore, ReqwestOAuthHttpClient,
79};
80pub use response_cache::{ClientCacheConfig, DEFAULT_MAX_CACHE_TTL};
81pub use stdio::StdioClientTransport;
82pub use transport::ClientTransport;
83
84use std::collections::HashMap;
85use std::sync::Arc;
86use std::sync::atomic::{AtomicBool, AtomicI64, Ordering};
87
88use tokio::sync::{Mutex, RwLock, mpsc, oneshot};
89use tokio::task::JoinHandle;
90
91use crate::ProtocolSupport;
92use crate::error::{Error, ErrorCode, McpErrorCode, Result};
93#[cfg(any(feature = "protocol-2026-07-28", feature = "stateless"))]
94use crate::protocol::DiscoverParams;
95use crate::protocol::{
96 CacheScope, CallToolParams, CallToolResult, CancelTaskParams, CancelledParams,
97 ClientCapabilities, CompleteParams, CompleteResult, CompletionArgument, CompletionReference,
98 CreateTaskResult, DiscoverResult, ElicitationCapability, GetPromptParams, GetPromptResult,
99 GetTaskInfoParams, Implementation, InitializeParams, InitializeResult, InputRequest,
100 InputRequests, InputResponse, InputResponses, JsonRpcNotification, JsonRpcRequest,
101 ListPromptsParams, ListPromptsResult, ListResourceTemplatesParams, ListResourceTemplatesResult,
102 ListResourcesParams, ListResourcesResult, ListRootsResult, ListToolsParams, ListToolsResult,
103 PromptDefinition, ReadResourceParams, ReadResourceResult, RequestId, RequestMeta,
104 RequestOutcome, ResourceDefinition, ResourceTemplateDefinition, Root, RootsCapability,
105 SamplingCapability, SubscriptionFilter, SubscriptionsAcknowledgedParams,
106 SubscriptionsListenParams, SubscriptionsListenResult, TaskObject, TaskRequestParams,
107 TaskStatusParams, ToolDefinition, UpdateTaskParams, notifications,
108};
109use response_cache::{CacheLookup, ClientResponseCache};
110use tower_mcp_types::JsonRpcError;
111
112#[derive(Debug, Clone, serde::Deserialize)]
120#[serde(untagged)]
121#[non_exhaustive]
122pub enum TaskAwareCallToolOutcome {
123 Task(crate::tasks::CreateTaskResult),
125 Complete(CallToolResult),
127 InputRequired(crate::protocol::InputRequiredResult),
129}
130
131trait CacheableResponse:
132 Clone + serde::Serialize + serde::de::DeserializeOwned + Send + Sync + 'static
133{
134 fn ttl_ms(&self) -> Option<u64>;
135 fn cache_scope(&self) -> Option<CacheScope>;
136}
137
138macro_rules! impl_cacheable_response {
139 ($($ty:ty),+ $(,)?) => {
140 $(
141 impl CacheableResponse for $ty {
142 fn ttl_ms(&self) -> Option<u64> {
143 self.ttl_ms
144 }
145
146 fn cache_scope(&self) -> Option<CacheScope> {
147 self.cache_scope
148 }
149 }
150 )+
151 };
152}
153
154impl_cacheable_response!(
155 DiscoverResult,
156 ListToolsResult,
157 ListResourcesResult,
158 ListResourceTemplatesResult,
159 ListPromptsResult,
160 ReadResourceResult,
161);
162
163enum LoopCommand {
165 Request {
167 method: String,
168 params: serde_json::Value,
169 response_tx: oneshot::Sender<Result<serde_json::Value>>,
170 },
171 StartSubscription {
173 params: serde_json::Value,
174 id_tx: oneshot::Sender<RequestId>,
175 acknowledgment_tx: oneshot::Sender<SubscriptionFilter>,
176 response_tx: oneshot::Sender<Result<serde_json::Value>>,
177 },
178 CancelRequest {
180 request_id: RequestId,
181 done_tx: Option<oneshot::Sender<Result<()>>>,
182 },
183 Notify {
185 method: String,
186 params: serde_json::Value,
187 },
188 ResetSession { done_tx: oneshot::Sender<()> },
190 ResolveInputs {
192 requests: InputRequests,
193 response_tx: oneshot::Sender<Result<InputResponses>>,
194 },
195 Shutdown,
197}
198
199#[must_use = "dropping the handle cancels the active subscription"]
207pub struct SubscriptionHandle {
208 request_id: RequestId,
209 command_tx: mpsc::Sender<LoopCommand>,
210 acknowledgment_rx: Option<oneshot::Receiver<SubscriptionFilter>>,
211 response_rx: Option<oneshot::Receiver<Result<serde_json::Value>>>,
212 active: bool,
213}
214
215impl SubscriptionHandle {
216 pub fn id(&self) -> &RequestId {
218 &self.request_id
219 }
220
221 pub async fn acknowledged(&mut self) -> Result<SubscriptionFilter> {
225 let receiver = self.acknowledgment_rx.take().ok_or_else(|| {
226 Error::Transport("subscription acknowledgment was already consumed".to_string())
227 })?;
228 receiver
229 .await
230 .map_err(|_| Error::Transport("subscription ended before acknowledgment".to_string()))
231 }
232
233 pub async fn wait(mut self) -> Result<SubscriptionsListenResult> {
239 let receiver = self.response_rx.take().ok_or_else(|| {
240 Error::Transport("subscription result was already consumed".to_string())
241 })?;
242 let value = receiver
243 .await
244 .map_err(|_| Error::Transport("connection closed".to_string()))??;
245 self.active = false;
246 let result: SubscriptionsListenResult = serde_json::from_value(value).map_err(|error| {
247 Error::Transport(format!(
248 "failed to deserialize subscriptions/listen response: {error}"
249 ))
250 })?;
251 if !result.result_type.is_complete() {
252 return Err(Error::Transport(format!(
253 "subscriptions/listen ended with unexpected result type {:?}",
254 result.result_type
255 )));
256 }
257 if !request_ids_match(&result.meta.subscription_id, &self.request_id) {
258 return Err(Error::Transport(
259 "subscriptions/listen result carried the wrong subscription ID".to_string(),
260 ));
261 }
262 Ok(result)
263 }
264
265 pub async fn cancel(mut self) -> Result<()> {
267 let (done_tx, done_rx) = oneshot::channel();
268 self.command_tx
269 .send(LoopCommand::CancelRequest {
270 request_id: self.request_id.clone(),
271 done_tx: Some(done_tx),
272 })
273 .await
274 .map_err(|_| Error::Transport("connection closed".to_string()))?;
275 let result = done_rx
276 .await
277 .map_err(|_| Error::Transport("connection closed".to_string()))?;
278 self.active = false;
279 result
280 }
281}
282
283impl Drop for SubscriptionHandle {
284 fn drop(&mut self) {
285 if self.active {
286 let _ = self.command_tx.try_send(LoopCommand::CancelRequest {
287 request_id: self.request_id.clone(),
288 done_tx: None,
289 });
290 }
291 }
292}
293
294pub struct McpClient {
324 command_tx: mpsc::Sender<LoopCommand>,
326 task: Option<JoinHandle<()>>,
328 initialized: AtomicBool,
330 server_info: RwLock<Option<InitializeResult>>,
332 capabilities: ClientCapabilities,
334 protocol_support: ProtocolSupport,
336 selected_protocol_version: RwLock<Option<String>>,
338 client_info: RwLock<Option<Implementation>>,
340 discovery: RwLock<Option<DiscoverResult>>,
342 roots: Arc<RwLock<Vec<Root>>>,
344 connected: Arc<AtomicBool>,
346 supports_session_recovery: bool,
348 init_params: RwLock<Option<(String, String)>>,
350 recovery_lock: Mutex<()>,
352 max_mrtr_rounds: usize,
354 response_cache: Arc<ClientResponseCache>,
356}
357
358pub struct McpClientBuilder {
378 capabilities: ClientCapabilities,
379 roots: Vec<Root>,
380 protocol_support: ProtocolSupport,
381 max_mrtr_rounds: usize,
382 cache_config: ClientCacheConfig,
383}
384
385impl McpClientBuilder {
386 pub fn new() -> Self {
388 Self {
389 capabilities: ClientCapabilities::default(),
390 roots: Vec::new(),
391 protocol_support: ProtocolSupport::stable(),
394 max_mrtr_rounds: 8,
395 cache_config: ClientCacheConfig::default(),
396 }
397 }
398
399 pub fn with_roots(mut self, roots: Vec<Root>) -> Self {
404 self.roots = roots;
405 self.capabilities.roots = Some(RootsCapability {
406 list_changed: true,
407 deprecated: None,
408 });
409 self
410 }
411
412 pub fn with_capabilities(mut self, capabilities: ClientCapabilities) -> Self {
414 self.capabilities = capabilities;
415 self
416 }
417
418 pub fn with_protocol_extension(mut self, extension: crate::ExtensionDeclaration) -> Self {
423 let (identifier, settings) = extension.into_parts();
424 self.capabilities
425 .extensions
426 .get_or_insert_default()
427 .insert(identifier, settings);
428 self
429 }
430
431 pub fn protocol_support(mut self, support: ProtocolSupport) -> Self {
437 self.protocol_support = support;
438 self
439 }
440
441 pub fn max_mrtr_rounds(mut self, rounds: usize) -> Self {
445 self.max_mrtr_rounds = rounds.max(1);
446 self
447 }
448
449 pub fn response_cache(mut self, config: ClientCacheConfig) -> Self {
451 self.cache_config = config;
452 self
453 }
454
455 pub fn disable_response_cache(mut self) -> Self {
457 self.cache_config.enabled = false;
458 self
459 }
460
461 pub fn with_sampling(mut self) -> Self {
468 self.capabilities.sampling = Some(SamplingCapability::default());
469 self
470 }
471
472 pub fn with_elicitation(mut self) -> Self {
479 self.capabilities.elicitation = Some(ElicitationCapability::default());
480 self
481 }
482
483 pub async fn connect<T, H>(self, transport: T, handler: H) -> Result<McpClient>
488 where
489 T: ClientTransport,
490 H: ClientHandler,
491 {
492 McpClient::connect_inner(
493 transport,
494 handler,
495 self.capabilities,
496 self.roots,
497 self.protocol_support,
498 self.max_mrtr_rounds,
499 self.cache_config,
500 )
501 .await
502 }
503
504 pub async fn connect_simple<T: ClientTransport>(self, transport: T) -> Result<McpClient> {
508 self.connect(transport, ()).await
509 }
510}
511
512impl Default for McpClientBuilder {
513 fn default() -> Self {
514 Self::new()
515 }
516}
517
518impl McpClient {
519 pub async fn connect<T: ClientTransport>(transport: T) -> Result<Self> {
523 McpClientBuilder::new().connect_simple(transport).await
524 }
525
526 pub async fn connect_with_handler<T, H>(transport: T, handler: H) -> Result<Self>
528 where
529 T: ClientTransport,
530 H: ClientHandler,
531 {
532 McpClientBuilder::new().connect(transport, handler).await
533 }
534
535 pub fn builder() -> McpClientBuilder {
537 McpClientBuilder::new()
538 }
539
540 async fn connect_inner<T, H>(
542 transport: T,
543 handler: H,
544 capabilities: ClientCapabilities,
545 roots: Vec<Root>,
546 protocol_support: ProtocolSupport,
547 max_mrtr_rounds: usize,
548 cache_config: ClientCacheConfig,
549 ) -> Result<Self>
550 where
551 T: ClientTransport,
552 H: ClientHandler,
553 {
554 let supports_session_recovery = transport.supports_session_recovery();
555 let (command_tx, command_rx) = mpsc::channel::<LoopCommand>(64);
556 let connected = Arc::new(AtomicBool::new(true));
557 let roots = Arc::new(RwLock::new(roots));
558 let response_cache = ClientResponseCache::new(cache_config);
559
560 let loop_connected = connected.clone();
561 let loop_roots = roots.clone();
562 let loop_response_cache = response_cache.clone();
563
564 let task = tokio::spawn(async move {
565 message_loop(
566 transport,
567 handler,
568 command_rx,
569 loop_connected,
570 loop_roots,
571 loop_response_cache,
572 )
573 .await;
574 });
575
576 Ok(Self {
577 command_tx,
578 task: Some(task),
579 initialized: AtomicBool::new(false),
580 server_info: RwLock::new(None),
581 capabilities,
582 protocol_support,
583 selected_protocol_version: RwLock::new(None),
584 client_info: RwLock::new(None),
585 discovery: RwLock::new(None),
586 roots,
587 connected,
588 supports_session_recovery,
589 init_params: RwLock::new(None),
590 recovery_lock: Mutex::new(()),
591 max_mrtr_rounds,
592 response_cache,
593 })
594 }
595
596 pub fn is_initialized(&self) -> bool {
598 self.initialized.load(Ordering::Acquire)
599 }
600
601 pub fn is_connected(&self) -> bool {
603 self.connected.load(Ordering::Acquire)
604 }
605
606 pub async fn server_info(&self) -> Option<InitializeResult> {
608 self.server_info.read().await.clone()
609 }
610
611 pub fn protocol_support(&self) -> &ProtocolSupport {
613 &self.protocol_support
614 }
615
616 pub async fn clear_response_cache(&self) {
618 self.response_cache.clear().await;
619 }
620
621 pub async fn set_cache_partition(&self, partition: impl Into<String>) {
627 self.response_cache.set_partition(partition.into()).await;
628 }
629
630 pub async fn response_cache_len(&self) -> usize {
632 self.response_cache.len().await
633 }
634
635 pub async fn discovery(&self) -> Option<DiscoverResult> {
637 self.discovery.read().await.clone()
638 }
639
640 pub async fn selected_protocol_version(&self) -> Option<String> {
642 self.selected_protocol_version.read().await.clone()
643 }
644
645 pub fn server_info_blocking(&self) -> Option<InitializeResult> {
651 self.server_info.try_read().ok()?.clone()
652 }
653
654 pub async fn initialize(
659 &self,
660 client_name: &str,
661 client_version: &str,
662 ) -> Result<InitializeResult> {
663 let params = InitializeParams {
664 protocol_version: crate::protocol::LATEST_PROTOCOL_VERSION.to_string(),
665 capabilities: self.capabilities.clone(),
666 client_info: Implementation {
667 name: client_name.to_string(),
668 version: client_version.to_string(),
669 ..Default::default()
670 },
671 meta: None,
672 };
673
674 let result: InitializeResult = self.send_request("initialize", ¶ms).await?;
675 *self.server_info.write().await = Some(result.clone());
676
677 *self.init_params.write().await =
679 Some((client_name.to_string(), client_version.to_string()));
680
681 self.send_notification("notifications/initialized", &serde_json::json!({}))
683 .await?;
684 self.initialized.store(true, Ordering::Release);
685
686 Ok(result)
687 }
688
689 #[cfg(any(feature = "protocol-2026-07-28", feature = "stateless"))]
697 pub async fn discover(
698 &self,
699 client_name: &str,
700 client_version: &str,
701 ) -> Result<DiscoverResult> {
702 use crate::protocol::PROTOCOL_VERSION_2026_07_28;
703
704 let client_info = Implementation {
705 name: client_name.to_string(),
706 version: client_version.to_string(),
707 ..Default::default()
708 };
709 *self.client_info.write().await = Some(client_info.clone());
710
711 let mut candidate = self
712 .protocol_support
713 .versions()
714 .iter()
715 .find(|version| version.as_str() == PROTOCOL_VERSION_2026_07_28)
716 .cloned()
717 .ok_or_else(|| {
718 Error::Transport(
719 "2026-07-28 is not enabled for this client; configure ProtocolSupport"
720 .to_string(),
721 )
722 })?;
723 let mut retried_unsupported = false;
724
725 loop {
726 let params = DiscoverParams {
727 meta: Some(self.request_meta_for(&candidate, &client_info)),
728 };
729 let cache_key = serde_json::to_string(&(
730 client_name,
731 client_version,
732 candidate.as_str(),
733 &self.capabilities,
734 ))
735 .expect("discovery cache key is serializable");
736 match self
737 .send_cacheable_request_when::<_, DiscoverResult>(
738 "server/discover",
739 &cache_key,
740 ¶ms,
741 true,
742 )
743 .await
744 {
745 Ok(result) => {
746 let selected = self
747 .protocol_support
748 .versions()
749 .iter()
750 .find(|version| {
751 result
752 .supported_versions
753 .iter()
754 .any(|supported| supported == *version)
755 })
756 .cloned()
757 .ok_or_else(|| {
758 Error::Transport(format!(
759 "server and client have no protocol version in common; server: {:?}, client: {:?}",
760 result.supported_versions,
761 self.protocol_support.versions()
762 ))
763 })?;
764 *self.selected_protocol_version.write().await = Some(selected);
765 *self.discovery.write().await = Some(result.clone());
766 self.initialized.store(true, Ordering::Release);
767 return Ok(result);
768 }
769 Err(Error::JsonRpc(error)) if error.code == -32022 && !retried_unsupported => {
770 let supported = error
771 .data
772 .as_ref()
773 .and_then(|data| data.get("supported"))
774 .and_then(serde_json::Value::as_array)
775 .ok_or_else(|| Error::JsonRpc(error.clone()))?;
776 candidate = self
777 .protocol_support
778 .versions()
779 .iter()
780 .find(|version| {
781 supported
782 .iter()
783 .any(|item| item.as_str() == Some(version.as_str()))
784 })
785 .cloned()
786 .ok_or_else(|| Error::JsonRpc(error.clone()))?;
787 retried_unsupported = true;
788 }
789 Err(error) => return Err(error),
790 }
791 }
792 }
793
794 pub async fn list_tools(&self) -> Result<ListToolsResult> {
796 self.list_tools_with_cursor(None).await
797 }
798
799 pub async fn call_tool(
807 &self,
808 name: &str,
809 arguments: serde_json::Value,
810 ) -> Result<CallToolResult> {
811 let mut input_responses = None;
812 let mut request_state = None;
813 let mut schema_retry_available = self.uses_final_protocol().await;
814 for round in 0..=self.max_mrtr_rounds {
815 let params = CallToolParams {
816 name: name.to_string(),
817 arguments: arguments.clone(),
818 input_responses: input_responses.take(),
819 request_state: request_state.take(),
820 meta: None,
821 task: None,
822 };
823 let outcome = self
824 .send_task_aware_tool_request_with_schema_retry(
825 ¶ms,
826 &mut schema_retry_available,
827 )
828 .await?;
829 match outcome {
830 TaskAwareCallToolOutcome::Complete(result) => return Ok(result),
831 TaskAwareCallToolOutcome::Task(created) => {
832 return self
833 .complete_final_task(&created.task.metadata.task_id)
834 .await;
835 }
836 TaskAwareCallToolOutcome::InputRequired(required) => {
837 if round == self.max_mrtr_rounds {
838 return Err(Error::Transport(format!(
839 "MRTR round limit ({}) exceeded for tools/call",
840 self.max_mrtr_rounds
841 )));
842 }
843 let requests = required.input_requests.ok_or_else(|| {
844 Error::Transport(
845 "input_required result has no requests the client can fulfil"
846 .to_string(),
847 )
848 })?;
849 input_responses = Some(self.resolve_input_requests(requests).await?);
850 request_state = required.request_state;
851 }
852 }
853 }
854 unreachable!("MRTR loop either completes or returns at the configured bound")
855 }
856
857 pub async fn call_tool_once(
859 &self,
860 name: &str,
861 arguments: serde_json::Value,
862 input_responses: Option<InputResponses>,
863 request_state: Option<String>,
864 ) -> Result<RequestOutcome<CallToolResult>> {
865 match self
866 .call_tool_once_task_aware(name, arguments, input_responses, request_state)
867 .await?
868 {
869 TaskAwareCallToolOutcome::Complete(result) => Ok(RequestOutcome::Complete(result)),
870 TaskAwareCallToolOutcome::InputRequired(required) => {
871 Ok(RequestOutcome::InputRequired(required))
872 }
873 TaskAwareCallToolOutcome::Task(created) => Err(Error::Transport(format!(
874 "tools/call returned task '{}'; use call_tool_once_task_aware for direct task lifecycle control",
875 created.task.metadata.task_id
876 ))),
877 }
878 }
879
880 pub async fn call_tool_once_task_aware(
886 &self,
887 name: &str,
888 arguments: serde_json::Value,
889 input_responses: Option<InputResponses>,
890 request_state: Option<String>,
891 ) -> Result<TaskAwareCallToolOutcome> {
892 self.ensure_initialized()?;
893 let params = CallToolParams {
894 name: name.to_string(),
895 arguments,
896 input_responses,
897 request_state,
898 meta: None,
899 task: None,
900 };
901 let mut schema_retry_available = self.uses_final_protocol().await;
902 self.send_task_aware_tool_request_with_schema_retry(¶ms, &mut schema_retry_available)
903 .await
904 }
905
906 pub async fn call_tool_as_task(
920 &self,
921 name: &str,
922 arguments: serde_json::Value,
923 ttl_ms: Option<u64>,
924 ) -> Result<CreateTaskResult> {
925 self.ensure_initialized()?;
926 if self.uses_final_protocol().await {
927 if ttl_ms.is_some() {
928 return Err(Error::Transport(
929 "ttl_ms is server-selected by the final Tasks extension".to_string(),
930 ));
931 }
932 let params = CallToolParams {
933 name: name.to_string(),
934 arguments,
935 input_responses: None,
936 request_state: None,
937 meta: None,
938 task: None,
939 };
940 let mut schema_retry_available = true;
941 return match self
942 .send_task_aware_tool_request_with_schema_retry(
943 ¶ms,
944 &mut schema_retry_available,
945 )
946 .await?
947 {
948 TaskAwareCallToolOutcome::Task(created) => {
949 Ok(Self::legacy_create_task_from_final(created))
950 }
951 TaskAwareCallToolOutcome::Complete(_) => Err(Error::Transport(
952 "server completed tools/call synchronously; final task creation is server-directed"
953 .to_string(),
954 )),
955 TaskAwareCallToolOutcome::InputRequired(_) => Err(Error::Transport(
956 "server requested input instead of creating a task".to_string(),
957 )),
958 };
959 }
960
961 let params = CallToolParams {
962 name: name.to_string(),
963 arguments,
964 input_responses: None,
965 request_state: None,
966 meta: None,
967 task: Some(TaskRequestParams { ttl: ttl_ms }),
968 };
969 let mut schema_retry_available = self.uses_final_protocol().await;
970 self.send_tool_request_with_schema_retry(¶ms, &mut schema_retry_available)
971 .await
972 }
973
974 pub async fn task_get(&self, task_id: &str) -> Result<TaskObject> {
981 self.ensure_initialized()?;
982 if self.uses_final_protocol().await {
983 return Self::legacy_task_from_final(self.task_get_detailed(task_id).await?);
984 }
985 let params = GetTaskInfoParams {
986 task_id: task_id.to_string(),
987 meta: None,
988 };
989 self.send_request("tasks/get", ¶ms).await
990 }
991
992 pub async fn task_get_detailed(&self, task_id: &str) -> Result<crate::tasks::GetTaskResult> {
998 self.ensure_initialized()?;
999 if !self.uses_final_protocol().await {
1000 return Err(Error::Transport(
1001 "task_get_detailed requires the 2026-07-28 client lifecycle".to_string(),
1002 ));
1003 }
1004 let params = crate::tasks::GetTaskParams {
1005 task_id: task_id.to_string(),
1006 meta: None,
1007 };
1008 self.send_request("tasks/get", ¶ms).await
1009 }
1010
1011 pub async fn task_cancel(&self, task_id: &str, reason: Option<String>) -> Result<()> {
1020 self.ensure_initialized()?;
1021 if self.uses_final_protocol().await {
1022 let params = crate::tasks::CancelTaskParams {
1023 task_id: task_id.to_string(),
1024 meta: None,
1025 };
1026 let _ack: crate::tasks::CancelTaskResult =
1027 self.send_request("tasks/cancel", ¶ms).await?;
1028 return Ok(());
1029 }
1030 let params = CancelTaskParams {
1031 task_id: task_id.to_string(),
1032 reason,
1033 meta: None,
1034 };
1035 let _ack: serde_json::Value = self.send_request("tasks/cancel", ¶ms).await?;
1036 Ok(())
1037 }
1038
1039 pub async fn task_update(&self, task_id: &str, input_responses: InputResponses) -> Result<()> {
1055 self.ensure_initialized()?;
1056 if self.uses_final_protocol().await {
1057 let params = crate::tasks::UpdateTaskParams {
1058 task_id: task_id.to_string(),
1059 input_responses,
1060 meta: None,
1061 };
1062 let _ack: crate::tasks::UpdateTaskResult =
1063 self.send_request("tasks/update", ¶ms).await?;
1064 return Ok(());
1065 }
1066 let input_responses = input_responses
1067 .into_iter()
1068 .map(|(key, response)| serde_json::to_value(response).map(|value| (key, value)))
1069 .collect::<std::result::Result<_, _>>()?;
1070 let params = UpdateTaskParams {
1071 task_id: task_id.to_string(),
1072 input_responses,
1073 meta: None,
1074 };
1075 let _ack: serde_json::Value = self.send_request("tasks/update", ¶ms).await?;
1076 Ok(())
1077 }
1078
1079 pub async fn task_wait(&self, task_id: &str) -> Result<TaskObject> {
1088 if self.uses_final_protocol().await {
1089 let result = self.wait_for_final_task(task_id).await?;
1090 return Self::legacy_task_from_final(result);
1091 }
1092 loop {
1093 let task = self.task_get(task_id).await?;
1094 if task.status.is_terminal() {
1095 return Ok(task);
1096 }
1097 let interval_ms = task.poll_interval.unwrap_or(1000).clamp(50, 30_000);
1098 tokio::time::sleep(std::time::Duration::from_millis(interval_ms)).await;
1099 }
1100 }
1101
1102 pub async fn list_resources(&self) -> Result<ListResourcesResult> {
1104 self.list_resources_with_cursor(None).await
1105 }
1106
1107 pub async fn read_resource(&self, uri: &str) -> Result<ReadResourceResult> {
1109 self.ensure_initialized()?;
1110 let cache_enabled = self.uses_final_protocol().await && self.response_cache.enabled();
1111 let generation = if cache_enabled {
1112 self.response_cache
1113 .capture_generation("resources/read", uri)
1114 .await
1115 } else {
1116 0
1117 };
1118 let mut generation_active = cache_enabled;
1119 let mut stale = None;
1120 if cache_enabled {
1121 match self.response_cache.lookup("resources/read", uri).await {
1122 CacheLookup::Fresh(value) => {
1123 if let Some(result) = decode_cached(&value, "resources/read") {
1124 self.response_cache
1125 .release_generation("resources/read", uri)
1126 .await;
1127 return Ok(result);
1128 }
1129 self.response_cache.evict_resource(uri).await;
1130 }
1131 CacheLookup::Stale(value) => stale = Some(value),
1132 CacheLookup::Miss => {}
1133 }
1134 }
1135
1136 let mut input_responses = None;
1137 let mut request_state = None;
1138 let mut followed_input_required = false;
1139 for round in 0..=self.max_mrtr_rounds {
1140 let outcome = match self
1141 .read_resource_once(uri, input_responses.take(), request_state.take())
1142 .await
1143 {
1144 Ok(outcome) => outcome,
1145 Err(error) => {
1146 if generation_active {
1147 self.response_cache
1148 .release_generation("resources/read", uri)
1149 .await;
1150 }
1151 if self.response_cache.serve_stale_on_error()
1152 && let Some(value) = stale.as_ref()
1153 && let Some(result) = decode_cached(value, "resources/read")
1154 {
1155 tracing::warn!(
1156 uri,
1157 error = %error,
1158 "Serving stale resources/read response after refresh failure"
1159 );
1160 return Ok(result);
1161 }
1162 return Err(error);
1163 }
1164 };
1165 match outcome {
1166 RequestOutcome::Complete(result) => {
1167 if generation_active && !followed_input_required {
1168 self.write_cached_response("resources/read", uri, generation, &result)
1169 .await;
1170 }
1171 return Ok(result);
1172 }
1173 RequestOutcome::InputRequired(required) => {
1174 if !followed_input_required {
1175 followed_input_required = true;
1176 if generation_active {
1177 self.response_cache
1178 .release_generation("resources/read", uri)
1179 .await;
1180 generation_active = false;
1181 }
1182 }
1183 if round == self.max_mrtr_rounds {
1184 return Err(Error::Transport(format!(
1185 "MRTR round limit ({}) exceeded for resources/read",
1186 self.max_mrtr_rounds
1187 )));
1188 }
1189 let requests = required.input_requests.ok_or_else(|| {
1190 Error::Transport(
1191 "input_required result has no requests the client can fulfil"
1192 .to_string(),
1193 )
1194 })?;
1195 input_responses = Some(self.resolve_input_requests(requests).await?);
1196 request_state = required.request_state;
1197 }
1198 }
1199 }
1200 unreachable!("MRTR loop either completes or returns at the configured bound")
1201 }
1202
1203 pub async fn read_resource_once(
1205 &self,
1206 uri: &str,
1207 input_responses: Option<InputResponses>,
1208 request_state: Option<String>,
1209 ) -> Result<RequestOutcome<ReadResourceResult>> {
1210 self.ensure_initialized()?;
1211 let params = ReadResourceParams {
1212 uri: uri.to_string(),
1213 input_responses,
1214 request_state,
1215 meta: None,
1216 };
1217 self.send_request("resources/read", ¶ms).await
1218 }
1219
1220 pub async fn listen_subscriptions(
1230 &self,
1231 notifications: SubscriptionFilter,
1232 ) -> Result<SubscriptionHandle> {
1233 self.ensure_initialized()?;
1234 if !self.uses_final_protocol().await {
1235 return Err(Error::Transport(
1236 "subscriptions/listen requires the 2026-07-28 protocol".to_string(),
1237 ));
1238 }
1239
1240 let params = SubscriptionsListenParams {
1241 notifications: Some(notifications),
1242 meta: None,
1243 };
1244 let params = serde_json::to_value(params).map_err(|error| {
1245 Error::Transport(format!(
1246 "failed to serialize subscriptions/listen params: {error}"
1247 ))
1248 })?;
1249 let params = self.with_final_request_meta(params).await?;
1250 let (id_tx, id_rx) = oneshot::channel();
1251 let (acknowledgment_tx, acknowledgment_rx) = oneshot::channel();
1252 let (response_tx, response_rx) = oneshot::channel();
1253 self.command_tx
1254 .send(LoopCommand::StartSubscription {
1255 params,
1256 id_tx,
1257 acknowledgment_tx,
1258 response_tx,
1259 })
1260 .await
1261 .map_err(|_| Error::Transport("connection closed".to_string()))?;
1262 let request_id = id_rx
1263 .await
1264 .map_err(|_| Error::Transport("connection closed".to_string()))?;
1265
1266 Ok(SubscriptionHandle {
1267 request_id,
1268 command_tx: self.command_tx.clone(),
1269 acknowledgment_rx: Some(acknowledgment_rx),
1270 response_rx: Some(response_rx),
1271 active: true,
1272 })
1273 }
1274
1275 pub async fn subscribe_resource(&self, uri: &str) -> Result<()> {
1284 self.ensure_initialized()?;
1285 if self.uses_final_protocol().await {
1286 return Err(Error::Transport(
1287 "resources/subscribe was removed in 2026-07-28; use subscriptions/listen"
1288 .to_string(),
1289 ));
1290 }
1291 let _: serde_json::Value = self
1292 .send_request("resources/subscribe", &serde_json::json!({ "uri": uri }))
1293 .await?;
1294 Ok(())
1295 }
1296
1297 pub async fn unsubscribe_resource(&self, uri: &str) -> Result<()> {
1299 self.ensure_initialized()?;
1300 if self.uses_final_protocol().await {
1301 return Err(Error::Transport(
1302 "resources/unsubscribe was removed in 2026-07-28; use subscriptions/listen"
1303 .to_string(),
1304 ));
1305 }
1306 let _: serde_json::Value = self
1307 .send_request("resources/unsubscribe", &serde_json::json!({ "uri": uri }))
1308 .await?;
1309 Ok(())
1310 }
1311
1312 pub async fn list_prompts(&self) -> Result<ListPromptsResult> {
1314 self.list_prompts_with_cursor(None).await
1315 }
1316
1317 pub async fn list_tools_with_cursor(&self, cursor: Option<String>) -> Result<ListToolsResult> {
1319 self.ensure_initialized()?;
1320 let cache_key = pagination_cache_key(cursor.as_deref());
1321 self.send_cacheable_request(
1322 "tools/list",
1323 &cache_key,
1324 &ListToolsParams { cursor, meta: None },
1325 )
1326 .await
1327 }
1328
1329 pub async fn list_resources_with_cursor(
1331 &self,
1332 cursor: Option<String>,
1333 ) -> Result<ListResourcesResult> {
1334 self.ensure_initialized()?;
1335 let cache_key = pagination_cache_key(cursor.as_deref());
1336 self.send_cacheable_request(
1337 "resources/list",
1338 &cache_key,
1339 &ListResourcesParams { cursor, meta: None },
1340 )
1341 .await
1342 }
1343
1344 pub async fn list_resource_templates(&self) -> Result<ListResourceTemplatesResult> {
1346 self.list_resource_templates_with_cursor(None).await
1347 }
1348
1349 pub async fn list_resource_templates_with_cursor(
1351 &self,
1352 cursor: Option<String>,
1353 ) -> Result<ListResourceTemplatesResult> {
1354 self.ensure_initialized()?;
1355 let cache_key = pagination_cache_key(cursor.as_deref());
1356 self.send_cacheable_request(
1357 "resources/templates/list",
1358 &cache_key,
1359 &ListResourceTemplatesParams { cursor, meta: None },
1360 )
1361 .await
1362 }
1363
1364 pub async fn list_prompts_with_cursor(
1366 &self,
1367 cursor: Option<String>,
1368 ) -> Result<ListPromptsResult> {
1369 self.ensure_initialized()?;
1370 let cache_key = pagination_cache_key(cursor.as_deref());
1371 self.send_cacheable_request(
1372 "prompts/list",
1373 &cache_key,
1374 &ListPromptsParams { cursor, meta: None },
1375 )
1376 .await
1377 }
1378
1379 pub async fn list_all_tools(&self) -> Result<Vec<ToolDefinition>> {
1381 let mut all = Vec::new();
1382 let mut cursor = None;
1383 loop {
1384 let result = self.list_tools_with_cursor(cursor).await?;
1385 all.extend(result.tools);
1386 match result.next_cursor {
1387 Some(c) => cursor = Some(c),
1388 None => break,
1389 }
1390 }
1391 Ok(all)
1392 }
1393
1394 pub async fn list_all_resources(&self) -> Result<Vec<ResourceDefinition>> {
1396 let mut all = Vec::new();
1397 let mut cursor = None;
1398 loop {
1399 let result = self.list_resources_with_cursor(cursor).await?;
1400 all.extend(result.resources);
1401 match result.next_cursor {
1402 Some(c) => cursor = Some(c),
1403 None => break,
1404 }
1405 }
1406 Ok(all)
1407 }
1408
1409 pub async fn list_all_resource_templates(&self) -> Result<Vec<ResourceTemplateDefinition>> {
1411 let mut all = Vec::new();
1412 let mut cursor = None;
1413 loop {
1414 let result = self.list_resource_templates_with_cursor(cursor).await?;
1415 all.extend(result.resource_templates);
1416 match result.next_cursor {
1417 Some(c) => cursor = Some(c),
1418 None => break,
1419 }
1420 }
1421 Ok(all)
1422 }
1423
1424 pub async fn list_all_prompts(&self) -> Result<Vec<PromptDefinition>> {
1426 let mut all = Vec::new();
1427 let mut cursor = None;
1428 loop {
1429 let result = self.list_prompts_with_cursor(cursor).await?;
1430 all.extend(result.prompts);
1431 match result.next_cursor {
1432 Some(c) => cursor = Some(c),
1433 None => break,
1434 }
1435 }
1436 Ok(all)
1437 }
1438
1439 pub async fn call_tool_text(&self, name: &str, arguments: serde_json::Value) -> Result<String> {
1447 let result = self.call_tool(name, arguments).await?;
1448 if result.is_error {
1449 return Err(Error::Internal(result.all_text()));
1450 }
1451 Ok(result.all_text())
1452 }
1453
1454 pub async fn get_prompt(
1456 &self,
1457 name: &str,
1458 arguments: Option<std::collections::HashMap<String, String>>,
1459 ) -> Result<GetPromptResult> {
1460 let arguments = arguments.unwrap_or_default();
1461 let mut input_responses = None;
1462 let mut request_state = None;
1463 for round in 0..=self.max_mrtr_rounds {
1464 match self
1465 .get_prompt_once(
1466 name,
1467 arguments.clone(),
1468 input_responses.take(),
1469 request_state.take(),
1470 )
1471 .await?
1472 {
1473 RequestOutcome::Complete(result) => return Ok(result),
1474 RequestOutcome::InputRequired(required) => {
1475 if round == self.max_mrtr_rounds {
1476 return Err(Error::Transport(format!(
1477 "MRTR round limit ({}) exceeded for prompts/get",
1478 self.max_mrtr_rounds
1479 )));
1480 }
1481 let requests = required.input_requests.ok_or_else(|| {
1482 Error::Transport(
1483 "input_required result has no requests the client can fulfil"
1484 .to_string(),
1485 )
1486 })?;
1487 input_responses = Some(self.resolve_input_requests(requests).await?);
1488 request_state = required.request_state;
1489 }
1490 }
1491 }
1492 unreachable!("MRTR loop either completes or returns at the configured bound")
1493 }
1494
1495 pub async fn get_prompt_once(
1497 &self,
1498 name: &str,
1499 arguments: std::collections::HashMap<String, String>,
1500 input_responses: Option<InputResponses>,
1501 request_state: Option<String>,
1502 ) -> Result<RequestOutcome<GetPromptResult>> {
1503 self.ensure_initialized()?;
1504 let params = GetPromptParams {
1505 name: name.to_string(),
1506 arguments,
1507 input_responses,
1508 request_state,
1509 meta: None,
1510 };
1511 self.send_request("prompts/get", ¶ms).await
1512 }
1513
1514 pub async fn ping(&self) -> Result<()> {
1516 if self.uses_final_protocol().await {
1517 return Err(Error::Transport(
1518 "ping was removed from the 2026-07-28 core protocol".to_string(),
1519 ));
1520 }
1521 let _: serde_json::Value = self.send_request("ping", &serde_json::json!({})).await?;
1522 Ok(())
1523 }
1524
1525 pub async fn complete(
1527 &self,
1528 reference: CompletionReference,
1529 argument_name: &str,
1530 argument_value: &str,
1531 ) -> Result<CompleteResult> {
1532 self.ensure_initialized()?;
1533 let params = CompleteParams {
1534 reference,
1535 argument: CompletionArgument::new(argument_name, argument_value),
1536 context: None,
1537 meta: None,
1538 };
1539 self.send_request("completion/complete", ¶ms).await
1540 }
1541
1542 pub async fn complete_prompt_arg(
1544 &self,
1545 prompt_name: &str,
1546 argument_name: &str,
1547 argument_value: &str,
1548 ) -> Result<CompleteResult> {
1549 self.complete(
1550 CompletionReference::prompt(prompt_name),
1551 argument_name,
1552 argument_value,
1553 )
1554 .await
1555 }
1556
1557 pub async fn complete_resource_uri(
1559 &self,
1560 resource_uri: &str,
1561 argument_name: &str,
1562 argument_value: &str,
1563 ) -> Result<CompleteResult> {
1564 self.complete(
1565 CompletionReference::resource(resource_uri),
1566 argument_name,
1567 argument_value,
1568 )
1569 .await
1570 }
1571
1572 pub async fn request<P: serde::Serialize, R: serde::de::DeserializeOwned>(
1574 &self,
1575 method: &str,
1576 params: &P,
1577 ) -> Result<R> {
1578 self.send_request(method, params).await
1579 }
1580
1581 pub async fn notify<P: serde::Serialize>(&self, method: &str, params: &P) -> Result<()> {
1583 self.send_notification(method, params).await
1584 }
1585
1586 pub async fn roots(&self) -> Vec<Root> {
1588 self.roots.read().await.clone()
1589 }
1590
1591 pub async fn set_roots(&self, roots: Vec<Root>) -> Result<()> {
1593 *self.roots.write().await = roots;
1594 if self.is_initialized() && !self.uses_final_protocol().await {
1595 self.send_notification(notifications::ROOTS_LIST_CHANGED, &serde_json::json!({}))
1596 .await?;
1597 }
1598 Ok(())
1599 }
1600
1601 pub async fn add_root(&self, root: Root) -> Result<()> {
1603 self.roots.write().await.push(root);
1604 if self.is_initialized() && !self.uses_final_protocol().await {
1605 self.send_notification(notifications::ROOTS_LIST_CHANGED, &serde_json::json!({}))
1606 .await?;
1607 }
1608 Ok(())
1609 }
1610
1611 pub async fn remove_root(&self, uri: &str) -> Result<bool> {
1613 let mut roots = self.roots.write().await;
1614 let initial_len = roots.len();
1615 roots.retain(|r| r.uri != uri);
1616 let removed = roots.len() < initial_len;
1617 drop(roots);
1618
1619 if removed && self.is_initialized() && !self.uses_final_protocol().await {
1620 self.send_notification(notifications::ROOTS_LIST_CHANGED, &serde_json::json!({}))
1621 .await?;
1622 }
1623 Ok(removed)
1624 }
1625
1626 pub async fn list_roots(&self) -> ListRootsResult {
1628 ListRootsResult {
1629 roots: self.roots.read().await.clone(),
1630 meta: None,
1631 }
1632 }
1633
1634 pub async fn shutdown(mut self) -> Result<()> {
1636 let _ = self.command_tx.send(LoopCommand::Shutdown).await;
1637 if let Some(task) = self.task.take() {
1638 let _ = task.await;
1639 }
1640 Ok(())
1641 }
1642
1643 async fn send_task_aware_tool_request_with_schema_retry(
1646 &self,
1647 params: &CallToolParams,
1648 retry_available: &mut bool,
1649 ) -> Result<TaskAwareCallToolOutcome> {
1650 self.send_tool_request_with_schema_retry(params, retry_available)
1651 .await
1652 }
1653
1654 async fn wait_for_final_task(&self, task_id: &str) -> Result<crate::tasks::GetTaskResult> {
1655 loop {
1656 let task = self.task_get_detailed(task_id).await?;
1657 match task.task.status() {
1658 crate::protocol::TaskStatus::Completed
1659 | crate::protocol::TaskStatus::Failed
1660 | crate::protocol::TaskStatus::Cancelled => return Ok(task),
1661 crate::protocol::TaskStatus::InputRequired => {
1662 let requests = task.task.input_requests().cloned().ok_or_else(|| {
1663 Error::Transport(format!(
1664 "task '{task_id}' is input_required without inputRequests"
1665 ))
1666 })?;
1667 if requests.is_empty() {
1668 return Err(Error::Transport(format!(
1669 "task '{task_id}' is input_required without inputRequests"
1670 )));
1671 }
1672 let responses = self.resolve_input_requests(requests).await?;
1673 self.task_update(task_id, responses).await?;
1674 }
1675 crate::protocol::TaskStatus::Working => {
1676 let interval_ms = task
1677 .task
1678 .metadata()
1679 .poll_interval_ms
1680 .unwrap_or(1000)
1681 .clamp(50, 30_000);
1682 tokio::time::sleep(std::time::Duration::from_millis(interval_ms)).await;
1683 }
1684 _ => {
1685 return Err(Error::Transport(format!(
1686 "task '{task_id}' returned an unsupported status"
1687 )));
1688 }
1689 }
1690 }
1691 }
1692
1693 async fn complete_final_task(&self, task_id: &str) -> Result<CallToolResult> {
1694 let task = self.wait_for_final_task(task_id).await?;
1695 match task.task.status() {
1696 crate::protocol::TaskStatus::Completed => {
1697 let result = task.task.result().cloned().ok_or_else(|| {
1698 Error::Transport(format!(
1699 "completed task '{task_id}' did not contain a result"
1700 ))
1701 })?;
1702 serde_json::from_value(serde_json::Value::Object(result)).map_err(|error| {
1703 Error::Transport(format!(
1704 "failed to deserialize completed task '{task_id}' result: {error}"
1705 ))
1706 })
1707 }
1708 crate::protocol::TaskStatus::Failed => {
1709 let error = task.task.error().cloned().unwrap_or_else(|| {
1710 JsonRpcError::internal_error(format!(
1711 "task '{task_id}' failed without an error payload"
1712 ))
1713 });
1714 Err(Error::JsonRpc(error))
1715 }
1716 crate::protocol::TaskStatus::Cancelled => {
1717 Err(Error::Transport(format!("task '{task_id}' was cancelled")))
1718 }
1719 _ => Err(Error::Transport(format!(
1720 "task '{task_id}' did not reach a terminal state"
1721 ))),
1722 }
1723 }
1724
1725 fn legacy_create_task_from_final(created: crate::tasks::CreateTaskResult) -> CreateTaskResult {
1726 let metadata = created.task.metadata;
1727 CreateTaskResult {
1728 task: TaskObject {
1729 task_id: metadata.task_id,
1730 status: created.task.status,
1731 status_message: metadata.status_message,
1732 created_at: metadata.created_at,
1733 last_updated_at: metadata.last_updated_at,
1734 ttl: metadata.ttl_ms,
1735 poll_interval: metadata.poll_interval_ms,
1736 result: None,
1737 error: None,
1738 meta: None,
1739 },
1740 meta: created.meta.map(serde_json::Value::Object),
1741 }
1742 }
1743
1744 fn legacy_task_from_final(result: crate::tasks::GetTaskResult) -> Result<TaskObject> {
1745 let task = &result.task;
1746 let metadata = task.metadata();
1747 let completed = task
1748 .result()
1749 .cloned()
1750 .map(serde_json::Value::Object)
1751 .map(serde_json::from_value)
1752 .transpose()
1753 .map_err(|error| {
1754 Error::Transport(format!(
1755 "failed to deserialize task '{}' result: {error}",
1756 metadata.task_id
1757 ))
1758 })?;
1759 Ok(TaskObject {
1760 task_id: metadata.task_id.clone(),
1761 status: task.status(),
1762 status_message: metadata.status_message.clone(),
1763 created_at: metadata.created_at.clone(),
1764 last_updated_at: metadata.last_updated_at.clone(),
1765 ttl: metadata.ttl_ms,
1766 poll_interval: metadata.poll_interval_ms,
1767 result: completed,
1768 error: task.error().cloned(),
1769 meta: result.meta.map(serde_json::Value::Object),
1770 })
1771 }
1772
1773 async fn send_tool_request_with_schema_retry<R>(
1774 &self,
1775 params: &CallToolParams,
1776 retry_available: &mut bool,
1777 ) -> Result<R>
1778 where
1779 R: serde::de::DeserializeOwned,
1780 {
1781 match self.send_request("tools/call", params).await {
1782 Err(error) if *retry_available && is_stale_tool_schema_error(&error) => {
1783 *retry_available = false;
1784 self.response_cache.evict_method("tools/list").await;
1785 tracing::info!(
1786 tool = params.name,
1787 error = %error,
1788 "Refreshing tools/list before one stale-schema retry"
1789 );
1790 if let Err(refresh_error) = self.list_tools().await {
1791 tracing::warn!(
1792 tool = params.name,
1793 error = %refresh_error,
1794 "Could not refresh tools/list after stale-schema rejection"
1795 );
1796 return Err(error);
1797 }
1798 self.send_request("tools/call", params).await
1799 }
1800 result => result,
1801 }
1802 }
1803
1804 async fn send_cacheable_request<P, R>(
1805 &self,
1806 method: &str,
1807 cache_key: &str,
1808 params: &P,
1809 ) -> Result<R>
1810 where
1811 P: serde::Serialize,
1812 R: CacheableResponse,
1813 {
1814 let cache_allowed = self.uses_final_protocol().await;
1815 self.send_cacheable_request_when(method, cache_key, params, cache_allowed)
1816 .await
1817 }
1818
1819 async fn send_cacheable_request_when<P, R>(
1820 &self,
1821 method: &str,
1822 cache_key: &str,
1823 params: &P,
1824 cache_allowed: bool,
1825 ) -> Result<R>
1826 where
1827 P: serde::Serialize,
1828 R: CacheableResponse,
1829 {
1830 if !cache_allowed || !self.response_cache.enabled() {
1831 return self.send_request(method, params).await;
1832 }
1833
1834 let generation = self
1835 .response_cache
1836 .capture_generation(method, cache_key)
1837 .await;
1838 let mut stale = None;
1839 match self.response_cache.lookup(method, cache_key).await {
1840 CacheLookup::Fresh(value) => {
1841 if let Some(result) = decode_cached(&value, method) {
1842 self.response_cache
1843 .release_generation(method, cache_key)
1844 .await;
1845 tracing::debug!(method, "Serving fresh response from cache");
1846 return Ok(result);
1847 }
1848 self.response_cache.evict_method(method).await;
1849 }
1850 CacheLookup::Stale(value) => stale = Some(value),
1851 CacheLookup::Miss => {}
1852 }
1853
1854 match self.send_request(method, params).await {
1855 Ok(result) => {
1856 self.write_cached_response(method, cache_key, generation, &result)
1857 .await;
1858 Ok(result)
1859 }
1860 Err(error) => {
1861 self.response_cache
1862 .release_generation(method, cache_key)
1863 .await;
1864 if self.response_cache.serve_stale_on_error()
1865 && let Some(value) = stale.as_ref()
1866 && let Some(result) = decode_cached(value, method)
1867 {
1868 tracing::warn!(
1869 method,
1870 error = %error,
1871 "Serving stale response after cache refresh failure"
1872 );
1873 return Ok(result);
1874 }
1875 Err(error)
1876 }
1877 }
1878 }
1879
1880 async fn write_cached_response<R: CacheableResponse>(
1881 &self,
1882 method: &str,
1883 cache_key: &str,
1884 generation: u64,
1885 result: &R,
1886 ) {
1887 match serde_json::to_value(result) {
1888 Ok(value) => {
1889 self.response_cache
1890 .write(
1891 method,
1892 cache_key,
1893 generation,
1894 value,
1895 result.ttl_ms(),
1896 result.cache_scope(),
1897 )
1898 .await;
1899 }
1900 Err(error) => {
1901 self.response_cache
1902 .release_generation(method, cache_key)
1903 .await;
1904 tracing::warn!(
1905 method,
1906 error = %error,
1907 "Skipping response-cache write after serialization failure"
1908 );
1909 }
1910 }
1911 }
1912
1913 async fn send_request<P: serde::Serialize, R: serde::de::DeserializeOwned>(
1914 &self,
1915 method: &str,
1916 params: &P,
1917 ) -> Result<R> {
1918 let final_protocol = self.uses_final_protocol().await;
1919 match self.send_request_once(method, params).await {
1920 Err(Error::SessionExpired)
1921 if self.supports_session_recovery && !final_protocol && method != "initialize" =>
1922 {
1923 tracing::info!(method = %method, "Session expired, attempting recovery");
1924 self.recover_session().await?;
1925 self.send_request_once(method, params).await
1926 }
1927 other => other,
1928 }
1929 }
1930
1931 async fn send_request_once<P: serde::Serialize, R: serde::de::DeserializeOwned>(
1932 &self,
1933 method: &str,
1934 params: &P,
1935 ) -> Result<R> {
1936 self.ensure_connected()?;
1937 let params_value = serde_json::to_value(params)
1938 .map_err(|e| Error::Transport(format!("Failed to serialize params: {}", e)))?;
1939 let params_value = self.with_final_request_meta(params_value).await?;
1940
1941 let (response_tx, response_rx) = oneshot::channel();
1942 self.command_tx
1943 .send(LoopCommand::Request {
1944 method: method.to_string(),
1945 params: params_value,
1946 response_tx,
1947 })
1948 .await
1949 .map_err(|_| Error::Transport("Connection closed".to_string()))?;
1950
1951 let result = response_rx
1952 .await
1953 .map_err(|_| Error::Transport("Connection closed".to_string()))??;
1954
1955 serde_json::from_value(result)
1956 .map_err(|e| Error::Transport(format!("Failed to deserialize response: {}", e)))
1957 }
1958
1959 async fn recover_session(&self) -> Result<()> {
1961 let _guard = self.recovery_lock.lock().await;
1963
1964 let init_params = self.init_params.read().await.clone();
1967 let (client_name, client_version) = match init_params {
1968 Some(params) => params,
1969 None => {
1970 return Err(Error::Transport(
1971 "Cannot recover: never initialized".to_string(),
1972 ));
1973 }
1974 };
1975
1976 let (done_tx, done_rx) = oneshot::channel();
1978 self.command_tx
1979 .send(LoopCommand::ResetSession { done_tx })
1980 .await
1981 .map_err(|_| Error::Transport("Connection closed".to_string()))?;
1982 done_rx
1983 .await
1984 .map_err(|_| Error::Transport("Connection closed during recovery".to_string()))?;
1985
1986 self.initialized.store(false, Ordering::Release);
1988 *self.server_info.write().await = None;
1989
1990 tracing::info!("Re-initializing session after expiry");
1992 let params = InitializeParams {
1993 protocol_version: crate::protocol::LATEST_PROTOCOL_VERSION.to_string(),
1994 capabilities: self.capabilities.clone(),
1995 client_info: Implementation {
1996 name: client_name,
1997 version: client_version,
1998 ..Default::default()
1999 },
2000 meta: None,
2001 };
2002
2003 let result: InitializeResult = self.send_request_once("initialize", ¶ms).await?;
2004 *self.server_info.write().await = Some(result);
2005
2006 self.send_notification("notifications/initialized", &serde_json::json!({}))
2007 .await?;
2008 self.initialized.store(true, Ordering::Release);
2009
2010 Ok(())
2011 }
2012
2013 async fn send_notification<P: serde::Serialize>(&self, method: &str, params: &P) -> Result<()> {
2014 self.ensure_connected()?;
2015 let params_value = serde_json::to_value(params)
2016 .map_err(|e| Error::Transport(format!("Failed to serialize params: {}", e)))?;
2017 let params_value = self.with_final_request_meta(params_value).await?;
2018
2019 self.command_tx
2020 .send(LoopCommand::Notify {
2021 method: method.to_string(),
2022 params: params_value,
2023 })
2024 .await
2025 .map_err(|_| Error::Transport("Connection closed".to_string()))?;
2026
2027 Ok(())
2028 }
2029
2030 async fn resolve_input_requests(&self, requests: InputRequests) -> Result<InputResponses> {
2031 if !self.uses_final_protocol().await {
2032 return Err(Error::Transport(
2033 "input_required results require the 2026-07-28 client lifecycle".to_string(),
2034 ));
2035 }
2036 for request in requests.values() {
2037 let declared = match request {
2038 InputRequest::CreateMessage(_) => self.capabilities.sampling.is_some(),
2039 InputRequest::ListRoots(_) => self.capabilities.roots.is_some(),
2040 InputRequest::Elicit(_) => self.capabilities.elicitation.is_some(),
2041 _ => false,
2042 };
2043 if !declared {
2044 return Err(Error::Transport(format!(
2045 "server requested undeclared MRTR input capability: {}",
2046 request.method_name()
2047 )));
2048 }
2049 }
2050
2051 let (response_tx, response_rx) = oneshot::channel();
2052 self.command_tx
2053 .send(LoopCommand::ResolveInputs {
2054 requests,
2055 response_tx,
2056 })
2057 .await
2058 .map_err(|_| Error::Transport("Connection closed".to_string()))?;
2059 response_rx
2060 .await
2061 .map_err(|_| Error::Transport("Connection closed".to_string()))?
2062 }
2063
2064 async fn uses_final_protocol(&self) -> bool {
2065 self.selected_protocol_version.read().await.as_deref()
2066 == Some(crate::protocol::PROTOCOL_VERSION_2026_07_28)
2067 }
2068
2069 fn request_meta_for(&self, version: &str, client_info: &Implementation) -> RequestMeta {
2070 RequestMeta {
2071 progress_token: None,
2072 protocol_version: Some(version.to_string()),
2073 client_info: Some(client_info.clone()),
2074 client_capabilities: Some(self.capabilities.clone()),
2075 log_level: None,
2076 }
2077 }
2078
2079 async fn with_final_request_meta(
2080 &self,
2081 mut params: serde_json::Value,
2082 ) -> Result<serde_json::Value> {
2083 let Some(version) = self.selected_protocol_version.read().await.clone() else {
2084 return Ok(params);
2085 };
2086 if version != crate::protocol::PROTOCOL_VERSION_2026_07_28 {
2087 return Ok(params);
2088 }
2089 let client_info = self.client_info.read().await.clone().ok_or_else(|| {
2090 Error::Transport("final protocol selected without client identity".to_string())
2091 })?;
2092 let required = serde_json::to_value(self.request_meta_for(&version, &client_info))
2093 .map_err(|e| Error::Transport(format!("Failed to serialize request metadata: {e}")))?;
2094 let required = required
2095 .as_object()
2096 .expect("RequestMeta serializes as an object");
2097
2098 if !params.is_object() {
2099 params = serde_json::json!({});
2100 }
2101 let params_object = params
2102 .as_object_mut()
2103 .expect("params was normalized to object");
2104 let meta = params_object
2105 .entry("_meta")
2106 .or_insert_with(|| serde_json::json!({}));
2107 if !meta.is_object() {
2108 *meta = serde_json::json!({});
2109 }
2110 let meta = meta
2111 .as_object_mut()
2112 .expect("metadata was normalized to object");
2113 for (key, value) in required {
2114 meta.insert(key.clone(), value.clone());
2115 }
2116
2117 Ok(params)
2118 }
2119
2120 fn ensure_connected(&self) -> Result<()> {
2121 if !self.connected.load(Ordering::Acquire) {
2122 return Err(Error::Transport("Connection closed".to_string()));
2123 }
2124 Ok(())
2125 }
2126
2127 fn ensure_initialized(&self) -> Result<()> {
2128 if !self.initialized.load(Ordering::Acquire) {
2129 return Err(Error::Transport("Client not initialized".to_string()));
2130 }
2131 Ok(())
2132 }
2133}
2134
2135fn pagination_cache_key(cursor: Option<&str>) -> String {
2136 serde_json::to_string(&cursor).expect("pagination cursor cache key is serializable")
2137}
2138
2139fn is_stale_tool_schema_error(error: &Error) -> bool {
2140 matches!(
2141 error,
2142 Error::JsonRpc(error)
2143 if error.code == McpErrorCode::HeaderMismatch.code()
2144 || error.code == ErrorCode::MethodNotFound.code()
2145 || error.code == ErrorCode::InvalidParams.code()
2146 )
2147}
2148
2149fn decode_cached<R: serde::de::DeserializeOwned>(
2150 value: &serde_json::Value,
2151 method: &str,
2152) -> Option<R> {
2153 match serde_json::from_value(value.clone()) {
2154 Ok(result) => Some(result),
2155 Err(error) => {
2156 tracing::warn!(
2157 method,
2158 error = %error,
2159 "Discarding response-cache entry that no longer deserializes"
2160 );
2161 None
2162 }
2163 }
2164}
2165
2166impl Drop for McpClient {
2167 fn drop(&mut self) {
2168 if let Some(task) = self.task.take() {
2169 task.abort();
2170 }
2171 }
2172}
2173
2174struct PendingRequest {
2180 method: String,
2181 response_tx: oneshot::Sender<Result<serde_json::Value>>,
2182 acknowledgment_tx: Option<oneshot::Sender<SubscriptionFilter>>,
2183}
2184
2185async fn message_loop<T: ClientTransport, H: ClientHandler>(
2187 mut transport: T,
2188 handler: H,
2189 mut command_rx: mpsc::Receiver<LoopCommand>,
2190 connected: Arc<AtomicBool>,
2191 roots: Arc<RwLock<Vec<Root>>>,
2192 response_cache: Arc<ClientResponseCache>,
2193) {
2194 let handler = Arc::new(handler);
2195 let mut pending_requests: HashMap<RequestId, PendingRequest> = HashMap::new();
2196 let next_id = AtomicI64::new(1);
2197
2198 loop {
2199 tokio::select! {
2200 command = command_rx.recv() => {
2202 match command {
2203 Some(LoopCommand::Request { method, params, response_tx }) => {
2204 let id = RequestId::Number(next_id.fetch_add(1, Ordering::Relaxed));
2205
2206 let request = JsonRpcRequest::new(id.clone(), &method)
2207 .with_params(params);
2208 let json = match serde_json::to_string(&request) {
2209 Ok(j) => j,
2210 Err(e) => {
2211 let _ = response_tx.send(Err(Error::Transport(
2212 format!("Serialization failed: {}", e)
2213 )));
2214 continue;
2215 }
2216 };
2217
2218 tracing::debug!(method = %method, id = ?id, "Sending request");
2219 pending_requests.insert(id, PendingRequest {
2220 method,
2221 response_tx,
2222 acknowledgment_tx: None,
2223 });
2224
2225 if let Err(e) = transport.send(&json).await {
2226 tracing::error!(error = %e, "Transport send error");
2227 fail_all_pending(&mut pending_requests, &format!("Transport error: {}", e));
2228 break;
2229 }
2230 }
2231 Some(LoopCommand::StartSubscription {
2232 params,
2233 id_tx,
2234 acknowledgment_tx,
2235 response_tx,
2236 }) => {
2237 let id = RequestId::Number(next_id.fetch_add(1, Ordering::Relaxed));
2238 let request = JsonRpcRequest::new(id.clone(), "subscriptions/listen")
2239 .with_params(params);
2240 let json = match serde_json::to_string(&request) {
2241 Ok(json) => json,
2242 Err(error) => {
2243 let _ = response_tx.send(Err(Error::Transport(
2244 format!("Serialization failed: {error}")
2245 )));
2246 continue;
2247 }
2248 };
2249
2250 tracing::debug!(id = ?id, "Opening subscription");
2251 pending_requests.insert(id.clone(), PendingRequest {
2252 method: "subscriptions/listen".to_string(),
2253 response_tx,
2254 acknowledgment_tx: Some(acknowledgment_tx),
2255 });
2256 let _ = id_tx.send(id);
2257
2258 if let Err(error) = transport.send(&json).await {
2259 tracing::error!(%error, "Subscription transport send error");
2260 fail_all_pending(
2261 &mut pending_requests,
2262 &format!("Transport error: {error}"),
2263 );
2264 break;
2265 }
2266 }
2267 Some(LoopCommand::CancelRequest {
2268 request_id,
2269 done_tx,
2270 }) => {
2271 let result = if pending_requests
2272 .get(&request_id)
2273 .is_some_and(|pending| pending.method == "subscriptions/listen")
2274 {
2275 let result = transport.cancel_request(&request_id).await;
2276 if result.is_ok()
2277 && let Some(pending) = pending_requests.remove(&request_id)
2278 {
2279 let _ = pending.response_tx.send(Err(Error::Transport(
2280 "subscription cancelled".to_string(),
2281 )));
2282 }
2283 result
2284 } else {
2285 Ok(())
2286 };
2287 if let Some(done_tx) = done_tx {
2288 let _ = done_tx.send(result);
2289 }
2290 }
2291 Some(LoopCommand::Notify { method, params }) => {
2292 let notification = JsonRpcNotification::new(&method)
2293 .with_params(params);
2294 if let Ok(json) = serde_json::to_string(¬ification) {
2295 tracing::debug!(method = %method, "Sending notification");
2296 let _ = transport.send(&json).await;
2297 }
2298 }
2299 Some(LoopCommand::ResolveInputs { requests, response_tx }) => {
2300 let result = resolve_inputs_with_handler(&handler, &roots, requests).await;
2301 let _ = response_tx.send(result);
2302 }
2303 Some(LoopCommand::ResetSession { done_tx }) => {
2304 tracing::info!("Resetting transport session for re-initialization");
2305 transport.reset_session().await;
2306 for (_, pending) in pending_requests.drain() {
2308 let _ = pending.response_tx.send(Err(Error::SessionExpired));
2309 }
2310 let _ = done_tx.send(());
2311 }
2312 Some(LoopCommand::Shutdown) | None => {
2313 tracing::debug!("Message loop shutting down");
2314 break;
2315 }
2316 }
2317 }
2318
2319 result = transport.recv() => {
2321 match result {
2322 Ok(Some(line)) => {
2323 handle_incoming(
2324 &line,
2325 &mut pending_requests,
2326 &handler,
2327 &roots,
2328 &mut transport,
2329 &response_cache,
2330 ).await;
2331 }
2332 Ok(None) => {
2333 tracing::info!("Transport closed (EOF)");
2334 break;
2335 }
2336 Err(e) => {
2337 tracing::error!(error = %e, "Transport receive error");
2338 break;
2339 }
2340 }
2341 }
2342 }
2343 }
2344
2345 connected.store(false, Ordering::Release);
2347 fail_all_pending(&mut pending_requests, "Connection closed");
2348 let _ = transport.close().await;
2349}
2350
2351async fn resolve_inputs_with_handler<H: ClientHandler>(
2352 handler: &Arc<H>,
2353 roots: &Arc<RwLock<Vec<Root>>>,
2354 requests: InputRequests,
2355) -> Result<InputResponses> {
2356 let mut responses = InputResponses::new();
2357 for (key, request) in requests {
2358 let response = match request {
2359 InputRequest::CreateMessage(params) => InputResponse::CreateMessage(
2360 handler
2361 .handle_create_message(params)
2362 .await
2363 .map_err(Error::JsonRpc)?,
2364 ),
2365 InputRequest::ListRoots(_) => {
2366 let configured = roots.read().await.clone();
2367 let result = if configured.is_empty() {
2368 handler.handle_list_roots().await.map_err(Error::JsonRpc)?
2369 } else {
2370 ListRootsResult {
2371 roots: configured,
2372 meta: None,
2373 }
2374 };
2375 InputResponse::ListRoots(result)
2376 }
2377 InputRequest::Elicit(params) => InputResponse::Elicit(
2378 handler
2379 .handle_elicit(params)
2380 .await
2381 .map_err(Error::JsonRpc)?,
2382 ),
2383 _ => {
2384 return Err(Error::Transport(
2385 "unsupported MRTR input request method".to_string(),
2386 ));
2387 }
2388 };
2389 responses.insert(key, response);
2390 }
2391 Ok(responses)
2392}
2393
2394async fn handle_incoming<T: ClientTransport, H: ClientHandler>(
2396 line: &str,
2397 pending_requests: &mut HashMap<RequestId, PendingRequest>,
2398 handler: &Arc<H>,
2399 roots: &Arc<RwLock<Vec<Root>>>,
2400 transport: &mut T,
2401 response_cache: &Arc<ClientResponseCache>,
2402) {
2403 let parsed: serde_json::Value = match serde_json::from_str(line) {
2404 Ok(v) => v,
2405 Err(e) => {
2406 tracing::warn!(error = %e, "Failed to parse incoming message");
2407 return;
2408 }
2409 };
2410
2411 if parsed.get("method").is_none()
2413 && (parsed.get("result").is_some() || parsed.get("error").is_some())
2414 {
2415 if let Some(error) = parsed.get("error") {
2418 let code = error.get("code").and_then(|c| c.as_i64()).unwrap_or(0) as i32;
2419 let id_missing_or_null = parsed.get("id").is_none_or(|id| id.is_null());
2420 if code == -32005 && id_missing_or_null {
2421 tracing::warn!(
2422 "Session expired (-32005 with null id), failing all pending requests"
2423 );
2424 for (_, pending) in pending_requests.drain() {
2425 let _ = pending.response_tx.send(Err(Error::SessionExpired));
2426 }
2427 return;
2428 }
2429 }
2430
2431 handle_response(&parsed, pending_requests);
2432 return;
2433 }
2434
2435 if parsed.get("id").is_some() && parsed.get("method").is_some() {
2437 let id = parse_request_id(&parsed);
2438 let method = parsed["method"].as_str().unwrap_or("");
2439 let params = parsed.get("params").cloned();
2440
2441 let result = dispatch_server_request(handler, roots, method, params).await;
2442
2443 let response = match result {
2445 Ok(value) => {
2446 if let Some(id) = id {
2447 serde_json::json!({
2448 "jsonrpc": "2.0",
2449 "id": id,
2450 "result": value
2451 })
2452 } else {
2453 return;
2454 }
2455 }
2456 Err(error) => {
2457 serde_json::json!({
2458 "jsonrpc": "2.0",
2459 "id": id,
2460 "error": {
2461 "code": error.code,
2462 "message": error.message
2463 }
2464 })
2465 }
2466 };
2467
2468 if let Ok(json) = serde_json::to_string(&response) {
2469 let _ = transport.send(&json).await;
2470 }
2471 return;
2472 }
2473
2474 if parsed.get("method").is_some() && parsed.get("id").is_none() {
2476 let method = parsed["method"].as_str().unwrap_or("");
2477 let params = parsed.get("params").cloned();
2478 invalidate_response_cache(response_cache, method, params.as_ref()).await;
2479 let notification = parse_server_notification(method, params);
2480 let should_dispatch = match ¬ification {
2481 ServerNotification::SubscriptionAcknowledged {
2482 subscription_id,
2483 notifications,
2484 } => {
2485 let Some(key) = matching_subscription_id(pending_requests, subscription_id) else {
2486 tracing::warn!(
2487 id = ?subscription_id,
2488 "Ignoring acknowledgment for unknown subscription"
2489 );
2490 return;
2491 };
2492 if let Some(sender) = pending_requests
2493 .get_mut(&key)
2494 .and_then(|pending| pending.acknowledgment_tx.take())
2495 {
2496 let _ = sender.send(notifications.clone());
2497 true
2498 } else {
2499 tracing::warn!(
2500 id = ?subscription_id,
2501 "Ignoring duplicate subscription acknowledgment"
2502 );
2503 false
2504 }
2505 }
2506 ServerNotification::Subscription {
2507 subscription_id, ..
2508 } => {
2509 let Some(key) = matching_subscription_id(pending_requests, subscription_id) else {
2510 tracing::warn!(
2511 id = ?subscription_id,
2512 "Ignoring notification for unknown subscription"
2513 );
2514 return;
2515 };
2516 let before_acknowledgment = pending_requests
2517 .get(&key)
2518 .is_some_and(|pending| pending.acknowledgment_tx.is_some());
2519 if before_acknowledgment {
2520 tracing::warn!(
2521 id = ?subscription_id,
2522 "Ending subscription that received a notification before acknowledgment"
2523 );
2524 if let Some(pending) = pending_requests.remove(&key) {
2525 let _ = pending.response_tx.send(Err(Error::Transport(
2526 "subscription notification arrived before acknowledgment".to_string(),
2527 )));
2528 }
2529 false
2530 } else {
2531 true
2532 }
2533 }
2534 ServerNotification::SubscriptionCancelled {
2535 subscription_id,
2536 reason,
2537 } => {
2538 let Some(key) = matching_subscription_id(pending_requests, subscription_id) else {
2539 tracing::warn!(
2540 id = ?subscription_id,
2541 "Ignoring cancellation for unknown or non-subscription request"
2542 );
2543 return;
2544 };
2545 if let Some(pending) = pending_requests.remove(&key) {
2546 let message = reason.as_deref().map_or_else(
2547 || "subscription cancelled by server".to_string(),
2548 |reason| format!("subscription cancelled by server: {reason}"),
2549 );
2550 let _ = pending.response_tx.send(Err(Error::Transport(message)));
2551 }
2552 true
2553 }
2554 _ => true,
2555 };
2556 if should_dispatch {
2557 handler.on_notification(notification).await;
2558 }
2559 }
2560}
2561
2562fn matching_subscription_id(
2563 pending_requests: &HashMap<RequestId, PendingRequest>,
2564 id: &RequestId,
2565) -> Option<RequestId> {
2566 if pending_requests
2567 .get(id)
2568 .is_some_and(|pending| pending.method == "subscriptions/listen")
2569 {
2570 return Some(id.clone());
2571 }
2572 pending_requests.iter().find_map(|(candidate, pending)| {
2573 (pending.method == "subscriptions/listen" && request_ids_match(candidate, id))
2574 .then(|| candidate.clone())
2575 })
2576}
2577
2578fn request_ids_match(left: &RequestId, right: &RequestId) -> bool {
2579 left == right
2580 || matches!(
2581 (left, right),
2582 (RequestId::Number(number), RequestId::String(value))
2583 | (RequestId::String(value), RequestId::Number(number))
2584 if value.parse::<i64>() == Ok(*number)
2585 )
2586}
2587
2588async fn invalidate_response_cache(
2589 response_cache: &ClientResponseCache,
2590 method: &str,
2591 params: Option<&serde_json::Value>,
2592) {
2593 match method {
2594 notifications::TOOLS_LIST_CHANGED => {
2595 response_cache.evict_method("tools/list").await;
2596 }
2597 notifications::PROMPTS_LIST_CHANGED => {
2598 response_cache.evict_method("prompts/list").await;
2599 }
2600 notifications::RESOURCES_LIST_CHANGED => {
2601 response_cache.evict_method("resources/list").await;
2602 response_cache
2603 .evict_method("resources/templates/list")
2604 .await;
2605 }
2606 notifications::RESOURCE_UPDATED => {
2607 if let Some(uri) = params
2608 .and_then(|value| value.get("uri"))
2609 .and_then(serde_json::Value::as_str)
2610 {
2611 response_cache.evict_resource(uri).await;
2612 }
2613 }
2614 _ => {}
2615 }
2616}
2617
2618fn handle_response(
2620 parsed: &serde_json::Value,
2621 pending_requests: &mut HashMap<RequestId, PendingRequest>,
2622) {
2623 let id = match parse_request_id(parsed) {
2624 Some(id) => id,
2625 None => {
2626 tracing::warn!("Response without id");
2627 return;
2628 }
2629 };
2630
2631 let pending = match pending_requests.remove(&id) {
2636 Some(p) => p,
2637 None => {
2638 let numeric_fallback = match &id {
2639 RequestId::String(s) => s
2640 .parse::<i64>()
2641 .ok()
2642 .and_then(|n| pending_requests.remove(&RequestId::Number(n))),
2643 _ => None,
2644 };
2645 match numeric_fallback {
2646 Some(p) => p,
2647 None => {
2648 tracing::warn!(id = ?id, "Response for unknown request");
2649 return;
2650 }
2651 }
2652 }
2653 };
2654
2655 tracing::debug!(id = ?id, "Received response");
2656
2657 if let Some(error) = parsed.get("error") {
2658 let code = error.get("code").and_then(|c| c.as_i64()).unwrap_or(-1) as i32;
2659 let message = error
2660 .get("message")
2661 .and_then(|m| m.as_str())
2662 .unwrap_or("Unknown error")
2663 .to_string();
2664 let data = error.get("data").cloned();
2665
2666 if code == -32005 {
2668 let _ = pending.response_tx.send(Err(Error::SessionExpired));
2669 return;
2670 }
2671
2672 let json_rpc_error = JsonRpcError {
2673 code,
2674 message,
2675 data,
2676 };
2677 let _ = pending
2678 .response_tx
2679 .send(Err(Error::JsonRpc(json_rpc_error)));
2680 } else if let Some(result) = parsed.get("result") {
2681 if pending.method == "subscriptions/listen" && pending.acknowledgment_tx.is_some() {
2682 let _ = pending.response_tx.send(Err(Error::Transport(
2683 "subscriptions/listen completed before acknowledgment".to_string(),
2684 )));
2685 return;
2686 }
2687 let _ = pending.response_tx.send(Ok(result.clone()));
2688 } else {
2689 let _ = pending
2690 .response_tx
2691 .send(Err(Error::Transport("Invalid response".to_string())));
2692 }
2693}
2694
2695async fn dispatch_server_request<H: ClientHandler>(
2697 handler: &Arc<H>,
2698 roots: &Arc<RwLock<Vec<Root>>>,
2699 method: &str,
2700 params: Option<serde_json::Value>,
2701) -> std::result::Result<serde_json::Value, JsonRpcError> {
2702 match method {
2703 "sampling/createMessage" => {
2704 let p = serde_json::from_value(params.unwrap_or_default())
2705 .map_err(|e| JsonRpcError::invalid_params(e.to_string()))?;
2706 let result = handler.handle_create_message(p).await?;
2707 serde_json::to_value(result).map_err(|e| JsonRpcError::internal_error(e.to_string()))
2708 }
2709 "elicitation/create" => {
2710 let p = serde_json::from_value(params.unwrap_or_default())
2711 .map_err(|e| JsonRpcError::invalid_params(e.to_string()))?;
2712 let result = handler.handle_elicit(p).await?;
2713 serde_json::to_value(result).map_err(|e| JsonRpcError::internal_error(e.to_string()))
2714 }
2715 "roots/list" => {
2716 let roots_list = roots.read().await;
2718 if !roots_list.is_empty() {
2719 let result = ListRootsResult {
2720 roots: roots_list.clone(),
2721 meta: None,
2722 };
2723 return serde_json::to_value(result)
2724 .map_err(|e| JsonRpcError::internal_error(e.to_string()));
2725 }
2726 drop(roots_list);
2727
2728 let result = handler.handle_list_roots().await?;
2729 serde_json::to_value(result).map_err(|e| JsonRpcError::internal_error(e.to_string()))
2730 }
2731 "ping" => Ok(serde_json::json!({})),
2732 _ => Err(JsonRpcError::method_not_found(method)),
2733 }
2734}
2735
2736fn parse_request_id(parsed: &serde_json::Value) -> Option<RequestId> {
2738 parsed.get("id").and_then(|id| {
2739 if let Some(n) = id.as_i64() {
2740 Some(RequestId::Number(n))
2741 } else {
2742 id.as_str().map(|s| RequestId::String(s.to_string()))
2743 }
2744 })
2745}
2746
2747fn parse_server_notification(
2749 method: &str,
2750 params: Option<serde_json::Value>,
2751) -> ServerNotification {
2752 if method == notifications::SUBSCRIPTIONS_ACKNOWLEDGED {
2753 if let Some(params) = ¶ms
2754 && let Ok(acknowledgment) =
2755 serde_json::from_value::<SubscriptionsAcknowledgedParams>(params.clone())
2756 && let Some(subscription_id) = acknowledgment.meta.and_then(|meta| meta.subscription_id)
2757 {
2758 return ServerNotification::SubscriptionAcknowledged {
2759 subscription_id,
2760 notifications: acknowledgment.notifications,
2761 };
2762 }
2763 return ServerNotification::Unknown {
2764 method: method.to_string(),
2765 params,
2766 };
2767 }
2768 if method == notifications::CANCELLED {
2769 if let Some(params) = ¶ms
2770 && let Ok(cancelled) = serde_json::from_value::<CancelledParams>(params.clone())
2771 && let Some(subscription_id) = cancelled.request_id
2772 {
2773 return ServerNotification::SubscriptionCancelled {
2774 subscription_id,
2775 reason: cancelled.reason,
2776 };
2777 }
2778 return ServerNotification::Unknown {
2779 method: method.to_string(),
2780 params,
2781 };
2782 }
2783
2784 let subscription_id = params
2785 .as_ref()
2786 .and_then(|params| params.pointer("/_meta/io.modelcontextprotocol~1subscriptionId"))
2787 .and_then(|id| serde_json::from_value::<RequestId>(id.clone()).ok());
2788 let notification = match method {
2789 notifications::PROGRESS => {
2790 if let Some(params) = params.clone()
2791 && let Ok(p) = serde_json::from_value(params)
2792 {
2793 ServerNotification::Progress(p)
2794 } else {
2795 ServerNotification::Unknown {
2796 method: method.to_string(),
2797 params: None,
2798 }
2799 }
2800 }
2801 notifications::MESSAGE => {
2802 if let Some(params) = params.clone()
2803 && let Ok(p) = serde_json::from_value(params)
2804 {
2805 ServerNotification::LogMessage(p)
2806 } else {
2807 ServerNotification::Unknown {
2808 method: method.to_string(),
2809 params: None,
2810 }
2811 }
2812 }
2813 notifications::RESOURCE_UPDATED => {
2814 if let Some(params) = ¶ms
2815 && let Some(uri) = params.get("uri").and_then(|u| u.as_str())
2816 {
2817 ServerNotification::ResourceUpdated {
2818 uri: uri.to_string(),
2819 }
2820 } else {
2821 ServerNotification::Unknown {
2822 method: method.to_string(),
2823 params: params.clone(),
2824 }
2825 }
2826 }
2827 notifications::RESOURCES_LIST_CHANGED => ServerNotification::ResourcesListChanged,
2828 notifications::TOOLS_LIST_CHANGED => ServerNotification::ToolsListChanged,
2829 notifications::PROMPTS_LIST_CHANGED => ServerNotification::PromptsListChanged,
2830 notifications::TASK_STATUS_CHANGED => {
2831 let is_final = params
2832 .as_ref()
2833 .and_then(serde_json::Value::as_object)
2834 .is_some_and(|params| params.contains_key("ttlMs"));
2835 match (is_final, params.clone()) {
2836 (true, Some(params)) => {
2837 match serde_json::from_value::<crate::tasks::TaskStatusNotificationParams>(
2838 params.clone(),
2839 ) {
2840 Ok(params) => ServerNotification::FinalTaskStatusChanged(params),
2841 Err(_) => ServerNotification::Unknown {
2842 method: method.to_string(),
2843 params: Some(params),
2844 },
2845 }
2846 }
2847 (false, Some(params)) => {
2848 match serde_json::from_value::<TaskStatusParams>(params.clone()) {
2849 Ok(params) => ServerNotification::TaskStatusChanged(params),
2850 Err(_) => ServerNotification::Unknown {
2851 method: method.to_string(),
2852 params: Some(params),
2853 },
2854 }
2855 }
2856 (_, None) => ServerNotification::Unknown {
2857 method: method.to_string(),
2858 params: None,
2859 },
2860 }
2861 }
2862 _ => ServerNotification::Unknown {
2863 method: method.to_string(),
2864 params: params.clone(),
2865 },
2866 };
2867 if let Some(subscription_id) = subscription_id {
2868 ServerNotification::Subscription {
2869 subscription_id,
2870 notification: Box::new(notification),
2871 }
2872 } else {
2873 notification
2874 }
2875}
2876
2877fn fail_all_pending(pending: &mut HashMap<RequestId, PendingRequest>, reason: &str) {
2879 for (_, req) in pending.drain() {
2880 let _ = req
2881 .response_tx
2882 .send(Err(Error::Transport(reason.to_string())));
2883 }
2884}
2885
2886#[cfg(test)]
2887mod tests {
2888 use super::*;
2889 use async_trait::async_trait;
2890 use std::sync::Mutex;
2891
2892 struct MockTransport {
2900 responses: Arc<Mutex<Vec<MockReply>>>,
2902 response_idx: Arc<std::sync::atomic::AtomicUsize>,
2904 incoming_tx: mpsc::Sender<String>,
2906 incoming_rx: mpsc::Receiver<String>,
2908 outgoing: Arc<Mutex<Vec<String>>>,
2910 connected: Arc<AtomicBool>,
2911 }
2912
2913 enum MockReply {
2914 Result(serde_json::Value),
2915 Error(JsonRpcError),
2916 }
2917
2918 #[allow(dead_code)]
2919 impl MockTransport {
2920 fn new() -> Self {
2921 let (tx, rx) = mpsc::channel(32);
2922 Self {
2923 responses: Arc::new(Mutex::new(Vec::new())),
2924 response_idx: Arc::new(std::sync::atomic::AtomicUsize::new(0)),
2925 incoming_tx: tx,
2926 incoming_rx: rx,
2927 outgoing: Arc::new(Mutex::new(Vec::new())),
2928 connected: Arc::new(AtomicBool::new(true)),
2929 }
2930 }
2931
2932 fn with_responses(responses: Vec<serde_json::Value>) -> Self {
2938 let (tx, rx) = mpsc::channel(32);
2939 Self {
2940 responses: Arc::new(Mutex::new(
2941 responses.into_iter().map(MockReply::Result).collect(),
2942 )),
2943 response_idx: Arc::new(std::sync::atomic::AtomicUsize::new(0)),
2944 incoming_tx: tx,
2945 incoming_rx: rx,
2946 outgoing: Arc::new(Mutex::new(Vec::new())),
2947 connected: Arc::new(AtomicBool::new(true)),
2948 }
2949 }
2950
2951 fn with_replies(responses: Vec<MockReply>) -> Self {
2952 let (tx, rx) = mpsc::channel(32);
2953 Self {
2954 responses: Arc::new(Mutex::new(responses)),
2955 response_idx: Arc::new(std::sync::atomic::AtomicUsize::new(0)),
2956 incoming_tx: tx,
2957 incoming_rx: rx,
2958 outgoing: Arc::new(Mutex::new(Vec::new())),
2959 connected: Arc::new(AtomicBool::new(true)),
2960 }
2961 }
2962 }
2963
2964 #[async_trait]
2965 impl ClientTransport for MockTransport {
2966 async fn send(&mut self, message: &str) -> Result<()> {
2967 self.outgoing.lock().unwrap().push(message.to_string());
2968
2969 if let Ok(parsed) = serde_json::from_str::<serde_json::Value>(message) {
2971 if let Some(id) = parsed.get("id") {
2973 let idx = self
2974 .response_idx
2975 .fetch_add(1, std::sync::atomic::Ordering::Relaxed);
2976 let responses = self.responses.lock().unwrap();
2977 if let Some(reply) = responses.get(idx) {
2978 let response = match reply {
2979 MockReply::Result(result) => serde_json::json!({
2980 "jsonrpc": "2.0",
2981 "id": id,
2982 "result": result
2983 }),
2984 MockReply::Error(error) => serde_json::json!({
2985 "jsonrpc": "2.0",
2986 "id": id,
2987 "error": error
2988 }),
2989 };
2990 let _ = self.incoming_tx.try_send(response.to_string());
2991 }
2992 }
2993 }
2994
2995 Ok(())
2996 }
2997
2998 async fn recv(&mut self) -> Result<Option<String>> {
2999 match self.incoming_rx.recv().await {
3002 Some(msg) => Ok(Some(msg)),
3003 None => Ok(None),
3004 }
3005 }
3006
3007 fn is_connected(&self) -> bool {
3008 self.connected.load(Ordering::Relaxed)
3009 }
3010
3011 async fn close(&mut self) -> Result<()> {
3012 self.connected.store(false, Ordering::Relaxed);
3013 Ok(())
3014 }
3015 }
3016
3017 fn mock_initialize_response() -> serde_json::Value {
3018 serde_json::json!({
3019 "protocolVersion": "2025-11-25",
3020 "serverInfo": {
3021 "name": "test-server",
3022 "version": "1.0.0"
3023 },
3024 "capabilities": {
3025 "tools": {}
3026 }
3027 })
3028 }
3029
3030 #[tokio::test]
3031 async fn test_client_not_initialized() {
3032 let client = McpClient::connect(MockTransport::with_responses(vec![]))
3033 .await
3034 .unwrap();
3035
3036 let result = client.list_tools().await;
3037 assert!(result.is_err());
3038 assert!(result.unwrap_err().to_string().contains("not initialized"));
3039 }
3040
3041 #[tokio::test]
3042 async fn test_client_initialize() {
3043 let client = McpClient::connect(MockTransport::with_responses(vec![
3044 mock_initialize_response(),
3045 ]))
3046 .await
3047 .unwrap();
3048
3049 assert!(!client.is_initialized());
3050
3051 let result = client.initialize("test-client", "1.0.0").await;
3052 assert!(result.is_ok());
3053 assert!(client.is_initialized());
3054
3055 let server_info = client.server_info().await.unwrap();
3056 assert_eq!(server_info.server_info.name, "test-server");
3057 }
3058
3059 #[cfg(any(feature = "protocol-2026-07-28", feature = "stateless"))]
3060 #[tokio::test]
3061 async fn final_discover_injects_metadata_on_every_request() {
3062 let transport = MockTransport::with_responses(vec![
3063 serde_json::json!({
3064 "resultType": "complete",
3065 "supportedVersions": ["2026-07-28"],
3066 "capabilities": {}
3067 }),
3068 serde_json::json!({
3069 "resultType": "complete",
3070 "tools": [],
3071 "ttlMs": 0,
3072 "cacheScope": "private"
3073 }),
3074 ]);
3075 let outgoing = transport.outgoing.clone();
3076 let client = McpClient::builder()
3077 .protocol_support(ProtocolSupport::try_new(["2026-07-28"]).unwrap())
3078 .with_elicitation()
3079 .connect_simple(transport)
3080 .await
3081 .unwrap();
3082
3083 client.discover("test-client", "1.0.0").await.unwrap();
3084 client.list_tools().await.unwrap();
3085 assert_eq!(
3086 client.selected_protocol_version().await.as_deref(),
3087 Some("2026-07-28")
3088 );
3089
3090 let messages: Vec<serde_json::Value> = outgoing
3091 .lock()
3092 .unwrap()
3093 .iter()
3094 .map(|message| serde_json::from_str(message).unwrap())
3095 .collect();
3096 assert_eq!(messages.len(), 2);
3097 assert_eq!(messages[0]["method"], "server/discover");
3098 assert_eq!(messages[1]["method"], "tools/list");
3099 for message in messages {
3100 let meta = &message["params"]["_meta"];
3101 assert_eq!(
3102 meta["io.modelcontextprotocol/protocolVersion"],
3103 "2026-07-28"
3104 );
3105 assert_eq!(
3106 meta["io.modelcontextprotocol/clientInfo"]["name"],
3107 "test-client"
3108 );
3109 assert!(meta["io.modelcontextprotocol/clientCapabilities"].is_object());
3110 }
3111 }
3112
3113 #[cfg(any(feature = "protocol-2026-07-28", feature = "stateless"))]
3114 fn final_discover_result() -> serde_json::Value {
3115 serde_json::json!({
3116 "resultType": "complete",
3117 "supportedVersions": ["2026-07-28"],
3118 "capabilities": {},
3119 "ttlMs": 0,
3120 "cacheScope": "private"
3121 })
3122 }
3123
3124 #[cfg(any(feature = "protocol-2026-07-28", feature = "stateless"))]
3125 struct SubscriptionTestTransport {
3126 incoming_tx: mpsc::Sender<String>,
3127 incoming_rx: mpsc::Receiver<String>,
3128 outgoing: Arc<Mutex<Vec<String>>>,
3129 connected: Arc<AtomicBool>,
3130 }
3131
3132 #[cfg(any(feature = "protocol-2026-07-28", feature = "stateless"))]
3133 impl SubscriptionTestTransport {
3134 fn new() -> Self {
3135 let (incoming_tx, incoming_rx) = mpsc::channel(32);
3136 Self {
3137 incoming_tx,
3138 incoming_rx,
3139 outgoing: Arc::new(Mutex::new(Vec::new())),
3140 connected: Arc::new(AtomicBool::new(true)),
3141 }
3142 }
3143 }
3144
3145 #[cfg(any(feature = "protocol-2026-07-28", feature = "stateless"))]
3146 #[async_trait]
3147 impl ClientTransport for SubscriptionTestTransport {
3148 async fn send(&mut self, message: &str) -> Result<()> {
3149 self.outgoing.lock().unwrap().push(message.to_string());
3150 let value: serde_json::Value = serde_json::from_str(message)
3151 .map_err(|error| Error::Transport(error.to_string()))?;
3152 let Some(id) = value.get("id").cloned() else {
3153 return Ok(());
3154 };
3155 match value
3156 .get("method")
3157 .and_then(serde_json::Value::as_str)
3158 .unwrap_or_default()
3159 {
3160 "server/discover" => {
3161 self.incoming_tx
3162 .send(
3163 serde_json::json!({
3164 "jsonrpc": "2.0",
3165 "id": id,
3166 "result": final_discover_result()
3167 })
3168 .to_string(),
3169 )
3170 .await
3171 .map_err(|_| Error::Transport("test channel closed".to_string()))?;
3172 }
3173 "subscriptions/listen" => {
3174 let notifications = value["params"]["notifications"].clone();
3175 self.incoming_tx
3176 .send(
3177 serde_json::json!({
3178 "jsonrpc": "2.0",
3179 "method": notifications::SUBSCRIPTIONS_ACKNOWLEDGED,
3180 "params": {
3181 "_meta": {
3182 "io.modelcontextprotocol/subscriptionId": id
3183 },
3184 "notifications": notifications
3185 }
3186 })
3187 .to_string(),
3188 )
3189 .await
3190 .map_err(|_| Error::Transport("test channel closed".to_string()))?;
3191
3192 if value["params"]["notifications"]["promptsListChanged"]
3193 == serde_json::Value::Bool(true)
3194 {
3195 self.incoming_tx
3196 .send(
3197 serde_json::json!({
3198 "jsonrpc": "2.0",
3199 "id": id,
3200 "result": {
3201 "resultType": "complete",
3202 "_meta": {
3203 "io.modelcontextprotocol/subscriptionId": id
3204 }
3205 }
3206 })
3207 .to_string(),
3208 )
3209 .await
3210 .map_err(|_| Error::Transport("test channel closed".to_string()))?;
3211 } else {
3212 self.incoming_tx
3213 .send(
3214 serde_json::json!({
3215 "jsonrpc": "2.0",
3216 "method": notifications::TOOLS_LIST_CHANGED,
3217 "params": {
3218 "_meta": {
3219 "io.modelcontextprotocol/subscriptionId": id
3220 }
3221 }
3222 })
3223 .to_string(),
3224 )
3225 .await
3226 .map_err(|_| Error::Transport("test channel closed".to_string()))?;
3227 }
3228 }
3229 _ => {}
3230 }
3231 Ok(())
3232 }
3233
3234 async fn recv(&mut self) -> Result<Option<String>> {
3235 Ok(self.incoming_rx.recv().await)
3236 }
3237
3238 fn is_connected(&self) -> bool {
3239 self.connected.load(Ordering::Acquire)
3240 }
3241
3242 async fn close(&mut self) -> Result<()> {
3243 self.connected.store(false, Ordering::Release);
3244 Ok(())
3245 }
3246 }
3247
3248 #[cfg(any(feature = "protocol-2026-07-28", feature = "stateless"))]
3249 #[tokio::test]
3250 async fn final_subscriptions_correlate_and_cancel_over_message_transport() {
3251 let transport = SubscriptionTestTransport::new();
3252 let outgoing = transport.outgoing.clone();
3253 let incoming = transport.incoming_tx.clone();
3254 let received = Arc::new(Mutex::new(Vec::new()));
3255
3256 struct RecordingHandler(Arc<Mutex<Vec<ServerNotification>>>);
3257
3258 #[async_trait]
3259 impl ClientHandler for RecordingHandler {
3260 async fn on_notification(&self, notification: ServerNotification) {
3261 self.0.lock().unwrap().push(notification);
3262 }
3263 }
3264
3265 let client = McpClient::builder()
3266 .protocol_support(ProtocolSupport::try_new(["2026-07-28"]).unwrap())
3267 .connect(transport, RecordingHandler(received.clone()))
3268 .await
3269 .unwrap();
3270 client.discover("test-client", "1.0.0").await.unwrap();
3271
3272 let requested = SubscriptionFilter {
3273 tools_list_changed: Some(true),
3274 ..Default::default()
3275 };
3276 let mut first = client
3277 .listen_subscriptions(requested.clone())
3278 .await
3279 .unwrap();
3280 let mut second = client.listen_subscriptions(requested).await.unwrap();
3281 assert_ne!(first.id(), second.id());
3282 assert_eq!(
3283 first.acknowledged().await.unwrap().tools_list_changed,
3284 Some(true)
3285 );
3286 assert_eq!(
3287 second.acknowledged().await.unwrap().tools_list_changed,
3288 Some(true)
3289 );
3290
3291 for _ in 0..100 {
3292 if received
3293 .lock()
3294 .unwrap()
3295 .iter()
3296 .filter(|notification| {
3297 matches!(
3298 notification,
3299 ServerNotification::Subscription {
3300 notification,
3301 ..
3302 } if matches!(notification.as_ref(), ServerNotification::ToolsListChanged)
3303 )
3304 })
3305 .count()
3306 == 2
3307 {
3308 break;
3309 }
3310 tokio::task::yield_now().await;
3311 }
3312 let subscription_ids: Vec<RequestId> = received
3313 .lock()
3314 .unwrap()
3315 .iter()
3316 .filter_map(|notification| match notification {
3317 ServerNotification::Subscription {
3318 subscription_id,
3319 notification,
3320 } if matches!(notification.as_ref(), ServerNotification::ToolsListChanged) => {
3321 Some(subscription_id.clone())
3322 }
3323 _ => None,
3324 })
3325 .collect();
3326 assert_eq!(subscription_ids, [first.id().clone(), second.id().clone()]);
3327
3328 incoming
3329 .send(
3330 serde_json::json!({
3331 "jsonrpc": "2.0",
3332 "method": notifications::CANCELLED,
3333 "params": {
3334 "requestId": 999,
3335 "reason": "not a subscription"
3336 }
3337 })
3338 .to_string(),
3339 )
3340 .await
3341 .unwrap();
3342 tokio::task::yield_now().await;
3343 assert!(
3344 !received.lock().unwrap().iter().any(|notification| matches!(
3345 notification,
3346 ServerNotification::SubscriptionCancelled { subscription_id, .. }
3347 if subscription_id == &RequestId::Number(999)
3348 ))
3349 );
3350
3351 let first_id = first.id().clone();
3352 let second_id = second.id().clone();
3353 first.cancel().await.unwrap();
3354 second.cancel().await.unwrap();
3355 let cancellation_ids: Vec<RequestId> = outgoing
3356 .lock()
3357 .unwrap()
3358 .iter()
3359 .filter_map(|message| {
3360 let value: serde_json::Value = serde_json::from_str(message).unwrap();
3361 (value["method"] == notifications::CANCELLED)
3362 .then(|| serde_json::from_value(value["params"]["requestId"].clone()).unwrap())
3363 })
3364 .collect();
3365 assert_eq!(cancellation_ids, [first_id, second_id]);
3366 }
3367
3368 #[cfg(any(feature = "protocol-2026-07-28", feature = "stateless"))]
3369 #[tokio::test]
3370 async fn final_subscription_observes_graceful_completion() {
3371 let client = McpClient::builder()
3372 .protocol_support(ProtocolSupport::try_new(["2026-07-28"]).unwrap())
3373 .connect_simple(SubscriptionTestTransport::new())
3374 .await
3375 .unwrap();
3376 client.discover("test-client", "1.0.0").await.unwrap();
3377
3378 let mut handle = client
3379 .listen_subscriptions(SubscriptionFilter {
3380 prompts_list_changed: Some(true),
3381 ..Default::default()
3382 })
3383 .await
3384 .unwrap();
3385 assert_eq!(
3386 handle.acknowledged().await.unwrap().prompts_list_changed,
3387 Some(true)
3388 );
3389 let expected_id = handle.id().clone();
3390 let result = handle.wait().await.unwrap();
3391 assert!(result.result_type.is_complete());
3392 assert_eq!(result.meta.subscription_id, expected_id);
3393 }
3394
3395 #[cfg(any(feature = "protocol-2026-07-28", feature = "stateless"))]
3396 #[tokio::test]
3397 async fn final_subscription_accepts_server_cancellation_only_for_active_listen() {
3398 let transport = SubscriptionTestTransport::new();
3399 let incoming = transport.incoming_tx.clone();
3400 let client = McpClient::builder()
3401 .protocol_support(ProtocolSupport::try_new(["2026-07-28"]).unwrap())
3402 .connect_simple(transport)
3403 .await
3404 .unwrap();
3405 client.discover("test-client", "1.0.0").await.unwrap();
3406
3407 let mut handle = client
3408 .listen_subscriptions(SubscriptionFilter {
3409 tools_list_changed: Some(true),
3410 ..Default::default()
3411 })
3412 .await
3413 .unwrap();
3414 handle.acknowledged().await.unwrap();
3415 let id = handle.id().clone();
3416 incoming
3417 .send(
3418 serde_json::json!({
3419 "jsonrpc": "2.0",
3420 "method": notifications::CANCELLED,
3421 "params": {
3422 "requestId": id,
3423 "reason": "server shutdown"
3424 }
3425 })
3426 .to_string(),
3427 )
3428 .await
3429 .unwrap();
3430
3431 let error = handle.wait().await.unwrap_err();
3432 assert!(
3433 error
3434 .to_string()
3435 .contains("subscription cancelled by server: server shutdown")
3436 );
3437 }
3438
3439 #[cfg(any(feature = "protocol-2026-07-28", feature = "stateless"))]
3440 fn cacheable_tools_result(name: &str) -> serde_json::Value {
3441 serde_json::json!({
3442 "resultType": "complete",
3443 "tools": [{
3444 "name": name,
3445 "inputSchema": {
3446 "type": "object",
3447 "properties": {}
3448 }
3449 }],
3450 "ttlMs": 60_000,
3451 "cacheScope": "private"
3452 })
3453 }
3454
3455 #[cfg(any(feature = "protocol-2026-07-28", feature = "stateless"))]
3456 #[tokio::test]
3457 async fn final_cache_serves_a_fresh_list_without_a_round_trip() {
3458 let transport = MockTransport::with_responses(vec![
3459 final_discover_result(),
3460 cacheable_tools_result("cached"),
3461 ]);
3462 let outgoing = transport.outgoing.clone();
3463 let client = McpClient::builder()
3464 .protocol_support(ProtocolSupport::try_new(["2026-07-28"]).unwrap())
3465 .connect_simple(transport)
3466 .await
3467 .unwrap();
3468 client.discover("test-client", "1.0.0").await.unwrap();
3469
3470 let first = client.list_tools().await.unwrap();
3471 let second = client.list_tools().await.unwrap();
3472
3473 assert_eq!(first.tools[0].name, "cached");
3474 assert_eq!(second.tools[0].name, "cached");
3475 assert_eq!(outgoing.lock().unwrap().len(), 2);
3476 assert_eq!(client.response_cache_len().await, 1);
3477 }
3478
3479 #[cfg(any(feature = "protocol-2026-07-28", feature = "stateless"))]
3480 #[tokio::test]
3481 async fn disabling_final_cache_forces_each_list_request() {
3482 let transport = MockTransport::with_responses(vec![
3483 final_discover_result(),
3484 cacheable_tools_result("first"),
3485 cacheable_tools_result("second"),
3486 ]);
3487 let outgoing = transport.outgoing.clone();
3488 let client = McpClient::builder()
3489 .protocol_support(ProtocolSupport::try_new(["2026-07-28"]).unwrap())
3490 .disable_response_cache()
3491 .connect_simple(transport)
3492 .await
3493 .unwrap();
3494 client.discover("test-client", "1.0.0").await.unwrap();
3495
3496 assert_eq!(client.list_tools().await.unwrap().tools[0].name, "first");
3497 assert_eq!(client.list_tools().await.unwrap().tools[0].name, "second");
3498 assert_eq!(outgoing.lock().unwrap().len(), 3);
3499 assert_eq!(client.response_cache_len().await, 0);
3500 }
3501
3502 #[cfg(any(feature = "protocol-2026-07-28", feature = "stateless"))]
3503 #[tokio::test]
3504 async fn list_changed_notification_invalidates_a_fresh_entry() {
3505 let transport = MockTransport::with_responses(vec![
3506 final_discover_result(),
3507 cacheable_tools_result("first"),
3508 cacheable_tools_result("second"),
3509 ]);
3510 let incoming = transport.incoming_tx.clone();
3511 let outgoing = transport.outgoing.clone();
3512 let notification_seen = Arc::new(AtomicBool::new(false));
3513 let handler = NotificationHandler::new().on_tools_changed({
3514 let notification_seen = notification_seen.clone();
3515 move || notification_seen.store(true, Ordering::Release)
3516 });
3517 let client = McpClient::builder()
3518 .protocol_support(ProtocolSupport::try_new(["2026-07-28"]).unwrap())
3519 .connect(transport, handler)
3520 .await
3521 .unwrap();
3522 client.discover("test-client", "1.0.0").await.unwrap();
3523 assert_eq!(client.list_tools().await.unwrap().tools[0].name, "first");
3524
3525 incoming
3526 .send(
3527 serde_json::json!({
3528 "jsonrpc": "2.0",
3529 "method": notifications::TOOLS_LIST_CHANGED,
3530 "params": {}
3531 })
3532 .to_string(),
3533 )
3534 .await
3535 .unwrap();
3536 for _ in 0..100 {
3537 if notification_seen.load(Ordering::Acquire) {
3538 break;
3539 }
3540 tokio::task::yield_now().await;
3541 }
3542 assert!(notification_seen.load(Ordering::Acquire));
3543
3544 assert_eq!(client.list_tools().await.unwrap().tools[0].name, "second");
3545 assert_eq!(outgoing.lock().unwrap().len(), 3);
3546 }
3547
3548 #[cfg(any(feature = "protocol-2026-07-28", feature = "stateless"))]
3549 #[tokio::test]
3550 async fn rotating_private_partition_refetches_resource() {
3551 let resource_result = |text: &str| {
3552 serde_json::json!({
3553 "resultType": "complete",
3554 "contents": [{
3555 "uri": "config://app",
3556 "text": text
3557 }],
3558 "ttlMs": 60_000,
3559 "cacheScope": "private"
3560 })
3561 };
3562 let transport = MockTransport::with_responses(vec![
3563 final_discover_result(),
3564 resource_result("principal-a"),
3565 resource_result("principal-b"),
3566 ]);
3567 let outgoing = transport.outgoing.clone();
3568 let client = McpClient::builder()
3569 .protocol_support(ProtocolSupport::try_new(["2026-07-28"]).unwrap())
3570 .response_cache(ClientCacheConfig::default().with_partition("principal-a"))
3571 .connect_simple(transport)
3572 .await
3573 .unwrap();
3574 client.discover("test-client", "1.0.0").await.unwrap();
3575
3576 assert_eq!(
3577 client
3578 .read_resource("config://app")
3579 .await
3580 .unwrap()
3581 .first_text(),
3582 Some("principal-a")
3583 );
3584 client.set_cache_partition("principal-b").await;
3585 assert_eq!(
3586 client
3587 .read_resource("config://app")
3588 .await
3589 .unwrap()
3590 .first_text(),
3591 Some("principal-b")
3592 );
3593 assert_eq!(outgoing.lock().unwrap().len(), 3);
3594 }
3595
3596 #[cfg(any(feature = "protocol-2026-07-28", feature = "stateless"))]
3597 #[tokio::test]
3598 async fn final_tool_call_refreshes_stale_schema_and_retries_once() {
3599 let transport = MockTransport::with_replies(vec![
3600 MockReply::Result(final_discover_result()),
3601 MockReply::Result(cacheable_tools_result("changing-tool")),
3602 MockReply::Error(JsonRpcError::header_mismatch("stale x-mcp-header mapping")),
3603 MockReply::Result(cacheable_tools_result("changing-tool")),
3604 MockReply::Result(serde_json::json!({
3605 "resultType": "complete",
3606 "content": [{"type": "text", "text": "retried"}]
3607 })),
3608 ]);
3609 let outgoing = transport.outgoing.clone();
3610 let client = McpClient::builder()
3611 .protocol_support(ProtocolSupport::try_new(["2026-07-28"]).unwrap())
3612 .connect_simple(transport)
3613 .await
3614 .unwrap();
3615 client.discover("test-client", "1.0.0").await.unwrap();
3616 client.list_tools().await.unwrap();
3617
3618 let result = client
3619 .call_tool("changing-tool", serde_json::json!({}))
3620 .await
3621 .unwrap();
3622 assert_eq!(result.first_text(), Some("retried"));
3623
3624 let methods: Vec<String> = outgoing
3625 .lock()
3626 .unwrap()
3627 .iter()
3628 .map(|message| {
3629 serde_json::from_str::<serde_json::Value>(message).unwrap()["method"]
3630 .as_str()
3631 .unwrap()
3632 .to_string()
3633 })
3634 .collect();
3635 assert_eq!(
3636 methods,
3637 [
3638 "server/discover",
3639 "tools/list",
3640 "tools/call",
3641 "tools/list",
3642 "tools/call"
3643 ]
3644 );
3645 }
3646
3647 #[cfg(any(feature = "protocol-2026-07-28", feature = "stateless"))]
3648 #[tokio::test]
3649 async fn final_call_tool_as_task_sends_no_legacy_task_parameter() {
3650 let transport = MockTransport::with_responses(vec![
3651 final_discover_result(),
3652 serde_json::json!({
3653 "resultType": "task",
3654 "taskId": "task-final",
3655 "status": "working",
3656 "createdAt": "2026-07-31T00:00:00Z",
3657 "lastUpdatedAt": "2026-07-31T00:00:00Z",
3658 "ttlMs": null,
3659 "pollIntervalMs": 50
3660 }),
3661 ]);
3662 let outgoing = transport.outgoing.clone();
3663 let client = McpClient::builder()
3664 .protocol_support(ProtocolSupport::try_new(["2026-07-28"]).unwrap())
3665 .with_tasks()
3666 .connect_simple(transport)
3667 .await
3668 .unwrap();
3669 client.discover("test-client", "1.0.0").await.unwrap();
3670
3671 let created = client
3672 .call_tool_as_task("long-tool", serde_json::json!({}), None)
3673 .await
3674 .unwrap();
3675 assert_eq!(created.task.task_id, "task-final");
3676
3677 let messages = outgoing.lock().unwrap();
3678 let call: serde_json::Value = serde_json::from_str(&messages[1]).unwrap();
3679 assert_eq!(call["method"], "tools/call");
3680 assert!(
3681 call["params"].get("task").is_none(),
3682 "final tools/call leaked the legacy task parameter: {call}"
3683 );
3684 assert!(
3685 call["params"]["_meta"]["io.modelcontextprotocol/clientCapabilities"]["extensions"]
3686 .get(crate::protocol::TASKS_EXTENSION_ID)
3687 .is_some()
3688 );
3689 }
3690
3691 #[cfg(any(feature = "protocol-2026-07-28", feature = "stateless"))]
3692 #[tokio::test]
3693 async fn final_stale_schema_retry_is_bounded() {
3694 let transport = MockTransport::with_replies(vec![
3695 MockReply::Result(final_discover_result()),
3696 MockReply::Result(cacheable_tools_result("changing-tool")),
3697 MockReply::Error(JsonRpcError::header_mismatch("first rejection")),
3698 MockReply::Result(cacheable_tools_result("changing-tool")),
3699 MockReply::Error(JsonRpcError::header_mismatch("second rejection")),
3700 ]);
3701 let outgoing = transport.outgoing.clone();
3702 let client = McpClient::builder()
3703 .protocol_support(ProtocolSupport::try_new(["2026-07-28"]).unwrap())
3704 .connect_simple(transport)
3705 .await
3706 .unwrap();
3707 client.discover("test-client", "1.0.0").await.unwrap();
3708 client.list_tools().await.unwrap();
3709
3710 let error = client
3711 .call_tool("changing-tool", serde_json::json!({}))
3712 .await
3713 .unwrap_err();
3714 assert!(matches!(
3715 error,
3716 Error::JsonRpc(error) if error.code == McpErrorCode::HeaderMismatch.code()
3717 ));
3718 assert_eq!(outgoing.lock().unwrap().len(), 5);
3719 }
3720
3721 #[tokio::test]
3722 async fn legacy_tool_call_does_not_retry_a_header_mismatch() {
3723 let transport = MockTransport::with_replies(vec![
3724 MockReply::Result(mock_initialize_response()),
3725 MockReply::Error(JsonRpcError::header_mismatch("legacy rejection")),
3726 ]);
3727 let outgoing = transport.outgoing.clone();
3728 let client = McpClient::connect(transport).await.unwrap();
3729 client.initialize("test-client", "1.0.0").await.unwrap();
3730
3731 let error = client
3732 .call_tool("changing-tool", serde_json::json!({}))
3733 .await
3734 .unwrap_err();
3735 assert!(matches!(
3736 error,
3737 Error::JsonRpc(error) if error.code == McpErrorCode::HeaderMismatch.code()
3738 ));
3739 let messages = outgoing.lock().unwrap();
3740 assert_eq!(messages.len(), 3);
3741 assert!(
3742 !messages
3743 .iter()
3744 .any(|message| message.contains("tools/list"))
3745 );
3746 }
3747
3748 #[test]
3749 fn stale_tool_schema_errors_are_pre_execution_protocol_errors() {
3750 for error in [
3751 Error::JsonRpc(JsonRpcError::header_mismatch("mismatch")),
3752 Error::JsonRpc(JsonRpcError::method_not_found("tool")),
3753 Error::JsonRpc(JsonRpcError::invalid_params("arguments")),
3754 ] {
3755 assert!(is_stale_tool_schema_error(&error));
3756 }
3757 assert!(!is_stale_tool_schema_error(&Error::JsonRpc(
3758 JsonRpcError::internal_error("executed")
3759 )));
3760 }
3761
3762 #[tokio::test]
3763 async fn test_list_tools() {
3764 let client = McpClient::connect(MockTransport::with_responses(vec![
3765 mock_initialize_response(),
3766 serde_json::json!({
3767 "tools": [
3768 {
3769 "name": "test_tool",
3770 "description": "A test tool",
3771 "inputSchema": {
3772 "type": "object",
3773 "properties": {}
3774 }
3775 }
3776 ]
3777 }),
3778 ]))
3779 .await
3780 .unwrap();
3781
3782 client.initialize("test-client", "1.0.0").await.unwrap();
3783 let tools = client.list_tools().await.unwrap();
3784
3785 assert_eq!(tools.tools.len(), 1);
3786 assert_eq!(tools.tools[0].name, "test_tool");
3787 }
3788
3789 #[tokio::test]
3790 async fn test_call_tool() {
3791 let client = McpClient::connect(MockTransport::with_responses(vec![
3792 mock_initialize_response(),
3793 serde_json::json!({
3794 "content": [
3795 {
3796 "type": "text",
3797 "text": "Tool result"
3798 }
3799 ]
3800 }),
3801 ]))
3802 .await
3803 .unwrap();
3804
3805 client.initialize("test-client", "1.0.0").await.unwrap();
3806 let result = client
3807 .call_tool("test_tool", serde_json::json!({"arg": "value"}))
3808 .await
3809 .unwrap();
3810
3811 assert!(!result.content.is_empty());
3812 }
3813
3814 #[tokio::test]
3815 async fn test_list_resources() {
3816 let client = McpClient::connect(MockTransport::with_responses(vec![
3817 mock_initialize_response(),
3818 serde_json::json!({
3819 "resources": [
3820 {
3821 "uri": "file://test.txt",
3822 "name": "Test File"
3823 }
3824 ]
3825 }),
3826 ]))
3827 .await
3828 .unwrap();
3829
3830 client.initialize("test-client", "1.0.0").await.unwrap();
3831 let resources = client.list_resources().await.unwrap();
3832
3833 assert_eq!(resources.resources.len(), 1);
3834 assert_eq!(resources.resources[0].uri, "file://test.txt");
3835 }
3836
3837 #[tokio::test]
3838 async fn test_read_resource() {
3839 let client = McpClient::connect(MockTransport::with_responses(vec![
3840 mock_initialize_response(),
3841 serde_json::json!({
3842 "contents": [
3843 {
3844 "uri": "file://test.txt",
3845 "text": "File contents"
3846 }
3847 ]
3848 }),
3849 ]))
3850 .await
3851 .unwrap();
3852
3853 client.initialize("test-client", "1.0.0").await.unwrap();
3854 let result = client.read_resource("file://test.txt").await.unwrap();
3855
3856 assert_eq!(result.contents.len(), 1);
3857 assert_eq!(result.contents[0].text.as_deref(), Some("File contents"));
3858 }
3859
3860 #[tokio::test]
3861 async fn test_list_prompts() {
3862 let client = McpClient::connect(MockTransport::with_responses(vec![
3863 mock_initialize_response(),
3864 serde_json::json!({
3865 "prompts": [
3866 {
3867 "name": "test_prompt",
3868 "description": "A test prompt"
3869 }
3870 ]
3871 }),
3872 ]))
3873 .await
3874 .unwrap();
3875
3876 client.initialize("test-client", "1.0.0").await.unwrap();
3877 let prompts = client.list_prompts().await.unwrap();
3878
3879 assert_eq!(prompts.prompts.len(), 1);
3880 assert_eq!(prompts.prompts[0].name, "test_prompt");
3881 }
3882
3883 #[tokio::test]
3884 async fn test_get_prompt() {
3885 let client = McpClient::connect(MockTransport::with_responses(vec![
3886 mock_initialize_response(),
3887 serde_json::json!({
3888 "messages": [
3889 {
3890 "role": "user",
3891 "content": {
3892 "type": "text",
3893 "text": "Prompt message"
3894 }
3895 }
3896 ]
3897 }),
3898 ]))
3899 .await
3900 .unwrap();
3901
3902 client.initialize("test-client", "1.0.0").await.unwrap();
3903 let result = client.get_prompt("test_prompt", None).await.unwrap();
3904
3905 assert_eq!(result.messages.len(), 1);
3906 }
3907
3908 #[tokio::test]
3909 async fn test_ping() {
3910 let client = McpClient::connect(MockTransport::with_responses(vec![
3911 mock_initialize_response(),
3912 serde_json::json!({}),
3913 ]))
3914 .await
3915 .unwrap();
3916
3917 client.initialize("test-client", "1.0.0").await.unwrap();
3918 let result = client.ping().await;
3919 assert!(result.is_ok());
3920 }
3921
3922 #[tokio::test]
3923 async fn test_with_roots() {
3924 let roots = vec![Root::new("file:///test")];
3925 let client = McpClient::builder()
3926 .with_roots(roots)
3927 .connect_simple(MockTransport::with_responses(vec![]))
3928 .await
3929 .unwrap();
3930
3931 let current_roots = client.roots().await;
3932 assert_eq!(current_roots.len(), 1);
3933 }
3934
3935 #[tokio::test]
3936 async fn test_roots_management() {
3937 let client = McpClient::connect(MockTransport::with_responses(vec![
3938 mock_initialize_response(),
3939 ]))
3940 .await
3941 .unwrap();
3942
3943 assert!(client.roots().await.is_empty());
3945
3946 client.add_root(Root::new("file:///project")).await.unwrap();
3948 assert_eq!(client.roots().await.len(), 1);
3949
3950 client.initialize("test-client", "1.0.0").await.unwrap();
3952
3953 let removed = client.remove_root("file:///project").await.unwrap();
3955 assert!(removed);
3956 assert!(client.roots().await.is_empty());
3957
3958 let not_removed = client.remove_root("file:///nonexistent").await.unwrap();
3960 assert!(!not_removed);
3961 }
3962
3963 #[tokio::test]
3964 async fn test_list_roots() {
3965 let roots = vec![
3966 Root::new("file:///project1"),
3967 Root::with_name("file:///project2", "Project 2"),
3968 ];
3969 let client = McpClient::builder()
3970 .with_roots(roots)
3971 .connect_simple(MockTransport::with_responses(vec![]))
3972 .await
3973 .unwrap();
3974
3975 let result = client.list_roots().await;
3976 assert_eq!(result.roots.len(), 2);
3977 assert_eq!(result.roots[1].name, Some("Project 2".to_string()));
3978 }
3979
3980 #[test]
3981 fn test_builder_with_sampling() {
3982 let builder = McpClientBuilder::new().with_sampling();
3983 assert!(builder.capabilities.sampling.is_some());
3984 }
3985
3986 #[test]
3987 fn test_builder_with_elicitation() {
3988 let builder = McpClientBuilder::new().with_elicitation();
3989 assert!(builder.capabilities.elicitation.is_some());
3990 }
3991
3992 #[test]
3993 fn builder_adds_protocol_extension_without_replacing_other_capabilities() {
3994 let extension = crate::ExtensionDeclaration::new(
3995 "com.example/rendering",
3996 serde_json::json!({"formats": ["html"]}),
3997 )
3998 .unwrap();
3999 let builder = McpClientBuilder::new()
4000 .with_sampling()
4001 .with_protocol_extension(extension);
4002
4003 assert!(builder.capabilities.sampling.is_some());
4004 assert_eq!(
4005 builder.capabilities.extensions.as_ref().unwrap()["com.example/rendering"]["formats"]
4006 [0],
4007 "html"
4008 );
4009 }
4010
4011 #[test]
4012 fn test_builder_chaining() {
4013 let builder = McpClientBuilder::new()
4014 .with_sampling()
4015 .with_elicitation()
4016 .with_roots(vec![Root::new("file:///project")]);
4017 assert!(builder.capabilities.sampling.is_some());
4018 assert!(builder.capabilities.elicitation.is_some());
4019 assert!(builder.capabilities.roots.is_some());
4020 }
4021
4022 #[tokio::test]
4023 async fn test_bidirectional_sampling_round_trip() {
4024 use crate::protocol::{
4025 ContentRole, CreateMessageParams, CreateMessageResult, SamplingContent,
4026 SamplingContentOrArray,
4027 };
4028
4029 struct RecordingHandler {
4031 called: Arc<AtomicBool>,
4032 }
4033
4034 #[async_trait]
4035 impl ClientHandler for RecordingHandler {
4036 async fn handle_create_message(
4037 &self,
4038 _params: CreateMessageParams,
4039 ) -> std::result::Result<CreateMessageResult, tower_mcp_types::JsonRpcError>
4040 {
4041 self.called.store(true, Ordering::SeqCst);
4042 Ok(CreateMessageResult {
4043 content: SamplingContentOrArray::Single(SamplingContent::Text {
4044 text: "test response".to_string(),
4045 annotations: None,
4046 meta: None,
4047 }),
4048 model: "test-model".to_string(),
4049 role: ContentRole::Assistant,
4050 stop_reason: Some("end_turn".to_string()),
4051 meta: None,
4052 })
4053 }
4054 }
4055
4056 let called = Arc::new(AtomicBool::new(false));
4057 let handler = RecordingHandler {
4058 called: called.clone(),
4059 };
4060
4061 let (inject_tx, rx) = mpsc::channel::<String>(32);
4064 let responses = vec![mock_initialize_response()];
4065 let inject_tx_clone = inject_tx.clone();
4066
4067 let transport = MockTransport {
4068 responses: Arc::new(Mutex::new(
4069 responses.into_iter().map(MockReply::Result).collect(),
4070 )),
4071 response_idx: Arc::new(std::sync::atomic::AtomicUsize::new(0)),
4072 incoming_tx: inject_tx,
4073 incoming_rx: rx,
4074 outgoing: Arc::new(Mutex::new(Vec::new())),
4075 connected: Arc::new(AtomicBool::new(true)),
4076 };
4077
4078 let client = McpClient::builder()
4079 .with_sampling()
4080 .connect(transport, handler)
4081 .await
4082 .unwrap();
4083
4084 client.initialize("test-client", "1.0.0").await.unwrap();
4086
4087 let sampling_request = serde_json::json!({
4089 "jsonrpc": "2.0",
4090 "id": 100,
4091 "method": "sampling/createMessage",
4092 "params": {
4093 "messages": [
4094 {
4095 "role": "user",
4096 "content": {
4097 "type": "text",
4098 "text": "Hello"
4099 }
4100 }
4101 ],
4102 "maxTokens": 100
4103 }
4104 });
4105 inject_tx_clone
4106 .send(sampling_request.to_string())
4107 .await
4108 .unwrap();
4109
4110 tokio::time::sleep(std::time::Duration::from_millis(100)).await;
4112
4113 assert!(
4115 called.load(Ordering::SeqCst),
4116 "handle_create_message should have been called"
4117 );
4118 }
4119
4120 #[tokio::test]
4121 async fn test_list_resource_templates() {
4122 let client = McpClient::connect(MockTransport::with_responses(vec![
4123 mock_initialize_response(),
4124 serde_json::json!({
4125 "resourceTemplates": [
4126 {
4127 "uriTemplate": "file:///{path}",
4128 "name": "File Template",
4129 "description": "A file template"
4130 }
4131 ]
4132 }),
4133 ]))
4134 .await
4135 .unwrap();
4136
4137 client.initialize("test-client", "1.0.0").await.unwrap();
4138 let result = client.list_resource_templates().await.unwrap();
4139
4140 assert_eq!(result.resource_templates.len(), 1);
4141 assert_eq!(result.resource_templates[0].name, "File Template");
4142 }
4143
4144 #[tokio::test]
4145 async fn test_list_all_tools_single_page() {
4146 let client = McpClient::connect(MockTransport::with_responses(vec![
4147 mock_initialize_response(),
4148 serde_json::json!({
4149 "tools": [
4150 {
4151 "name": "tool_a",
4152 "description": "Tool A",
4153 "inputSchema": { "type": "object", "properties": {} }
4154 },
4155 {
4156 "name": "tool_b",
4157 "description": "Tool B",
4158 "inputSchema": { "type": "object", "properties": {} }
4159 }
4160 ]
4161 }),
4162 ]))
4163 .await
4164 .unwrap();
4165
4166 client.initialize("test-client", "1.0.0").await.unwrap();
4167 let tools = client.list_all_tools().await.unwrap();
4168
4169 assert_eq!(tools.len(), 2);
4170 assert_eq!(tools[0].name, "tool_a");
4171 assert_eq!(tools[1].name, "tool_b");
4172 }
4173
4174 #[tokio::test]
4175 async fn test_list_all_tools_paginated() {
4176 let client = McpClient::connect(MockTransport::with_responses(vec![
4177 mock_initialize_response(),
4178 serde_json::json!({
4180 "tools": [
4181 {
4182 "name": "tool_a",
4183 "description": "Tool A",
4184 "inputSchema": { "type": "object", "properties": {} }
4185 }
4186 ],
4187 "nextCursor": "page2"
4188 }),
4189 serde_json::json!({
4191 "tools": [
4192 {
4193 "name": "tool_b",
4194 "description": "Tool B",
4195 "inputSchema": { "type": "object", "properties": {} }
4196 }
4197 ]
4198 }),
4199 ]))
4200 .await
4201 .unwrap();
4202
4203 client.initialize("test-client", "1.0.0").await.unwrap();
4204 let tools = client.list_all_tools().await.unwrap();
4205
4206 assert_eq!(tools.len(), 2);
4207 assert_eq!(tools[0].name, "tool_a");
4208 assert_eq!(tools[1].name, "tool_b");
4209 }
4210
4211 #[tokio::test]
4212 async fn test_call_tool_text_success() {
4213 let client = McpClient::connect(MockTransport::with_responses(vec![
4214 mock_initialize_response(),
4215 serde_json::json!({
4216 "content": [
4217 { "type": "text", "text": "Hello " },
4218 { "type": "text", "text": "World" }
4219 ]
4220 }),
4221 ]))
4222 .await
4223 .unwrap();
4224
4225 client.initialize("test-client", "1.0.0").await.unwrap();
4226 let text = client
4227 .call_tool_text("test_tool", serde_json::json!({}))
4228 .await
4229 .unwrap();
4230
4231 assert_eq!(text, "Hello World");
4232 }
4233
4234 #[tokio::test]
4235 async fn test_call_tool_text_error() {
4236 let client = McpClient::connect(MockTransport::with_responses(vec![
4237 mock_initialize_response(),
4238 serde_json::json!({
4239 "content": [
4240 { "type": "text", "text": "something went wrong" }
4241 ],
4242 "isError": true
4243 }),
4244 ]))
4245 .await
4246 .unwrap();
4247
4248 client.initialize("test-client", "1.0.0").await.unwrap();
4249 let result = client
4250 .call_tool_text("test_tool", serde_json::json!({}))
4251 .await;
4252
4253 assert!(result.is_err());
4254 let err = result.unwrap_err();
4255 assert!(
4256 err.to_string().contains("something went wrong"),
4257 "Error message should contain tool error text, got: {}",
4258 err
4259 );
4260 }
4261
4262 #[tokio::test]
4263 async fn test_server_notification_parsing() {
4264 let notification = parse_server_notification("notifications/tools/list_changed", None);
4265 assert!(matches!(notification, ServerNotification::ToolsListChanged));
4266
4267 let notification = parse_server_notification("notifications/resources/list_changed", None);
4268 assert!(matches!(
4269 notification,
4270 ServerNotification::ResourcesListChanged
4271 ));
4272
4273 let notification = parse_server_notification(
4274 "notifications/resources/updated",
4275 Some(serde_json::json!({"uri": "file:///test"})),
4276 );
4277 match notification {
4278 ServerNotification::ResourceUpdated { uri } => {
4279 assert_eq!(uri, "file:///test");
4280 }
4281 _ => panic!("Expected ResourceUpdated"),
4282 }
4283
4284 let notification =
4285 parse_server_notification("custom/notification", Some(serde_json::json!({"data": 42})));
4286 match notification {
4287 ServerNotification::Unknown { method, params } => {
4288 assert_eq!(method, "custom/notification");
4289 assert!(params.is_some());
4290 }
4291 _ => panic!("Expected Unknown"),
4292 }
4293
4294 let notification = parse_server_notification(
4295 notifications::SUBSCRIPTIONS_ACKNOWLEDGED,
4296 Some(serde_json::json!({
4297 "_meta": {
4298 "io.modelcontextprotocol/subscriptionId": 7
4299 },
4300 "notifications": {
4301 "toolsListChanged": true
4302 }
4303 })),
4304 );
4305 assert!(matches!(
4306 notification,
4307 ServerNotification::SubscriptionAcknowledged {
4308 subscription_id: RequestId::Number(7),
4309 ..
4310 }
4311 ));
4312
4313 let notification = parse_server_notification(
4314 notifications::TOOLS_LIST_CHANGED,
4315 Some(serde_json::json!({
4316 "_meta": {
4317 "io.modelcontextprotocol/subscriptionId": "stream-a"
4318 }
4319 })),
4320 );
4321 assert!(matches!(
4322 notification,
4323 ServerNotification::Subscription {
4324 subscription_id: RequestId::String(id),
4325 notification,
4326 } if id == "stream-a"
4327 && matches!(notification.as_ref(), ServerNotification::ToolsListChanged)
4328 ));
4329
4330 let notification = parse_server_notification(
4331 notifications::CANCELLED,
4332 Some(serde_json::json!({
4333 "requestId": 7,
4334 "reason": "done"
4335 })),
4336 );
4337 assert!(matches!(
4338 notification,
4339 ServerNotification::SubscriptionCancelled {
4340 subscription_id: RequestId::Number(7),
4341 reason: Some(reason),
4342 } if reason == "done"
4343 ));
4344
4345 let notification = parse_server_notification(
4346 notifications::TASK_STATUS_CHANGED,
4347 Some(serde_json::json!({
4348 "taskId": "legacy-task",
4349 "status": "completed",
4350 "createdAt": "2026-08-02T00:00:00Z",
4351 "lastUpdatedAt": "2026-08-02T00:00:01Z",
4352 "ttl": null
4353 })),
4354 );
4355 assert!(matches!(
4356 notification,
4357 ServerNotification::TaskStatusChanged(TaskStatusParams {
4358 task_id,
4359 status: crate::protocol::TaskStatus::Completed,
4360 ..
4361 }) if task_id == "legacy-task"
4362 ));
4363
4364 let notification = parse_server_notification(
4365 notifications::TASK_STATUS_CHANGED,
4366 Some(serde_json::json!({
4367 "taskId": "final-task",
4368 "status": "cancelled",
4369 "createdAt": "2026-08-02T00:00:00Z",
4370 "lastUpdatedAt": "2026-08-02T00:00:01Z",
4371 "ttlMs": null,
4372 "_meta": {
4373 "io.modelcontextprotocol/subscriptionId": "task-stream"
4374 }
4375 })),
4376 );
4377 assert!(matches!(
4378 notification,
4379 ServerNotification::Subscription {
4380 subscription_id: RequestId::String(id),
4381 notification,
4382 } if id == "task-stream"
4383 && matches!(
4384 notification.as_ref(),
4385 ServerNotification::FinalTaskStatusChanged(params)
4386 if params.task.task_id() == "final-task"
4387 && params.task.status() == crate::protocol::TaskStatus::Cancelled
4388 )
4389 ));
4390 }
4391
4392 fn pending_with(
4397 ids: &[RequestId],
4398 ) -> (
4399 HashMap<RequestId, PendingRequest>,
4400 Vec<oneshot::Receiver<Result<serde_json::Value>>>,
4401 ) {
4402 let mut map = HashMap::new();
4403 let mut rxs = Vec::new();
4404 for id in ids {
4405 let (tx, rx) = oneshot::channel();
4406 map.insert(
4407 id.clone(),
4408 PendingRequest {
4409 method: "test".to_string(),
4410 response_tx: tx,
4411 acknowledgment_tx: None,
4412 },
4413 );
4414 rxs.push(rx);
4415 }
4416 (map, rxs)
4417 }
4418
4419 #[tokio::test]
4420 async fn test_stringified_numeric_response_id_correlates() {
4421 let (mut pending, mut rxs) = pending_with(&[RequestId::Number(42)]);
4424
4425 let response = serde_json::json!({
4426 "jsonrpc": "2.0",
4427 "id": "42",
4428 "result": {"ok": true}
4429 });
4430 handle_response(&response, &mut pending);
4431
4432 assert!(pending.is_empty(), "pending request should be resolved");
4433 let result = rxs.remove(0).await.unwrap().unwrap();
4434 assert_eq!(result, serde_json::json!({"ok": true}));
4435 }
4436
4437 #[tokio::test]
4438 async fn test_exact_string_id_takes_precedence() {
4439 let (mut pending, mut rxs) =
4442 pending_with(&[RequestId::String("42".to_string()), RequestId::Number(42)]);
4443
4444 let response = serde_json::json!({
4445 "jsonrpc": "2.0",
4446 "id": "42",
4447 "result": {"which": "string"}
4448 });
4449 handle_response(&response, &mut pending);
4450
4451 assert_eq!(pending.len(), 1);
4453 assert!(pending.contains_key(&RequestId::Number(42)));
4454 let result = rxs.remove(0).await.unwrap().unwrap();
4455 assert_eq!(result, serde_json::json!({"which": "string"}));
4456 }
4457
4458 #[tokio::test]
4459 async fn test_non_numeric_string_id_does_not_correlate() {
4460 let (mut pending, _rxs) = pending_with(&[RequestId::Number(42)]);
4463
4464 let response = serde_json::json!({
4465 "jsonrpc": "2.0",
4466 "id": "not-a-number",
4467 "result": {}
4468 });
4469 handle_response(&response, &mut pending);
4470
4471 assert_eq!(pending.len(), 1, "numeric request should stay pending");
4472 }
4473}