1#![expect(deprecated)]
3pub(super) mod cache;
4
5use std::{borrow::Cow, num::NonZeroUsize, sync::Arc, time::Duration};
6
7use cache::CacheGeneration;
8pub use cache::{ClientCacheConfig, MAX_CLIENT_CACHE_TTL};
9use thiserror::Error;
10
11use super::*;
12use crate::{
13 model::{
14 ArgumentInfo, CacheScope, CallToolRequest, CallToolRequestParams, CallToolResponse,
15 CallToolResult, CancelTaskParams, CancelTaskRequest, CancelledNotification,
16 CancelledNotificationParam, ClientConfig, ClientJsonRpcMessage, ClientNotification,
17 ClientRequest, ClientResult, CompleteRequest, CompleteRequestParams, CompleteResult,
18 CompletionContext, CompletionInfo, DEFAULT_MRTR_MAX_ROUNDS, DiscoverRequest,
19 DiscoverRequestParams, DiscoverResult, ErrorData, GetExtensions, GetMeta, GetPromptRequest,
20 GetPromptRequestParams, GetPromptResponse, GetPromptResult, GetTaskParams, GetTaskRequest,
21 GetTaskResult, InitializeRequest, InitializeRequestParams, InitializedNotification,
22 InputRequest, InputRequiredResult, InputResponses, JsonRpcResponse, ListPromptsRequest,
23 ListPromptsResult, ListResourceTemplatesRequest, ListResourceTemplatesResult,
24 ListResourcesRequest, ListResourcesResult, ListToolsRequest, ListToolsResult,
25 NumberOrString, PaginatedRequestParams, ProgressNotification, ProgressNotificationParam,
26 ProtocolVersion, ReadResourceRequest, ReadResourceRequestParams, ReadResourceResponse,
27 ReadResourceResult, Reference, RequestId, RequestMetaObject, RootsListChangedNotification,
28 ServerJsonRpcMessage, ServerNotification, ServerPeerInfo, ServerRequest, ServerResult,
29 SetLevelRequest, SetLevelRequestParams, SubscribeRequest, SubscribeRequestParams,
30 SubscriptionFilter, SubscriptionsListenRequest, SubscriptionsListenRequestParams,
31 SubscriptionsListenResult, UnsubscribeRequest, UnsubscribeRequestParams, UpdateTaskParams,
32 UpdateTaskRequest,
33 },
34 transport::DynamicTransportError,
35};
36
37#[derive(Error, Debug)]
41#[non_exhaustive]
42pub enum ClientInitializeError {
43 #[error("expect initialized response, but received: {0:?}")]
44 ExpectedInitResponse(Option<ServerJsonRpcMessage>),
45
46 #[error("expect initialized result, but received: {0:?}")]
47 ExpectedInitResult(Option<ServerResult>),
48
49 #[error("conflict initialized response id: expected {0}, got {1}")]
50 ConflictInitResponseId(RequestId, RequestId),
51
52 #[error(
53 "uncorrelated error response: expected id {expected}, error response carried {received}"
54 )]
55 UncorrelatedErrorResponse {
56 expected: RequestId,
57 received: RequestId,
58 },
59
60 #[error("connection closed: {0}")]
61 ConnectionClosed(String),
62
63 #[error("Send message error {error}, when {context}")]
64 TransportError {
65 error: DynamicTransportError,
66 context: Cow<'static, str>,
67 },
68
69 #[error("JSON-RPC error: {0}")]
70 JsonRpcError(ErrorData),
71
72 #[error(
73 "no compatible protocol version (client: {client_supported:?}, server: {server_supported:?})"
74 )]
75 NoCompatibleProtocolVersion {
76 client_supported: Vec<ProtocolVersion>,
77 server_supported: Vec<ProtocolVersion>,
78 },
79
80 #[error("discover startup requires at least one preferred protocol version")]
81 NoPreferredProtocolVersion,
82
83 #[error("Cancelled")]
84 Cancelled,
85
86 #[error("discover and legacy initialize both failed")]
87 LegacyFallbackFailed {
88 discover: Box<ClientInitializeError>,
89 fallback: Box<ClientInitializeError>,
90 },
91}
92
93impl ClientInitializeError {
94 pub fn transport<T: Transport<RoleClient> + 'static>(
95 error: T::Error,
96 context: impl Into<Cow<'static, str>>,
97 ) -> Self {
98 Self::TransportError {
99 error: DynamicTransportError::new::<T, _>(error),
100 context: context.into(),
101 }
102 }
103
104 #[cfg(feature = "transport-streamable-http-client")]
110 pub fn auth_challenge(&self) -> Option<&str> {
111 use crate::transport::streamable_http_client::{AuthRequiredError, InsufficientScopeError};
112
113 let error = match self {
114 Self::TransportError { error, .. } => error,
115 Self::LegacyFallbackFailed { fallback, .. } => {
117 return fallback.auth_challenge();
118 }
119 _ => return None,
120 };
121 let mut source: Option<&(dyn std::error::Error + 'static)> = Some(error.error.as_ref());
122 while let Some(current) = source {
123 if let Some(auth_required) = current.downcast_ref::<AuthRequiredError>() {
124 return Some(&auth_required.www_authenticate_header);
125 }
126 if let Some(insufficient_scope) = current.downcast_ref::<InsufficientScopeError>() {
127 return Some(&insufficient_scope.www_authenticate_header);
128 }
129 source = current.source();
130 }
131 None
132 }
133
134 pub fn is_authorization_required(&self) -> bool {
139 match self {
140 Self::TransportError { error, .. } => error.is_authorization_required(),
141 Self::LegacyFallbackFailed { fallback, .. } => fallback.is_authorization_required(),
142 _ => false,
143 }
144 }
145}
146
147async fn expect_next_message<T>(
149 transport: &mut T,
150 context: &str,
151) -> Result<ServerJsonRpcMessage, ClientInitializeError>
152where
153 T: Transport<RoleClient>,
154{
155 transport
156 .receive()
157 .await
158 .ok_or_else(|| ClientInitializeError::ConnectionClosed(context.to_string()))
159}
160
161#[derive(Clone, Copy)]
164enum PreInitPing {
165 Answer,
166 Ignore,
167}
168
169async fn expect_response<T, S>(
181 transport: &mut T,
182 context: &str,
183 service: &S,
184 peer: Peer<RoleClient>,
185 expected_id: &RequestId,
186 pre_init_ping: PreInitPing,
187) -> Result<ServerResult, ClientInitializeError>
188where
189 T: Transport<RoleClient> + 'static,
190 S: Service<RoleClient>,
191{
192 loop {
193 let message = expect_next_message(transport, context).await?;
194 match message {
195 ServerJsonRpcMessage::Response(JsonRpcResponse { id, result, .. }) => {
196 if !expected_id.matches_response_id(&id) {
197 return Err(ClientInitializeError::ConflictInitResponseId(
198 expected_id.clone(),
199 id,
200 ));
201 }
202 return Ok(result);
203 }
204 ServerJsonRpcMessage::Error(error) => {
205 return Err(match &error.id {
206 Some(id) if expected_id.matches_response_id(id) => {
207 ClientInitializeError::JsonRpcError(error.error)
208 }
209 None => ClientInitializeError::JsonRpcError(error.error),
213 Some(id) => ClientInitializeError::UncorrelatedErrorResponse {
214 expected: expected_id.clone(),
215 received: id.clone(),
216 },
217 });
218 }
219 ServerJsonRpcMessage::Notification(mut notification) => {
221 let ServerNotification::LoggingMessageNotification(logging) =
222 &mut notification.notification
223 else {
224 tracing::warn!(?notification, "Received unexpected message");
225 continue;
226 };
227
228 let mut context = NotificationContext {
229 peer: peer.clone(),
230 meta: NotificationMetaObject::default(),
231 extensions: Extensions::default(),
232 };
233
234 if let Some(meta) = logging.extensions.get_mut::<NotificationMetaObject>() {
235 std::mem::swap(&mut context.meta, meta);
236 }
237 std::mem::swap(&mut context.extensions, &mut logging.extensions);
238
239 if let Err(error) = service
240 .handle_notification(notification.notification, context)
241 .await
242 {
243 tracing::warn!(?error, "Handle logging before handshake failed.");
244 }
245 }
246 ServerJsonRpcMessage::Request(request)
248 if matches!(request.request, ServerRequest::PingRequest(_)) =>
249 {
250 match pre_init_ping {
251 PreInitPing::Answer => {
252 transport
253 .send(ClientJsonRpcMessage::response(
254 ClientResult::empty(()),
255 request.id,
256 ))
257 .await
258 .map_err(|error| {
259 ClientInitializeError::transport::<T>(
260 error,
261 "send pre-init ping response",
262 )
263 })?;
264 }
265 PreInitPing::Ignore => tracing::trace!("Received ping request. Ignored."),
266 }
267 }
268 _ => tracing::warn!(?message, "Received unexpected message"),
270 }
271 }
272}
273
274#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
275#[expect(clippy::exhaustive_structs, reason = "intentionally exhaustive")]
276pub struct RoleClient;
277
278pub fn select_protocol_version(
282 client_preference: &[ProtocolVersion],
283 server_supported: &[ProtocolVersion],
284) -> Option<ProtocolVersion> {
285 client_preference
286 .iter()
287 .find(|version| server_supported.contains(version))
288 .cloned()
289}
290
291impl ServiceRole for RoleClient {
292 type Req = ClientRequest;
293 type Resp = ClientResult;
294 type Not = ClientNotification;
295 type PeerReq = ServerRequest;
296 type PeerResp = ServerResult;
297 type PeerNot = ServerNotification;
298 type Info = ClientConfig;
299 type PeerInfo = ServerPeerInfo;
300 type InitializeError = ClientInitializeError;
301 const IS_CLIENT: bool = true;
302
303 fn configure_direct_peer(peer: &Peer<Self>, info: &Self::Info) {
304 let Some(server_info) = peer.peer_info() else {
305 return;
306 };
307 if server_info.protocol_version.has_initialize() {
308 return;
309 }
310 peer.set_client_request_metadata(ClientRequestMetadata {
311 protocol_version: server_info.protocol_version.clone(),
312 client_info: info.client_info.clone(),
313 client_capabilities: info.capabilities.clone(),
314 });
315 }
316
317 fn peer_cancelled_params(notification: &Self::PeerNot) -> Option<&CancelledNotificationParam> {
318 match notification {
319 ServerNotification::CancelledNotification(notification) => Some(¬ification.params),
320 _ => None,
321 }
322 }
323
324 fn peer_cancels_subscriptions(peer: &Peer<Self>) -> bool {
325 !super::uses_legacy_lifecycle(
327 peer.peer_info()
328 .as_deref()
329 .map(|info| &info.protocol_version),
330 peer.client_request_metadata.get().is_some(),
331 )
332 }
333
334 fn is_subscription_request(request: &Self::Req) -> bool {
335 matches!(request, ClientRequest::SubscriptionsListenRequest(_))
336 }
337
338 fn enforce_peer_request_association(
342 peer_request: &Self::PeerReq,
343 peer_info: Option<&Self::PeerInfo>,
344 association: PeerRequestAssociation,
345 ) -> Result<(), ErrorData> {
346 let restricted = matches!(
347 peer_request,
348 ServerRequest::CreateMessageRequest(_)
349 | ServerRequest::ListRootsRequest(_)
350 | ServerRequest::ElicitRequest(_)
351 );
352 if !restricted {
353 return Ok(());
354 }
355 let strict =
356 peer_info.is_some_and(|info| info.protocol_version >= ProtocolVersion::V_2026_07_28);
357 if !strict {
358 return Ok(());
359 }
360 let associated = match association {
361 PeerRequestAssociation::Associated => true,
362 PeerRequestAssociation::Unassociated => false,
363 PeerRequestAssociation::Unknown {
364 has_pending_outbound_request,
365 } => has_pending_outbound_request,
366 };
367 if associated {
368 Ok(())
369 } else {
370 Err(ErrorData::invalid_params(
371 "SEP-2260: server-to-client requests must be associated with an in-flight client request",
372 None,
373 ))
374 }
375 }
376
377 async fn invalidate_response_cache(peer: &Peer<Self>, notification: &Self::PeerNot) {
378 match notification {
379 ServerNotification::ResourceUpdatedNotification(notification) => {
380 peer.invalidate_resource_read_cache(¬ification.params.uri)
381 .await;
382 }
383 ServerNotification::ResourceListChangedNotification(_) => {
384 peer.invalidate_resource_list_cache().await;
385 }
386 ServerNotification::ToolListChangedNotification(_) => {
387 peer.invalidate_tool_cache().await;
388 }
389 ServerNotification::PromptListChangedNotification(_) => {
390 peer.invalidate_prompt_cache().await;
391 }
392 _ => {}
393 }
394 }
395}
396
397pub type ServerSink = Peer<RoleClient>;
398
399pub const DEFAULT_SUBSCRIPTION_CHANNEL_CAPACITY: usize = 64;
401
402#[derive(Debug, Clone, PartialEq)]
404#[non_exhaustive]
405pub enum SubscriptionEnd {
406 Graceful(SubscriptionsListenResult),
408 Abrupt,
411 Cancelled,
413 Lagged { capacity: usize },
415}
416
417#[derive(Debug)]
419pub struct Subscription {
420 id: RequestId,
421 acknowledged: SubscriptionFilter,
422 notifications: tokio::sync::mpsc::Receiver<ServerNotification>,
423 request: Option<RequestHandle<RoleClient>>,
424 end: Option<SubscriptionEnd>,
425}
426
427type SubscriptionResponse =
428 Result<Result<ServerResult, ServiceError>, tokio::sync::oneshot::error::RecvError>;
429
430struct PendingSubscriptionRequest {
431 handle: Option<RequestHandle<RoleClient>>,
432}
433
434impl PendingSubscriptionRequest {
435 fn new(handle: RequestHandle<RoleClient>) -> Self {
436 Self {
437 handle: Some(handle),
438 }
439 }
440
441 async fn recv(&mut self) -> Option<SubscriptionResponse> {
442 let handle = self.handle.as_mut()?;
443 Some((&mut handle.rx).await)
444 }
445
446 fn take(&mut self) -> Option<RequestHandle<RoleClient>> {
447 self.handle.take()
448 }
449
450 fn unregister(&self, id: &RequestId) {
451 if let Some(handle) = self.handle.as_ref() {
452 handle.peer.unregister_subscription(id);
453 }
454 }
455
456 fn disarm(&mut self) {
457 self.handle.take();
458 }
459
460 async fn cancel(&mut self, reason: &'static str) {
461 if let Some(handle) = self.handle.take() {
462 let _ = handle.cancel(Some(reason.to_owned())).await;
463 }
464 }
465}
466
467impl Drop for PendingSubscriptionRequest {
468 fn drop(&mut self) {
469 let Some(handle) = self.handle.take() else {
470 return;
471 };
472 handle.peer.unregister_subscription(&handle.id);
473 handle.peer.try_cancel_request(
474 handle.id,
475 Some("subscription establishment cancelled".to_owned()),
476 );
477 }
478}
479
480struct AbandonedRequestGuard<'a> {
487 peer: &'a Peer<RoleClient>,
488 id: Option<RequestId>,
489}
490
491impl<'a> AbandonedRequestGuard<'a> {
492 const REASON: &'static str = "request abandoned by the caller";
493
494 fn new(peer: &'a Peer<RoleClient>, id: RequestId) -> Self {
495 Self { peer, id: Some(id) }
496 }
497
498 fn disarm(&mut self) {
499 self.id.take();
500 }
501}
502
503impl Drop for AbandonedRequestGuard<'_> {
504 fn drop(&mut self) {
505 let Some(id) = self.id.take() else {
506 return;
507 };
508 self.peer
509 .try_cancel_request(id, Some(Self::REASON.to_owned()));
510 }
511}
512
513impl Subscription {
514 pub fn id(&self) -> &RequestId {
516 &self.id
517 }
518
519 pub fn acknowledged(&self) -> &SubscriptionFilter {
521 &self.acknowledged
522 }
523
524 pub fn end(&self) -> Option<&SubscriptionEnd> {
526 self.end.as_ref()
527 }
528
529 pub async fn next(&mut self) -> Result<Option<ServerNotification>, ServiceError> {
539 if self.end.is_some() {
540 return Ok(None);
541 }
542 let Some(request) = self.request.as_mut() else {
543 self.end = Some(SubscriptionEnd::Abrupt);
544 return Ok(None);
545 };
546
547 tokio::select! {
548 biased;
549 notification = self.notifications.recv() => {
550 let Some(notification) = notification else {
551 let response = (&mut request.rx).await;
552 return self.handle_response(response);
553 };
554 if let ServerNotification::CancelledNotification(cancelled) = ¬ification {
555 if cancelled.params.request_id.as_ref() != Some(&self.id) {
556 self.cancel_as_abrupt("subscription cancellation ID mismatch")
557 .await;
558 return Err(ServiceError::UnexpectedResponse);
559 }
560 self.finish(SubscriptionEnd::Cancelled);
561 return Ok(None);
562 }
563 if notification.get_meta().subscription_id().as_ref() != Some(&self.id) {
564 self.cancel_as_abrupt("subscription notification ID mismatch")
565 .await;
566 return Err(ServiceError::UnexpectedResponse);
567 }
568 if !self.accepts(¬ification) {
569 self.cancel_as_abrupt(
570 "subscription notification was outside the acknowledged filter",
571 )
572 .await;
573 return Err(ServiceError::UnexpectedResponse);
574 }
575 Ok(Some(notification))
576 }
577 response = &mut request.rx => {
578 self.handle_response(response)
579 }
580 }
581 }
582
583 pub async fn cancel(&mut self) -> Result<(), ServiceError> {
589 self.cancel_with_reason(None).await
590 }
591
592 pub async fn cancel_with_reason(&mut self, reason: Option<String>) -> Result<(), ServiceError> {
598 let Some(request) = self.request.take() else {
599 return Ok(());
600 };
601 request.cancel(reason).await?;
602 self.end = Some(SubscriptionEnd::Cancelled);
603 Ok(())
604 }
605
606 fn finish(&mut self, end: SubscriptionEnd) {
607 if let Some(request) = self.request.take() {
608 request.peer.unregister_subscription(&self.id);
609 }
610 self.end = Some(end);
611 }
612
613 async fn cancel_as_abrupt(&mut self, reason: &'static str) {
614 if let Some(request) = self.request.take() {
615 let _ = request.cancel(Some(reason.to_owned())).await;
616 }
617 self.end = Some(SubscriptionEnd::Abrupt);
618 }
619
620 fn accepts(&self, notification: &ServerNotification) -> bool {
621 match notification {
622 ServerNotification::ToolListChangedNotification(_) => {
623 self.acknowledged.tools_list_changed == Some(true)
624 }
625 ServerNotification::PromptListChangedNotification(_) => {
626 self.acknowledged.prompts_list_changed == Some(true)
627 }
628 ServerNotification::ResourceListChangedNotification(_) => {
629 self.acknowledged.resources_list_changed == Some(true)
630 }
631 ServerNotification::ResourceUpdatedNotification(update) => self
632 .acknowledged
633 .resource_subscriptions
634 .as_ref()
635 .is_some_and(|uris| uris.contains(&update.params.uri)),
636 ServerNotification::SubscriptionsAcknowledgedNotification(_)
637 | ServerNotification::CancelledNotification(_)
638 | ServerNotification::ProgressNotification(_)
639 | ServerNotification::LoggingMessageNotification(_)
640 | ServerNotification::TaskStatusNotification(_)
641 | ServerNotification::CustomNotification(_) => false,
642 }
643 }
644
645 fn handle_response(
646 &mut self,
647 response: SubscriptionResponse,
648 ) -> Result<Option<ServerNotification>, ServiceError> {
649 let response = match response {
650 Ok(response) => response,
651 Err(_) => {
652 self.finish(SubscriptionEnd::Abrupt);
653 return Ok(None);
654 }
655 };
656 let response = match response {
657 Ok(response) => response,
658 Err(ServiceError::TransportClosed) => {
659 self.finish(SubscriptionEnd::Abrupt);
660 return Ok(None);
661 }
662 Err(ServiceError::SubscriptionLagged { capacity }) => {
663 self.finish(SubscriptionEnd::Lagged { capacity });
664 return Ok(None);
665 }
666 Err(error) => {
667 self.finish(SubscriptionEnd::Abrupt);
668 return Err(error);
669 }
670 };
671 let ServerResult::SubscriptionsListenResult(result) = response else {
672 self.finish(SubscriptionEnd::Abrupt);
673 return Err(ServiceError::UnexpectedResponse);
674 };
675 if !result.result_type.is_complete()
676 || result.meta.subscription_id().as_ref() != Some(&self.id)
677 {
678 self.finish(SubscriptionEnd::Abrupt);
679 return Err(ServiceError::UnexpectedResponse);
680 }
681 self.finish(SubscriptionEnd::Graceful(result));
682 Ok(None)
683 }
684}
685
686impl Drop for Subscription {
687 fn drop(&mut self) {
688 let Some(request) = self.request.take() else {
689 return;
690 };
691 request.peer.unregister_subscription(&self.id);
692 request.peer.try_cancel_request(
693 self.id.clone(),
694 Some("subscription handle dropped".to_owned()),
695 );
696 }
697}
698
699#[derive(Debug, Clone, PartialEq, Eq)]
703#[non_exhaustive]
704pub enum ClientLifecycleMode {
705 Initialize,
707 Discover {
709 preferred_versions: Vec<ProtocolVersion>,
710 },
711 Auto {
714 preferred_versions: Vec<ProtocolVersion>,
715 legacy_version: Option<ProtocolVersion>,
716 },
717}
718
719const DEFAULT_AUTO_DISCOVER_TIMEOUT: Duration = Duration::from_secs(10);
720
721pub trait ClientServiceExt: Service<RoleClient> + Sized {
723 fn serve_with_lifecycle<T, E, A>(
724 self,
725 transport: T,
726 lifecycle: ClientLifecycleMode,
727 ) -> impl Future<Output = Result<RunningService<RoleClient, Self>, ClientInitializeError>>
728 + MaybeSendFuture
729 where
730 T: IntoTransport<RoleClient, E, A>,
731 E: std::error::Error + Send + Sync + 'static,
732 {
733 serve_client_with_lifecycle(self, transport, lifecycle)
734 }
735}
736
737impl<S: Service<RoleClient>> ClientServiceExt for S {}
738
739impl<S: Service<RoleClient>> ServiceExt<RoleClient> for S {
740 fn serve_with_ct<T, E, A>(
741 self,
742 transport: T,
743 ct: CancellationToken,
744 ) -> impl Future<Output = Result<RunningService<RoleClient, Self>, ClientInitializeError>>
745 + MaybeSendFuture
746 where
747 T: IntoTransport<RoleClient, E, A>,
748 E: std::error::Error + Send + Sync + 'static,
749 Self: Sized,
750 {
751 serve_client_with_ct(self, transport, ct)
752 }
753}
754
755pub async fn serve_client<S, T, E, A>(
756 service: S,
757 transport: T,
758) -> Result<RunningService<RoleClient, S>, ClientInitializeError>
759where
760 S: Service<RoleClient>,
761 T: IntoTransport<RoleClient, E, A>,
762 E: std::error::Error + Send + Sync + 'static,
763{
764 serve_client_with_lifecycle_and_ct(
765 service,
766 transport,
767 ClientLifecycleMode::Initialize,
768 Default::default(),
769 )
770 .await
771}
772
773pub async fn serve_client_with_ct<S, T, E, A>(
774 service: S,
775 transport: T,
776 ct: CancellationToken,
777) -> Result<RunningService<RoleClient, S>, ClientInitializeError>
778where
779 S: Service<RoleClient>,
780 T: IntoTransport<RoleClient, E, A>,
781 E: std::error::Error + Send + Sync + 'static,
782{
783 serve_client_with_lifecycle_and_ct(service, transport, ClientLifecycleMode::Initialize, ct)
784 .await
785}
786
787pub async fn serve_client_with_lifecycle<S, T, E, A>(
788 service: S,
789 transport: T,
790 lifecycle: ClientLifecycleMode,
791) -> Result<RunningService<RoleClient, S>, ClientInitializeError>
792where
793 S: Service<RoleClient>,
794 T: IntoTransport<RoleClient, E, A>,
795 E: std::error::Error + Send + Sync + 'static,
796{
797 serve_client_with_lifecycle_and_ct(service, transport, lifecycle, Default::default()).await
798}
799
800pub async fn serve_client_with_lifecycle_and_ct<S, T, E, A>(
801 service: S,
802 transport: T,
803 lifecycle: ClientLifecycleMode,
804 ct: CancellationToken,
805) -> Result<RunningService<RoleClient, S>, ClientInitializeError>
806where
807 S: Service<RoleClient>,
808 T: IntoTransport<RoleClient, E, A>,
809 E: std::error::Error + Send + Sync + 'static,
810{
811 tokio::select! {
812 result = serve_client_with_ct_inner(
813 service,
814 transport.into_transport(),
815 lifecycle,
816 ct.clone(),
817 DEFAULT_AUTO_DISCOVER_TIMEOUT,
818 ) => { result }
819 _ = ct.cancelled() => {
820 Err(ClientInitializeError::Cancelled)
821 }
822 }
823}
824
825async fn serve_client_with_ct_inner<S, T>(
826 service: S,
827 transport: T,
828 lifecycle: ClientLifecycleMode,
829 ct: CancellationToken,
830 auto_discover_timeout: Duration,
831) -> Result<RunningService<RoleClient, S>, ClientInitializeError>
832where
833 S: Service<RoleClient>,
834 T: Transport<RoleClient> + 'static,
835{
836 let mut transport = transport.into_transport();
837 let id_provider = <Arc<AtomicU32RequestIdProvider>>::default();
838 let (peer, peer_rx) = Peer::new(id_provider.clone(), None);
839 let client_info = service.get_info();
840
841 match lifecycle {
842 ClientLifecycleMode::Initialize => {
843 legacy_startup(&service, &mut transport, &id_provider, &peer, client_info).await?;
844 }
845 ClientLifecycleMode::Discover { preferred_versions } => {
846 match discover_startup(
847 &service,
848 &mut transport,
849 &id_provider,
850 &peer,
851 &client_info,
852 preferred_versions,
853 )
854 .await?
855 {
856 DiscoverOutcome::Modern => {}
857 DiscoverOutcome::Legacy(error) => return Err(*error),
859 }
860 }
861 ClientLifecycleMode::Auto {
862 preferred_versions,
863 legacy_version,
864 } => {
865 let discover_result = tokio::time::timeout(
866 auto_discover_timeout,
867 discover_startup(
868 &service,
869 &mut transport,
870 &id_provider,
871 &peer,
872 &client_info,
873 preferred_versions,
874 ),
875 )
876 .await;
877 match discover_result {
878 Ok(Ok(DiscoverOutcome::Modern)) => {}
879 Ok(Ok(DiscoverOutcome::Legacy(discover_error))) => {
880 let mut legacy_info = client_info;
881 if let Some(version) = legacy_version {
882 legacy_info.protocol_version = version;
883 }
884 if let Err(fallback_error) =
885 legacy_startup(&service, &mut transport, &id_provider, &peer, legacy_info)
886 .await
887 {
888 return Err(ClientInitializeError::LegacyFallbackFailed {
889 discover: discover_error,
890 fallback: Box::new(fallback_error),
891 });
892 }
893 }
894 Ok(Err(error)) => return Err(error),
895 Err(_) => {
896 let mut legacy_info = client_info;
897 if let Some(version) = legacy_version {
898 legacy_info.protocol_version = version;
899 }
900 legacy_startup(&service, &mut transport, &id_provider, &peer, legacy_info)
901 .await?;
902 }
903 }
904 }
905 }
906 Ok(serve_inner(
907 service,
908 transport,
909 peer,
910 peer_rx,
911 VecDeque::new(),
912 ct,
913 ))
914}
915
916fn is_modern_rejection_code(code: crate::model::ErrorCode) -> bool {
924 matches!(
925 code,
926 crate::model::ErrorCode::MISSING_REQUIRED_CLIENT_CAPABILITY
927 | crate::model::ErrorCode::HEADER_MISMATCH
928 )
929}
930
931enum DiscoverOutcome {
942 Modern,
944 Legacy(Box<ClientInitializeError>),
949}
950
951async fn legacy_startup<S, T>(
952 service: &S,
953 transport: &mut T,
954 id_provider: &Arc<AtomicU32RequestIdProvider>,
955 peer: &Peer<RoleClient>,
956 client_info: InitializeRequestParams,
957) -> Result<(), ClientInitializeError>
958where
959 S: Service<RoleClient>,
960 T: Transport<RoleClient> + 'static,
961{
962 let id = id_provider.next_request_id();
963 let client_info_for_meta = client_info.clone();
964 let init_request = InitializeRequest {
965 method: Default::default(),
966 params: client_info,
967 extensions: Default::default(),
968 };
969 transport
970 .send(ClientJsonRpcMessage::request(
971 ClientRequest::InitializeRequest(init_request),
972 id.clone(),
973 ))
974 .await
975 .map_err(|error| ClientInitializeError::TransportError {
976 error: DynamicTransportError::new::<T, _>(error),
977 context: "send initialize request".into(),
978 })?;
979
980 let response = expect_response(
981 transport,
982 "initialize response",
983 service,
984 peer.clone(),
985 &id,
986 PreInitPing::Answer,
987 )
988 .await?;
989
990 let ServerResult::InitializeResult(initialize_result) = response else {
991 return Err(ClientInitializeError::ExpectedInitResult(Some(response)));
992 };
993 if !initialize_result.protocol_version.has_initialize() {
998 peer.set_client_request_metadata(ClientRequestMetadata {
999 protocol_version: initialize_result.protocol_version.clone(),
1000 client_info: client_info_for_meta.client_info,
1001 client_capabilities: client_info_for_meta.capabilities,
1002 });
1003 }
1004 peer.set_peer_info(initialize_result.into());
1005
1006 let notification = ClientJsonRpcMessage::notification(
1008 ClientNotification::InitializedNotification(InitializedNotification {
1009 method: Default::default(),
1010 extensions: Default::default(),
1011 }),
1012 );
1013 transport.send(notification).await.map_err(|error| {
1014 ClientInitializeError::transport::<T>(error, "send initialized notification")
1015 })?;
1016 Ok(())
1017}
1018
1019async fn discover_startup<S, T>(
1020 service: &S,
1021 transport: &mut T,
1022 id_provider: &Arc<AtomicU32RequestIdProvider>,
1023 peer: &Peer<RoleClient>,
1024 client_info: &InitializeRequestParams,
1025 preferred_versions: Vec<ProtocolVersion>,
1026) -> Result<DiscoverOutcome, ClientInitializeError>
1027where
1028 S: Service<RoleClient>,
1029 T: Transport<RoleClient> + 'static,
1030{
1031 if preferred_versions.is_empty() {
1032 return Err(ClientInitializeError::NoPreferredProtocolVersion);
1033 }
1034
1035 let mut attempted = Vec::new();
1036 let mut candidate = preferred_versions[0].clone();
1037 loop {
1038 attempted.push(candidate.clone());
1039
1040 let meta = RequestMetaObject::with_client_context(
1041 candidate.clone(),
1042 client_info.client_info.clone(),
1043 client_info.capabilities.clone(),
1044 );
1045 let mut discover = DiscoverRequest::new(DiscoverRequestParams {});
1046 discover.extensions.insert(meta);
1047 let id = id_provider.next_request_id();
1048 transport
1049 .send(ClientJsonRpcMessage::request(
1050 ClientRequest::DiscoverRequest(discover),
1051 id.clone(),
1052 ))
1053 .await
1054 .map_err(|error| {
1055 ClientInitializeError::transport::<T>(error, "send discover request")
1056 })?;
1057
1058 match expect_response(
1059 transport,
1060 "discover response",
1061 service,
1062 peer.clone(),
1063 &id,
1064 PreInitPing::Ignore,
1065 )
1066 .await
1067 {
1068 Ok(ServerResult::DiscoverResult(result)) => {
1069 let Some(selected) =
1070 select_protocol_version(&preferred_versions, &result.supported_versions)
1071 else {
1072 return Err(ClientInitializeError::NoCompatibleProtocolVersion {
1073 client_supported: preferred_versions,
1074 server_supported: result.supported_versions,
1075 });
1076 };
1077 peer.set_peer_info(ServerPeerInfo::from_discover_result(
1078 selected.clone(),
1079 result,
1080 ));
1081 peer.set_client_request_metadata(ClientRequestMetadata {
1082 protocol_version: selected,
1083 client_info: client_info.client_info.clone(),
1084 client_capabilities: client_info.capabilities.clone(),
1085 });
1086 return Ok(DiscoverOutcome::Modern);
1087 }
1088 Ok(response) => {
1089 return Err(ClientInitializeError::ExpectedInitResult(Some(response)));
1090 }
1091 Err(ClientInitializeError::JsonRpcError(error))
1092 if error.code == crate::model::ErrorCode::UNSUPPORTED_PROTOCOL_VERSION =>
1093 {
1094 let supported = error
1095 .data
1096 .as_ref()
1097 .and_then(|data| data.get("supported"))
1098 .cloned()
1099 .and_then(|value| serde_json::from_value::<Vec<ProtocolVersion>>(value).ok())
1100 .unwrap_or_default();
1101 let may_retry_current = attempted
1102 .iter()
1103 .filter(|version| *version == &candidate)
1104 .count()
1105 == 1;
1106 let next = preferred_versions
1107 .iter()
1108 .find(|version| {
1109 supported.contains(version)
1110 && (!attempted.contains(version)
1111 || (may_retry_current && *version == &candidate))
1112 })
1113 .cloned();
1114 let Some(next) = next else {
1115 return Err(ClientInitializeError::NoCompatibleProtocolVersion {
1116 client_supported: preferred_versions,
1117 server_supported: supported,
1118 });
1119 };
1120 candidate = next;
1121 }
1122 Err(error)
1127 if matches!(
1128 &error,
1129 ClientInitializeError::JsonRpcError(data)
1130 if !is_modern_rejection_code(data.code)
1131 ) =>
1132 {
1133 return Ok(DiscoverOutcome::Legacy(Box::new(error)));
1134 }
1135 Err(error) => return Err(error),
1136 }
1137 }
1138}
1139
1140const DISCOVER_CACHE_PREFIX: &str = "server/discover:";
1141const TOOL_LIST_CACHE_PREFIX: &str = "tools/list:";
1142const PROMPT_LIST_CACHE_PREFIX: &str = "prompts/list:";
1143const RESOURCE_LIST_CACHE_PREFIX: &str = "resources/list:";
1144const RESOURCE_TEMPLATE_LIST_CACHE_PREFIX: &str = "resources/templates/list:";
1145const RESOURCE_READ_CACHE_PREFIX: &str = "resources/read:";
1146
1147fn discover_cache_key() -> String {
1152 DISCOVER_CACHE_PREFIX.to_string()
1154}
1155
1156fn list_response_cache_key(prefix: &str, params: &Option<PaginatedRequestParams>) -> String {
1157 let cursor = params.as_ref().and_then(|params| params.cursor.as_deref());
1159 let cursor =
1160 serde_json::to_string(&cursor).expect("serializing a pagination cursor cannot fail");
1161 format!("{prefix}{cursor}")
1162}
1163
1164fn resource_read_cache_key(params: &ReadResourceRequestParams) -> Option<String> {
1165 if params.input_responses.is_some() || params.request_state.is_some() {
1168 return None;
1169 }
1170 Some(resource_read_cache_prefix_for_uri(¶ms.uri))
1172}
1173
1174fn resource_read_cache_prefix_for_uri(uri: &str) -> String {
1175 let uri = serde_json::to_string(uri).expect("serializing a resource URI cannot fail");
1176 format!("{RESOURCE_READ_CACHE_PREFIX}{uri}:")
1177}
1178
1179fn request_uses_cursor(params: &Option<PaginatedRequestParams>) -> bool {
1180 params
1181 .as_ref()
1182 .and_then(|params| params.cursor.as_ref())
1183 .is_some()
1184}
1185
1186macro_rules! method {
1187 ($(#[$meta:meta])* peer_req $method:ident $Req:ident() => $Resp: ident ) => {
1188 $(#[$meta])*
1189 pub async fn $method(&self) -> Result<$Resp, ServiceError> {
1190 let result = self
1191 .send_request_cancel_on_drop(ClientRequest::$Req($Req {
1192 method: Default::default(),
1193 }))
1194 .await?;
1195 match result {
1196 ServerResult::$Resp(result) => Ok(result),
1197 _ => Err(ServiceError::UnexpectedResponse),
1198 }
1199 }
1200 };
1201 ($(#[$meta:meta])* peer_req $method:ident $Req:ident($Param: ident) => $Resp: ident ) => {
1202 $(#[$meta])*
1203 pub async fn $method(&self, params: $Param) -> Result<$Resp, ServiceError> {
1204 let result = self
1205 .send_request_cancel_on_drop(ClientRequest::$Req($Req {
1206 method: Default::default(),
1207 params,
1208 extensions: Default::default(),
1209 }))
1210 .await?;
1211 match result {
1212 ServerResult::$Resp(result) => Ok(result),
1213 _ => Err(ServiceError::UnexpectedResponse),
1214 }
1215 }
1216 };
1217 ($(#[$meta:meta])* peer_req $method:ident $Req:ident($Param: ident)? => $Resp: ident ) => {
1218 $(#[$meta])*
1219 pub async fn $method(&self, params: Option<$Param>) -> Result<$Resp, ServiceError> {
1220 let result = self
1221 .send_request_cancel_on_drop(ClientRequest::$Req($Req {
1222 method: Default::default(),
1223 params,
1224 extensions: Default::default(),
1225 }))
1226 .await?;
1227 match result {
1228 ServerResult::$Resp(result) => Ok(result),
1229 _ => Err(ServiceError::UnexpectedResponse),
1230 }
1231 }
1232 };
1233 ($(#[$meta:meta])* peer_req $method:ident $Req:ident($Param: ident)) => {
1234 $(#[$meta])*
1235 pub async fn $method(&self, params: $Param) -> Result<(), ServiceError> {
1236 let result = self
1237 .send_request_cancel_on_drop(ClientRequest::$Req($Req {
1238 method: Default::default(),
1239 params,
1240 extensions: Default::default(),
1241 }))
1242 .await?;
1243 match result {
1244 ServerResult::EmptyResult(_) => Ok(()),
1245 _ => Err(ServiceError::UnexpectedResponse),
1246 }
1247 }
1248 };
1249
1250 ($(#[$meta:meta])* peer_not $method:ident $Not:ident($Param: ident)) => {
1251 $(#[$meta])*
1252 pub async fn $method(&self, params: $Param) -> Result<(), ServiceError> {
1253 self.send_notification(ClientNotification::$Not($Not {
1254 method: Default::default(),
1255 params,
1256 extensions: Default::default(),
1257 }))
1258 .await?;
1259 Ok(())
1260 }
1261 };
1262 ($(#[$meta:meta])* peer_not $method:ident $Not:ident) => {
1263 $(#[$meta])*
1264 pub async fn $method(&self) -> Result<(), ServiceError> {
1265 self.send_notification(ClientNotification::$Not($Not {
1266 method: Default::default(),
1267 extensions: Default::default(),
1268 }))
1269 .await?;
1270 Ok(())
1271 }
1272 };
1273}
1274
1275impl Peer<RoleClient> {
1276 pub async fn listen(
1286 &self,
1287 notifications: SubscriptionFilter,
1288 ) -> Result<Subscription, ServiceError> {
1289 self.listen_with_channel_capacity_inner(
1290 notifications,
1291 DEFAULT_SUBSCRIPTION_CHANNEL_CAPACITY,
1292 )
1293 .await
1294 }
1295
1296 pub async fn listen_with_capacity(
1306 &self,
1307 notifications: SubscriptionFilter,
1308 channel_capacity: NonZeroUsize,
1309 ) -> Result<Subscription, ServiceError> {
1310 self.listen_with_channel_capacity_inner(notifications, channel_capacity.get())
1311 .await
1312 }
1313
1314 async fn listen_with_channel_capacity_inner(
1315 &self,
1316 notifications: SubscriptionFilter,
1317 channel_capacity: usize,
1318 ) -> Result<Subscription, ServiceError> {
1319 let request = ClientRequest::SubscriptionsListenRequest(SubscriptionsListenRequest::new(
1320 SubscriptionsListenRequestParams::new(notifications.clone()),
1321 ));
1322 let (handle, mut subscription_notifications) = self
1323 .send_subscription_request(request, PeerRequestOptions::no_options(), channel_capacity)
1324 .await?;
1325 let id = handle.id.clone();
1326 let mut pending = PendingSubscriptionRequest::new(handle);
1327
1328 tokio::select! {
1329 biased;
1330 notification = subscription_notifications.recv() => {
1331 let Some(notification) = notification else {
1332 pending.cancel("subscription stream closed before acknowledgment").await;
1333 return Err(ServiceError::TransportClosed);
1334 };
1335 if notification.get_meta().subscription_id().as_ref() != Some(&id) {
1336 pending.cancel("subscription acknowledgment ID mismatch").await;
1337 return Err(ServiceError::UnexpectedResponse);
1338 }
1339 let ServerNotification::SubscriptionsAcknowledgedNotification(
1340 acknowledgment,
1341 ) = notification else {
1342 pending.cancel("notification received before subscription acknowledgment").await;
1343 return Err(ServiceError::UnexpectedResponse);
1344 };
1345 let accepted = acknowledgment.params.notifications;
1346 if !accepted.is_subset_of(¬ifications) {
1347 pending.cancel("subscription acknowledged an unrequested filter").await;
1348 return Err(ServiceError::UnexpectedResponse);
1349 }
1350 let Some(handle) = pending.take() else {
1351 return Err(ServiceError::TransportClosed);
1352 };
1353 Ok(Subscription {
1354 id,
1355 acknowledged: accepted,
1356 notifications: subscription_notifications,
1357 request: Some(handle),
1358 end: None,
1359 })
1360 }
1361 response = pending.recv() => {
1362 pending.unregister(&id);
1363 pending.disarm();
1364 let Some(response) = response else {
1365 return Err(ServiceError::TransportClosed);
1366 };
1367 match response {
1368 Ok(Err(error)) => Err(error),
1369 Ok(Ok(_)) => Err(ServiceError::UnexpectedResponse),
1370 Err(_) => Err(ServiceError::TransportClosed),
1371 }
1372 }
1373 }
1374 }
1375
1376 pub async fn discover(&self, meta: RequestMetaObject) -> Result<DiscoverResult, ServiceError> {
1381 let cache_key = discover_cache_key();
1382 if let Some(ServerResult::DiscoverResult(result)) = self.cached_response(&cache_key).await {
1383 return Ok(result);
1384 }
1385 let generation = self.capture_response_cache_generation().await;
1386 let mut request = DiscoverRequest::new(DiscoverRequestParams {});
1387 request.extensions.insert(meta);
1388 let result = self
1389 .send_request(ClientRequest::DiscoverRequest(request))
1390 .await;
1391 let result = match result {
1392 Ok(result) => result,
1393 Err(error) => {
1394 if let Some(ServerResult::DiscoverResult(result)) =
1395 self.stale_cached_response(&cache_key).await
1396 {
1397 return Ok(result);
1398 }
1399 return Err(error);
1400 }
1401 };
1402 match result {
1403 ServerResult::DiscoverResult(result) => {
1404 self.cache_result(
1405 Some(cache_key),
1406 Some(result.ttl_ms),
1407 Some(result.cache_scope),
1408 generation,
1409 ServerResult::DiscoverResult(result.clone()),
1410 )
1411 .await;
1412 Ok(result)
1413 }
1414 _ => Err(ServiceError::UnexpectedResponse),
1415 }
1416 }
1417
1418 async fn cache_result(
1419 &self,
1420 cache_key: Option<String>,
1421 ttl_ms: Option<u64>,
1422 cache_scope: Option<CacheScope>,
1423 generation: CacheGeneration,
1424 result: ServerResult,
1425 ) {
1426 let Some(cache_key) = cache_key else {
1427 return;
1428 };
1429 self.cache_response_with_generation(cache_key, result, ttl_ms, cache_scope, generation)
1430 .await;
1431 }
1432
1433 pub(crate) async fn invalidate_tool_cache(&self) {
1434 self.invalidate_cached_responses(TOOL_LIST_CACHE_PREFIX)
1435 .await;
1436 }
1437
1438 pub(crate) async fn invalidate_prompt_cache(&self) {
1439 self.invalidate_cached_responses(PROMPT_LIST_CACHE_PREFIX)
1440 .await;
1441 }
1442
1443 pub(crate) async fn invalidate_resource_list_cache(&self) {
1444 self.invalidate_cached_responses(RESOURCE_LIST_CACHE_PREFIX)
1445 .await;
1446 self.invalidate_cached_responses(RESOURCE_TEMPLATE_LIST_CACHE_PREFIX)
1447 .await;
1448 }
1449
1450 pub(crate) async fn invalidate_resource_read_cache(&self, uri: &str) {
1451 self.invalidate_cached_responses(&resource_read_cache_prefix_for_uri(uri))
1452 .await;
1453 }
1454
1455 async fn send_request_cancel_on_drop(
1458 &self,
1459 request: ClientRequest,
1460 ) -> Result<ServerResult, ServiceError> {
1461 let handle = self
1462 .send_request_with_option(request, PeerRequestOptions::no_options())
1463 .await?;
1464 let mut guard = AbandonedRequestGuard::new(self, handle.id.clone());
1465 let result = handle.await_response().await;
1466 guard.disarm();
1467 result
1468 }
1469
1470 pub async fn call_tool_once(
1473 &self,
1474 params: CallToolRequestParams,
1475 ) -> Result<CallToolResponse, ServiceError> {
1476 let result = self
1477 .send_request_cancel_on_drop(ClientRequest::CallToolRequest(CallToolRequest {
1478 method: Default::default(),
1479 params,
1480 extensions: Default::default(),
1481 }))
1482 .await?;
1483 match result {
1484 ServerResult::CallToolResult(result) => Ok(CallToolResponse::Complete(result)),
1485 ServerResult::InputRequiredResult(result) => {
1486 Ok(CallToolResponse::InputRequired(result))
1487 }
1488 ServerResult::CreateTaskResult(result) => Ok(CallToolResponse::Task(result)),
1490 _ => Err(ServiceError::UnexpectedResponse),
1491 }
1492 }
1493
1494 pub async fn get_task(&self, params: GetTaskParams) -> Result<GetTaskResult, ServiceError> {
1496 let result = self
1497 .send_request_cancel_on_drop(ClientRequest::GetTaskRequest(GetTaskRequest::new(params)))
1498 .await?;
1499 match result {
1500 ServerResult::GetTaskResult(result) => Ok(result),
1501 _ => Err(ServiceError::UnexpectedResponse),
1502 }
1503 }
1504
1505 pub async fn update_task(&self, params: UpdateTaskParams) -> Result<(), ServiceError> {
1508 let result = self
1509 .send_request_cancel_on_drop(ClientRequest::UpdateTaskRequest(UpdateTaskRequest::new(
1510 params,
1511 )))
1512 .await?;
1513 match result {
1514 ServerResult::TaskAckResult(_) | ServerResult::EmptyResult(_) => Ok(()),
1515 _ => Err(ServiceError::UnexpectedResponse),
1516 }
1517 }
1518
1519 pub async fn cancel_task(&self, params: CancelTaskParams) -> Result<(), ServiceError> {
1522 let result = self
1523 .send_request_cancel_on_drop(ClientRequest::CancelTaskRequest(CancelTaskRequest::new(
1524 params,
1525 )))
1526 .await?;
1527 match result {
1528 ServerResult::TaskAckResult(_) | ServerResult::EmptyResult(_) => Ok(()),
1529 _ => Err(ServiceError::UnexpectedResponse),
1530 }
1531 }
1532
1533 pub async fn get_prompt_once(
1536 &self,
1537 params: GetPromptRequestParams,
1538 ) -> Result<GetPromptResponse, ServiceError> {
1539 let result = self
1540 .send_request_cancel_on_drop(ClientRequest::GetPromptRequest(GetPromptRequest {
1541 method: Default::default(),
1542 params,
1543 extensions: Default::default(),
1544 }))
1545 .await?;
1546 match result {
1547 ServerResult::GetPromptResult(result) => Ok(GetPromptResponse::Complete(result)),
1548 ServerResult::InputRequiredResult(result) => {
1549 Ok(GetPromptResponse::InputRequired(result))
1550 }
1551 _ => Err(ServiceError::UnexpectedResponse),
1552 }
1553 }
1554
1555 pub async fn read_resource_once(
1558 &self,
1559 params: ReadResourceRequestParams,
1560 ) -> Result<ReadResourceResponse, ServiceError> {
1561 let cache_key = resource_read_cache_key(¶ms);
1562 if let Some(key) = cache_key.as_deref()
1563 && let Some(ServerResult::ReadResourceResult(result)) = self.cached_response(key).await
1564 {
1565 return Ok(ReadResourceResponse::Complete(result));
1566 }
1567
1568 let generation = self.capture_response_cache_generation().await;
1569 let result = self
1570 .send_request_cancel_on_drop(ClientRequest::ReadResourceRequest(ReadResourceRequest {
1571 method: Default::default(),
1572 params,
1573 extensions: Default::default(),
1574 }))
1575 .await;
1576 let result = match result {
1577 Ok(result) => result,
1578 Err(error) => {
1579 if let Some(key) = cache_key.as_deref()
1580 && let Some(ServerResult::ReadResourceResult(result)) =
1581 self.stale_cached_response(key).await
1582 {
1583 return Ok(ReadResourceResponse::Complete(result));
1584 }
1585 return Err(error);
1586 }
1587 };
1588 match result {
1589 ServerResult::ReadResourceResult(result) => {
1590 self.cache_result(
1591 cache_key,
1592 result.ttl_ms,
1593 result.cache_scope,
1594 generation,
1595 ServerResult::ReadResourceResult(result.clone()),
1596 )
1597 .await;
1598 Ok(ReadResourceResponse::Complete(result))
1599 }
1600 ServerResult::InputRequiredResult(result) => {
1601 Ok(ReadResourceResponse::InputRequired(result))
1602 }
1603 _ => Err(ServiceError::UnexpectedResponse),
1604 }
1605 }
1606
1607 method!(peer_req complete CompleteRequest(CompleteRequestParams) => CompleteResult);
1608 method!(
1609 #[deprecated(
1610 since = "1.8.0",
1611 note = "Logging is deprecated by SEP-2577 and will be removed in a future release. See https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2577"
1612 )]
1613 peer_req set_level SetLevelRequest(SetLevelRequestParams)
1614 );
1615 method!(peer_req get_prompt GetPromptRequest(GetPromptRequestParams) => GetPromptResult);
1616 method!(
1617 #[deprecated(
1618 note = "resources/subscribe is legacy-only; use Peer::listen for protocol version 2026-07-28"
1619 )]
1620 peer_req subscribe SubscribeRequest(SubscribeRequestParams)
1621 );
1622 method!(
1623 #[deprecated(
1624 note = "resources/unsubscribe is legacy-only; cancel the Subscription handle instead"
1625 )]
1626 peer_req unsubscribe UnsubscribeRequest(UnsubscribeRequestParams)
1627 );
1628 method!(peer_req call_tool CallToolRequest(CallToolRequestParams) => CallToolResult);
1629
1630 pub async fn list_prompts(
1631 &self,
1632 params: Option<PaginatedRequestParams>,
1633 ) -> Result<ListPromptsResult, ServiceError> {
1634 let cache_key = list_response_cache_key(PROMPT_LIST_CACHE_PREFIX, ¶ms);
1635 if let Some(ServerResult::ListPromptsResult(result)) =
1636 self.cached_response(&cache_key).await
1637 {
1638 return Ok(result);
1639 }
1640 let generation = self.capture_response_cache_generation().await;
1641 let uses_cursor = request_uses_cursor(¶ms);
1642 let result = self
1643 .send_request_cancel_on_drop(ClientRequest::ListPromptsRequest(ListPromptsRequest {
1644 method: Default::default(),
1645 params,
1646 extensions: Default::default(),
1647 }))
1648 .await;
1649 let result = match result {
1650 Ok(result) => result,
1651 Err(error) => {
1652 if uses_cursor {
1653 self.invalidate_prompt_cache().await;
1654 return Err(error);
1655 }
1656 if let Some(ServerResult::ListPromptsResult(result)) =
1657 self.stale_cached_response(&cache_key).await
1658 {
1659 return Ok(result);
1660 }
1661 return Err(error);
1662 }
1663 };
1664 match result {
1665 ServerResult::ListPromptsResult(result) => {
1666 self.cache_result(
1667 Some(cache_key),
1668 result.ttl_ms,
1669 result.cache_scope,
1670 generation,
1671 ServerResult::ListPromptsResult(result.clone()),
1672 )
1673 .await;
1674 Ok(result)
1675 }
1676 _ => Err(ServiceError::UnexpectedResponse),
1677 }
1678 }
1679
1680 pub async fn list_resources(
1681 &self,
1682 params: Option<PaginatedRequestParams>,
1683 ) -> Result<ListResourcesResult, ServiceError> {
1684 let cache_key = list_response_cache_key(RESOURCE_LIST_CACHE_PREFIX, ¶ms);
1685 if let Some(ServerResult::ListResourcesResult(result)) =
1686 self.cached_response(&cache_key).await
1687 {
1688 return Ok(result);
1689 }
1690 let generation = self.capture_response_cache_generation().await;
1691 let uses_cursor = request_uses_cursor(¶ms);
1692 let result = self
1693 .send_request_cancel_on_drop(ClientRequest::ListResourcesRequest(
1694 ListResourcesRequest {
1695 method: Default::default(),
1696 params,
1697 extensions: Default::default(),
1698 },
1699 ))
1700 .await;
1701 let result = match result {
1702 Ok(result) => result,
1703 Err(error) => {
1704 if uses_cursor {
1705 self.invalidate_cached_responses(RESOURCE_LIST_CACHE_PREFIX)
1706 .await;
1707 return Err(error);
1708 }
1709 if let Some(ServerResult::ListResourcesResult(result)) =
1710 self.stale_cached_response(&cache_key).await
1711 {
1712 return Ok(result);
1713 }
1714 return Err(error);
1715 }
1716 };
1717 match result {
1718 ServerResult::ListResourcesResult(result) => {
1719 self.cache_result(
1720 Some(cache_key),
1721 result.ttl_ms,
1722 result.cache_scope,
1723 generation,
1724 ServerResult::ListResourcesResult(result.clone()),
1725 )
1726 .await;
1727 Ok(result)
1728 }
1729 _ => Err(ServiceError::UnexpectedResponse),
1730 }
1731 }
1732
1733 pub async fn list_resource_templates(
1734 &self,
1735 params: Option<PaginatedRequestParams>,
1736 ) -> Result<ListResourceTemplatesResult, ServiceError> {
1737 let cache_key = list_response_cache_key(RESOURCE_TEMPLATE_LIST_CACHE_PREFIX, ¶ms);
1738 if let Some(ServerResult::ListResourceTemplatesResult(result)) =
1739 self.cached_response(&cache_key).await
1740 {
1741 return Ok(result);
1742 }
1743 let generation = self.capture_response_cache_generation().await;
1744 let uses_cursor = request_uses_cursor(¶ms);
1745 let result = self
1746 .send_request_cancel_on_drop(ClientRequest::ListResourceTemplatesRequest(
1747 ListResourceTemplatesRequest {
1748 method: Default::default(),
1749 params,
1750 extensions: Default::default(),
1751 },
1752 ))
1753 .await;
1754 let result = match result {
1755 Ok(result) => result,
1756 Err(error) => {
1757 if uses_cursor {
1758 self.invalidate_cached_responses(RESOURCE_TEMPLATE_LIST_CACHE_PREFIX)
1759 .await;
1760 return Err(error);
1761 }
1762 if let Some(ServerResult::ListResourceTemplatesResult(result)) =
1763 self.stale_cached_response(&cache_key).await
1764 {
1765 return Ok(result);
1766 }
1767 return Err(error);
1768 }
1769 };
1770 match result {
1771 ServerResult::ListResourceTemplatesResult(result) => {
1772 self.cache_result(
1773 Some(cache_key),
1774 result.ttl_ms,
1775 result.cache_scope,
1776 generation,
1777 ServerResult::ListResourceTemplatesResult(result.clone()),
1778 )
1779 .await;
1780 Ok(result)
1781 }
1782 _ => Err(ServiceError::UnexpectedResponse),
1783 }
1784 }
1785
1786 pub async fn read_resource(
1787 &self,
1788 params: ReadResourceRequestParams,
1789 ) -> Result<ReadResourceResult, ServiceError> {
1790 match self.read_resource_once(params).await? {
1791 ReadResourceResponse::Complete(result) => Ok(result),
1792 ReadResourceResponse::InputRequired(_) => Err(ServiceError::UnexpectedResponse),
1793 }
1794 }
1795
1796 pub async fn list_tools(
1797 &self,
1798 params: Option<PaginatedRequestParams>,
1799 ) -> Result<ListToolsResult, ServiceError> {
1800 let cache_key = list_response_cache_key(TOOL_LIST_CACHE_PREFIX, ¶ms);
1801 if let Some(ServerResult::ListToolsResult(result)) = self.cached_response(&cache_key).await
1802 {
1803 return Ok(result);
1804 }
1805 let generation = self.capture_response_cache_generation().await;
1806 let uses_cursor = request_uses_cursor(¶ms);
1807 let result = self
1808 .send_request_cancel_on_drop(ClientRequest::ListToolsRequest(ListToolsRequest {
1809 method: Default::default(),
1810 params,
1811 extensions: Default::default(),
1812 }))
1813 .await;
1814 let result = match result {
1815 Ok(result) => result,
1816 Err(error) => {
1817 if uses_cursor {
1818 self.invalidate_tool_cache().await;
1819 return Err(error);
1820 }
1821 if let Some(ServerResult::ListToolsResult(result)) =
1822 self.stale_cached_response(&cache_key).await
1823 {
1824 return Ok(result);
1825 }
1826 return Err(error);
1827 }
1828 };
1829 match result {
1830 ServerResult::ListToolsResult(result) => {
1831 self.cache_result(
1832 Some(cache_key),
1833 result.ttl_ms,
1834 result.cache_scope,
1835 generation,
1836 ServerResult::ListToolsResult(result.clone()),
1837 )
1838 .await;
1839 Ok(result)
1840 }
1841 _ => Err(ServiceError::UnexpectedResponse),
1842 }
1843 }
1844
1845 method!(peer_not notify_cancelled CancelledNotification(CancelledNotificationParam));
1846 method!(peer_not notify_progress ProgressNotification(ProgressNotificationParam));
1847 method!(peer_not notify_initialized InitializedNotification);
1848 method!(peer_not notify_roots_list_changed RootsListChangedNotification);
1849}
1850
1851impl Peer<RoleClient> {
1852 pub async fn list_all_tools(&self) -> Result<Vec<crate::model::Tool>, ServiceError> {
1856 let mut tools = Vec::new();
1857 let mut cursor = None;
1858 loop {
1859 let result = self
1860 .list_tools(Some(PaginatedRequestParams { meta: None, cursor }))
1861 .await?;
1862 tools.extend(result.tools);
1863 cursor = result.next_cursor;
1864 if cursor.is_none() {
1865 break;
1866 }
1867 }
1868 Ok(tools)
1869 }
1870
1871 pub async fn list_all_prompts(&self) -> Result<Vec<crate::model::Prompt>, ServiceError> {
1875 let mut prompts = Vec::new();
1876 let mut cursor = None;
1877 loop {
1878 let result = self
1879 .list_prompts(Some(PaginatedRequestParams { meta: None, cursor }))
1880 .await?;
1881 prompts.extend(result.prompts);
1882 cursor = result.next_cursor;
1883 if cursor.is_none() {
1884 break;
1885 }
1886 }
1887 Ok(prompts)
1888 }
1889
1890 pub async fn list_all_resources(&self) -> Result<Vec<crate::model::Resource>, ServiceError> {
1894 let mut resources = Vec::new();
1895 let mut cursor = None;
1896 loop {
1897 let result = self
1898 .list_resources(Some(PaginatedRequestParams { meta: None, cursor }))
1899 .await?;
1900 resources.extend(result.resources);
1901 cursor = result.next_cursor;
1902 if cursor.is_none() {
1903 break;
1904 }
1905 }
1906 Ok(resources)
1907 }
1908
1909 pub async fn list_all_resource_templates(
1913 &self,
1914 ) -> Result<Vec<crate::model::ResourceTemplate>, ServiceError> {
1915 let mut resource_templates = Vec::new();
1916 let mut cursor = None;
1917 loop {
1918 let result = self
1919 .list_resource_templates(Some(PaginatedRequestParams { meta: None, cursor }))
1920 .await?;
1921 resource_templates.extend(result.resource_templates);
1922 cursor = result.next_cursor;
1923 if cursor.is_none() {
1924 break;
1925 }
1926 }
1927 Ok(resource_templates)
1928 }
1929
1930 pub async fn complete_prompt_argument(
1941 &self,
1942 prompt_name: impl Into<String>,
1943 argument_name: impl Into<String>,
1944 current_value: impl Into<String>,
1945 context: Option<CompletionContext>,
1946 ) -> Result<CompletionInfo, ServiceError> {
1947 let request = CompleteRequestParams {
1948 meta: None,
1949 r#ref: Reference::for_prompt(prompt_name),
1950 argument: ArgumentInfo {
1951 name: argument_name.into(),
1952 value: current_value.into(),
1953 },
1954 context,
1955 };
1956
1957 let result = self.complete(request).await?;
1958 Ok(result.completion)
1959 }
1960
1961 pub async fn complete_resource_argument(
1972 &self,
1973 uri_template: impl Into<String>,
1974 argument_name: impl Into<String>,
1975 current_value: impl Into<String>,
1976 context: Option<CompletionContext>,
1977 ) -> Result<CompletionInfo, ServiceError> {
1978 let request = CompleteRequestParams {
1979 meta: None,
1980 r#ref: Reference::for_resource(uri_template),
1981 argument: ArgumentInfo {
1982 name: argument_name.into(),
1983 value: current_value.into(),
1984 },
1985 context,
1986 };
1987
1988 let result = self.complete(request).await?;
1989 Ok(result.completion)
1990 }
1991
1992 pub async fn complete_prompt_simple(
1997 &self,
1998 prompt_name: impl Into<String>,
1999 argument_name: impl Into<String>,
2000 current_value: impl Into<String>,
2001 ) -> Result<Vec<String>, ServiceError> {
2002 let completion = self
2003 .complete_prompt_argument(prompt_name, argument_name, current_value, None)
2004 .await?;
2005 Ok(completion.values)
2006 }
2007
2008 pub async fn complete_resource_simple(
2013 &self,
2014 uri_template: impl Into<String>,
2015 argument_name: impl Into<String>,
2016 current_value: impl Into<String>,
2017 ) -> Result<Vec<String>, ServiceError> {
2018 let completion = self
2019 .complete_resource_argument(uri_template, argument_name, current_value, None)
2020 .await?;
2021 Ok(completion.values)
2022 }
2023}
2024
2025impl<S> RunningService<RoleClient, S>
2026where
2027 S: Service<RoleClient>,
2028{
2029 pub async fn call_tool_once(
2031 &self,
2032 params: CallToolRequestParams,
2033 ) -> Result<CallToolResponse, ServiceError> {
2034 self.peer.call_tool_once(params).await
2035 }
2036
2037 pub async fn get_prompt_once(
2039 &self,
2040 params: GetPromptRequestParams,
2041 ) -> Result<GetPromptResponse, ServiceError> {
2042 self.peer.get_prompt_once(params).await
2043 }
2044
2045 pub async fn read_resource_once(
2047 &self,
2048 params: ReadResourceRequestParams,
2049 ) -> Result<ReadResourceResponse, ServiceError> {
2050 self.peer.read_resource_once(params).await
2051 }
2052
2053 pub async fn call_tool(
2062 &self,
2063 params: CallToolRequestParams,
2064 ) -> Result<CallToolResult, ServiceError> {
2065 self.call_tool_with_mrtr_max_rounds(params, DEFAULT_MRTR_MAX_ROUNDS)
2066 .await
2067 }
2068
2069 pub async fn call_tool_with_mrtr_max_rounds(
2078 &self,
2079 mut params: CallToolRequestParams,
2080 max_rounds: usize,
2081 ) -> Result<CallToolResult, ServiceError> {
2082 let mut state_only_rounds = 0usize;
2083 for _round in 0..max_rounds {
2084 match self.peer.call_tool_once(params.clone()).await? {
2085 CallToolResponse::Complete(result) => return Ok(result),
2086 CallToolResponse::InputRequired(result) => {
2087 let (input_responses, request_state) = self
2088 .prepare_input_required_retry(result, &mut state_only_rounds)
2089 .await?;
2090 params.input_responses = input_responses;
2091 params.request_state = request_state;
2092 }
2093 CallToolResponse::Task(_) => return Err(ServiceError::UnexpectedResponse),
2097 }
2098 }
2099 Err(ServiceError::InputRequiredRoundsExceeded { max_rounds })
2100 }
2101
2102 pub async fn get_prompt(
2111 &self,
2112 params: GetPromptRequestParams,
2113 ) -> Result<GetPromptResult, ServiceError> {
2114 self.get_prompt_with_mrtr_max_rounds(params, DEFAULT_MRTR_MAX_ROUNDS)
2115 .await
2116 }
2117
2118 pub async fn get_prompt_with_mrtr_max_rounds(
2127 &self,
2128 mut params: GetPromptRequestParams,
2129 max_rounds: usize,
2130 ) -> Result<GetPromptResult, ServiceError> {
2131 let mut state_only_rounds = 0usize;
2132 for _round in 0..max_rounds {
2133 match self.peer.get_prompt_once(params.clone()).await? {
2134 GetPromptResponse::Complete(result) => return Ok(result),
2135 GetPromptResponse::InputRequired(result) => {
2136 let (input_responses, request_state) = self
2137 .prepare_input_required_retry(result, &mut state_only_rounds)
2138 .await?;
2139 params.input_responses = input_responses;
2140 params.request_state = request_state;
2141 }
2142 }
2143 }
2144 Err(ServiceError::InputRequiredRoundsExceeded { max_rounds })
2145 }
2146
2147 pub async fn read_resource(
2157 &self,
2158 params: ReadResourceRequestParams,
2159 ) -> Result<ReadResourceResult, ServiceError> {
2160 self.read_resource_with_mrtr_max_rounds(params, DEFAULT_MRTR_MAX_ROUNDS)
2161 .await
2162 }
2163
2164 pub async fn read_resource_with_mrtr_max_rounds(
2173 &self,
2174 mut params: ReadResourceRequestParams,
2175 max_rounds: usize,
2176 ) -> Result<ReadResourceResult, ServiceError> {
2177 let mut state_only_rounds = 0usize;
2178 for _round in 0..max_rounds {
2179 match self.peer.read_resource_once(params.clone()).await? {
2180 ReadResourceResponse::Complete(result) => return Ok(result),
2181 ReadResourceResponse::InputRequired(result) => {
2182 let (input_responses, request_state) = self
2183 .prepare_input_required_retry(result, &mut state_only_rounds)
2184 .await?;
2185 params.input_responses = input_responses;
2186 params.request_state = request_state;
2187 }
2188 }
2189 }
2190 Err(ServiceError::InputRequiredRoundsExceeded { max_rounds })
2191 }
2192
2193 async fn prepare_input_required_retry(
2194 &self,
2195 result: InputRequiredResult,
2196 state_only_rounds: &mut usize,
2197 ) -> Result<(Option<InputResponses>, Option<String>), ServiceError> {
2198 let had_input_requests = result
2199 .input_requests
2200 .as_ref()
2201 .is_some_and(|requests| !requests.is_empty());
2202 if !had_input_requests && result.request_state.is_none() {
2203 return Err(ServiceError::UnexpectedResponse);
2204 }
2205
2206 let responses = self
2207 .fulfill_input_requests(result.input_requests.unwrap_or_default())
2208 .await?;
2209 if had_input_requests {
2210 *state_only_rounds = 0;
2211 } else {
2212 Self::sleep_state_only_round(*state_only_rounds).await;
2213 *state_only_rounds += 1;
2214 }
2215
2216 Ok((
2217 (!responses.is_empty()).then_some(responses),
2218 result.request_state,
2219 ))
2220 }
2221
2222 async fn fulfill_input_requests(
2223 &self,
2224 requests: crate::model::InputRequests,
2225 ) -> Result<InputResponses, ServiceError> {
2226 let responses = futures::future::try_join_all(
2227 requests
2228 .into_iter()
2229 .map(|(key, request)| self.fulfill_input_request(key, request)),
2230 )
2231 .await?;
2232 Ok(responses.into_iter().collect())
2233 }
2234
2235 async fn fulfill_input_request(
2236 &self,
2237 key: String,
2238 request: InputRequest,
2239 ) -> Result<(String, serde_json::Value), ServiceError> {
2240 let response = match request {
2241 InputRequest::CreateMessage(request) => {
2242 let mut request = ServerRequest::CreateMessageRequest(request);
2243 let context = self.input_request_context(&key, &mut request);
2244 match self
2245 .service
2246 .handle_request(request, context)
2247 .await
2248 .map_err(ServiceError::McpError)?
2249 {
2250 ClientResult::CreateMessageResult(result) => {
2251 serde_json::to_value(result).map_err(Self::serde_to_service_error)?
2252 }
2253 _ => return Err(ServiceError::UnexpectedResponse),
2254 }
2255 }
2256 InputRequest::Elicitation(request) => {
2257 let mut request = ServerRequest::ElicitRequest(request);
2258 let context = self.input_request_context(&key, &mut request);
2259 match self
2260 .service
2261 .handle_request(request, context)
2262 .await
2263 .map_err(ServiceError::McpError)?
2264 {
2265 ClientResult::ElicitResult(result) => {
2266 serde_json::to_value(result).map_err(Self::serde_to_service_error)?
2267 }
2268 _ => return Err(ServiceError::UnexpectedResponse),
2269 }
2270 }
2271 InputRequest::ListRoots(request) => {
2272 let mut request = ServerRequest::ListRootsRequest(request);
2273 let context = self.input_request_context(&key, &mut request);
2274 match self
2275 .service
2276 .handle_request(request, context)
2277 .await
2278 .map_err(ServiceError::McpError)?
2279 {
2280 ClientResult::ListRootsResult(result) => {
2281 serde_json::to_value(result).map_err(Self::serde_to_service_error)?
2282 }
2283 _ => return Err(ServiceError::UnexpectedResponse),
2284 }
2285 }
2286 };
2287 Ok((key, response))
2288 }
2289
2290 fn input_request_context<T>(&self, key: &str, request: &mut T) -> RequestContext<RoleClient>
2291 where
2292 T: GetMeta<Metadata = crate::model::RequestMetaObject> + GetExtensions,
2293 {
2294 let mut meta = Default::default();
2295 let mut extensions = Default::default();
2296 std::mem::swap(&mut meta, request.get_meta_mut());
2297 std::mem::swap(&mut extensions, request.extensions_mut());
2298 RequestContext {
2299 ct: tokio_util::sync::CancellationToken::new(),
2300 id: NumberOrString::String(Arc::from(key)),
2301 peer: self.peer.clone(),
2302 meta,
2303 extensions,
2304 }
2305 }
2306
2307 async fn sleep_state_only_round(state_only_rounds: usize) {
2308 let millis = (50u64.saturating_mul(1_u64 << state_only_rounds.min(3))).min(250);
2309 tokio::time::sleep(Duration::from_millis(millis)).await;
2310 }
2311
2312 fn serde_to_service_error(error: serde_json::Error) -> ServiceError {
2313 ServiceError::McpError(ErrorData::internal_error(
2314 format!("failed to serialize MRTR input response: {error}"),
2315 None,
2316 ))
2317 }
2318}
2319
2320#[cfg(test)]
2321mod tests {
2322 use super::*;
2323
2324 #[tokio::test]
2325 async fn server_cancellation_retires_subscription_responder() {
2326 tokio::task::LocalSet::new()
2327 .run_until(async {
2328 for (discover, version) in [
2329 (false, ProtocolVersion::V_2026_07_28),
2330 (true, ProtocolVersion::V_2026_07_28),
2331 (true, ProtocolVersion::V_2025_11_25),
2332 ] {
2333 tokio::time::timeout(
2334 Duration::from_secs(5),
2335 check_subscription_cancellation(discover, version),
2336 )
2337 .await
2338 .expect("subscription cancellation timed out");
2339 }
2340 })
2341 .await;
2342 }
2343
2344 async fn check_subscription_cancellation(discover: bool, version: ProtocolVersion) {
2345 use crate::model::{
2346 GetMeta, PingRequest, ServerInfo, SubscriptionsAcknowledgedNotification,
2347 SubscriptionsAcknowledgedNotificationParams,
2348 };
2349
2350 let (server_transport, client_transport) = tokio::io::duplex(4096);
2351 let mut server =
2352 crate::transport::IntoTransport::<RoleServer, _, _>::into_transport(server_transport);
2353 let info = ServerInfo {
2354 protocol_version: version.clone(),
2355 ..Default::default()
2356 };
2357 let client = if discover {
2358 let (client, ()) = tokio::join!(
2359 serve_client_with_lifecycle(
2360 (),
2361 client_transport,
2362 ClientLifecycleMode::Discover {
2363 preferred_versions: vec![version.clone()]
2364 }
2365 ),
2366 async {
2367 let Some(ClientJsonRpcMessage::Request(request)) = server.receive().await
2368 else {
2369 panic!("expected discover request");
2370 };
2371 assert!(matches!(request.request, ClientRequest::DiscoverRequest(_)));
2372 server
2373 .send(ServerJsonRpcMessage::response(
2374 ServerResult::DiscoverResult(DiscoverResult::new(
2375 vec![version],
2376 info.capabilities,
2377 )),
2378 request.id,
2379 ))
2380 .await
2381 .unwrap();
2382 }
2383 );
2384 client.unwrap()
2385 } else {
2386 super::super::serve_directly::<RoleClient, _, _, _, _>(
2387 (),
2388 client_transport,
2389 Some(info.into()),
2390 )
2391 };
2392 let filter = SubscriptionFilter::default();
2393 let (subscription, ()) = tokio::join!(client.listen(filter.clone()), async {
2394 let Some(ClientJsonRpcMessage::Request(request)) = server.receive().await else {
2395 panic!("expected listen request");
2396 };
2397 assert!(matches!(
2398 request.request,
2399 ClientRequest::SubscriptionsListenRequest(_)
2400 ));
2401 let mut ack: ServerNotification = SubscriptionsAcknowledgedNotification::new(
2402 SubscriptionsAcknowledgedNotificationParams::new(filter),
2403 )
2404 .into();
2405 ack.get_meta_mut().set_subscription_id(request.id);
2406 server
2407 .send(ServerJsonRpcMessage::notification(ack))
2408 .await
2409 .unwrap();
2410 });
2411 let mut subscription = subscription.unwrap();
2412 let id = subscription.id().clone();
2413 let mut ordinary = client
2414 .send_cancellable_request(
2415 PingRequest {
2416 method: Default::default(),
2417 extensions: Default::default(),
2418 }
2419 .into(),
2420 PeerRequestOptions::no_options(),
2421 )
2422 .await
2423 .unwrap();
2424 assert!(matches!(
2425 server.receive().await,
2426 Some(ClientJsonRpcMessage::Request(_))
2427 ));
2428 for unknown in [
2429 ordinary.id.clone(),
2430 RequestId::String(id.to_string().into()),
2431 RequestId::String("unknown".into()),
2432 ] {
2433 server
2434 .send(ServerJsonRpcMessage::notification(
2435 CancelledNotification::new(CancelledNotificationParam::new(
2436 Some(unknown),
2437 None,
2438 ))
2439 .into(),
2440 ))
2441 .await
2442 .unwrap();
2443 }
2444 server
2445 .send(ServerJsonRpcMessage::request(
2446 PingRequest {
2447 method: Default::default(),
2448 extensions: Default::default(),
2449 }
2450 .into(),
2451 RequestId::String("before-cancellation".into()),
2452 ))
2453 .await
2454 .unwrap();
2455 assert!(matches!(
2456 server.receive().await,
2457 Some(ClientJsonRpcMessage::Response(_))
2458 ));
2459 assert!(matches!(
2460 subscription.request.as_mut().unwrap().rx.try_recv(),
2461 Err(tokio::sync::oneshot::error::TryRecvError::Empty)
2462 ));
2463 assert!(matches!(
2464 ordinary.rx.try_recv(),
2465 Err(tokio::sync::oneshot::error::TryRecvError::Empty)
2466 ));
2467 assert!(matches!(
2468 subscription.notifications.try_recv(),
2469 Err(tokio::sync::mpsc::error::TryRecvError::Empty)
2470 ));
2471 server
2472 .send(ServerJsonRpcMessage::notification(
2473 CancelledNotification::new(CancelledNotificationParam::new(
2474 Some(id.clone()),
2475 Some("subscription ended".to_owned()),
2476 ))
2477 .into(),
2478 ))
2479 .await
2480 .unwrap();
2481 server
2483 .send(ServerJsonRpcMessage::request(
2484 PingRequest {
2485 method: Default::default(),
2486 extensions: Default::default(),
2487 }
2488 .into(),
2489 RequestId::String("after-cancellation".into()),
2490 ))
2491 .await
2492 .unwrap();
2493 assert!(matches!(
2494 server.receive().await,
2495 Some(ClientJsonRpcMessage::Response(_))
2496 ));
2497 assert!(
2498 matches!(
2499 subscription.request.as_mut().unwrap().rx.try_recv(),
2500 Ok(Err(ServiceError::Cancelled { .. }))
2501 ),
2502 "the outbound responder must retire without a final response or disconnect"
2503 );
2504 assert!(subscription.next().await.unwrap().is_none());
2505 assert!(matches!(
2506 subscription.end(),
2507 Some(SubscriptionEnd::Cancelled)
2508 ));
2509 assert!(client.peer().subscription_sender(&id).is_none());
2510 server
2511 .send(ServerJsonRpcMessage::response(
2512 ServerResult::empty(()),
2513 ordinary.id.clone(),
2514 ))
2515 .await
2516 .unwrap();
2517 assert!(ordinary.await_response().await.is_ok());
2518
2519 let raw = client
2521 .send_cancellable_request(
2522 ClientRequest::SubscriptionsListenRequest(SubscriptionsListenRequest::new(
2523 SubscriptionsListenRequestParams::new(SubscriptionFilter::default()),
2524 )),
2525 PeerRequestOptions::no_options(),
2526 )
2527 .await
2528 .unwrap();
2529 assert!(matches!(
2530 server.receive().await,
2531 Some(ClientJsonRpcMessage::Request(_))
2532 ));
2533 server
2534 .send(ServerJsonRpcMessage::notification(
2535 CancelledNotification::new(CancelledNotificationParam::new(
2536 Some(raw.id.clone()),
2537 None,
2538 ))
2539 .into(),
2540 ))
2541 .await
2542 .unwrap();
2543 assert!(matches!(
2544 raw.await_response().await,
2545 Err(ServiceError::Cancelled { .. })
2546 ));
2547 client.cancel().await.unwrap();
2548 }
2549
2550 #[tokio::test]
2551 async fn auto_startup_falls_back_when_discover_is_ignored() {
2552 use crate::model::{InitializeResult, ServerCapabilities};
2553
2554 tokio::task::LocalSet::new()
2555 .run_until(async {
2556 let (server_transport, client_transport) = tokio::io::duplex(4096);
2557 let mut server =
2558 crate::transport::IntoTransport::<RoleServer, _, _>::into_transport(
2559 server_transport,
2560 );
2561 let server_task = tokio::task::spawn_local(async move {
2562 let ClientJsonRpcMessage::Request(discover) =
2563 server.receive().await.expect("expected discover request")
2564 else {
2565 panic!("expected discover request");
2566 };
2567 assert!(matches!(
2568 discover.request,
2569 ClientRequest::DiscoverRequest(_)
2570 ));
2571
2572 let ClientJsonRpcMessage::Request(initialize) =
2573 server.receive().await.expect("expected initialize request")
2574 else {
2575 panic!("expected initialize request");
2576 };
2577 assert!(matches!(
2578 initialize.request,
2579 ClientRequest::InitializeRequest(_)
2580 ));
2581 server
2582 .send(ServerJsonRpcMessage::response(
2583 ServerResult::InitializeResult(InitializeResult::new(
2584 ServerCapabilities::default(),
2585 )),
2586 initialize.id,
2587 ))
2588 .await
2589 .expect("send initialize response");
2590 assert!(matches!(
2591 server.receive().await,
2592 Some(ClientJsonRpcMessage::Notification(_))
2593 ));
2594 });
2595
2596 let client_transport =
2597 crate::transport::IntoTransport::<RoleClient, _, _>::into_transport(
2598 client_transport,
2599 );
2600 let client = serve_client_with_ct_inner(
2601 (),
2602 client_transport,
2603 ClientLifecycleMode::Auto {
2604 preferred_versions: vec![ProtocolVersion::V_2026_07_28],
2605 legacy_version: Some(ProtocolVersion::V_2025_11_25),
2606 },
2607 CancellationToken::new(),
2608 Duration::from_millis(25),
2609 )
2610 .await
2611 .expect("auto client should fall back after discover timeout");
2612 client.cancel().await.expect("cancel client");
2613 server_task.await.expect("server task");
2614 })
2615 .await;
2616 }
2617
2618 fn disconnected_peer() -> Peer<RoleClient> {
2619 let (peer, receiver) =
2620 Peer::<RoleClient>::new(Arc::new(AtomicU32RequestIdProvider::default()), None);
2621 drop(receiver);
2622 peer
2623 }
2624
2625 fn tools_result(ttl_ms: Option<u64>, cache_scope: Option<CacheScope>) -> ListToolsResult {
2626 let mut result = ListToolsResult::with_all_items(Vec::new());
2627 result.ttl_ms = ttl_ms;
2628 result.cache_scope = cache_scope;
2629 result
2630 }
2631
2632 #[tokio::test]
2633 async fn fresh_cached_page_is_served_without_transport_io() {
2634 let peer = disconnected_peer();
2635 let params = None::<PaginatedRequestParams>;
2636 let key = list_response_cache_key(TOOL_LIST_CACHE_PREFIX, ¶ms);
2637 let expected = tools_result(Some(5_000), Some(CacheScope::Public));
2638 peer.cache_response(
2639 key,
2640 ServerResult::ListToolsResult(expected.clone()),
2641 expected.ttl_ms,
2642 expected.cache_scope,
2643 )
2644 .await;
2645
2646 assert_eq!(peer.list_tools(params).await.unwrap(), expected);
2647 }
2648
2649 #[tokio::test]
2650 async fn expired_entry_falls_through_to_the_transport() {
2651 let peer = disconnected_peer();
2652 peer.set_response_cache_config(
2653 ClientCacheConfig::default().with_serve_stale_on_error(false),
2654 )
2655 .await;
2656 let params = None::<PaginatedRequestParams>;
2657 let key = list_response_cache_key(TOOL_LIST_CACHE_PREFIX, ¶ms);
2658 peer.cache_response(
2659 key,
2660 ServerResult::ListToolsResult(tools_result(Some(1), Some(CacheScope::Public))),
2661 Some(1),
2662 Some(CacheScope::Public),
2663 )
2664 .await;
2665 tokio::time::sleep(Duration::from_millis(5)).await;
2666
2667 assert!(matches!(
2668 peer.list_tools(params).await,
2669 Err(ServiceError::TransportClosed)
2670 ));
2671 }
2672
2673 #[tokio::test]
2674 async fn private_entries_are_isolated_between_authorization_partitions() {
2675 let peer = disconnected_peer();
2676 let key = list_response_cache_key(TOOL_LIST_CACHE_PREFIX, &None);
2677
2678 peer.set_response_cache_config(
2679 ClientCacheConfig::default().with_private_partition("auth-a"),
2680 )
2681 .await;
2682 peer.cache_response(
2683 key.clone(),
2684 ServerResult::ListToolsResult(tools_result(Some(5_000), Some(CacheScope::Private))),
2685 Some(5_000),
2686 Some(CacheScope::Private),
2687 )
2688 .await;
2689 assert!(peer.cached_response(&key).await.is_some());
2690
2691 peer.set_response_cache_config(
2694 ClientCacheConfig::default().with_private_partition("auth-b"),
2695 )
2696 .await;
2697 assert!(peer.cached_response(&key).await.is_none());
2698 }
2699
2700 #[tokio::test]
2701 async fn list_change_notification_discards_every_cached_page() {
2702 let peer = disconnected_peer();
2703 for cursor in [None, Some("page-a".into()), Some("page-b".into())] {
2704 let params =
2705 cursor.map(|cursor| PaginatedRequestParams::default().with_cursor(Some(cursor)));
2706 let key = list_response_cache_key(TOOL_LIST_CACHE_PREFIX, ¶ms);
2707 peer.cache_response(
2708 key,
2709 ServerResult::ListToolsResult(tools_result(Some(5_000), Some(CacheScope::Public))),
2710 Some(5_000),
2711 Some(CacheScope::Public),
2712 )
2713 .await;
2714 }
2715
2716 peer.invalidate_tool_cache().await;
2717
2718 for cursor in [None, Some("page-a".into()), Some("page-b".into())] {
2719 let params =
2720 cursor.map(|cursor| PaginatedRequestParams::default().with_cursor(Some(cursor)));
2721 let key = list_response_cache_key(TOOL_LIST_CACHE_PREFIX, ¶ms);
2722 assert!(peer.cached_response(&key).await.is_none());
2723 }
2724 }
2725
2726 #[tokio::test]
2727 async fn expired_entry_is_served_when_refetch_fails() {
2728 let peer = disconnected_peer();
2729 let params = None::<PaginatedRequestParams>;
2730 let key = list_response_cache_key(TOOL_LIST_CACHE_PREFIX, ¶ms);
2731 let expected = tools_result(Some(1), Some(CacheScope::Public));
2732 peer.cache_response(
2733 key,
2734 ServerResult::ListToolsResult(expected.clone()),
2735 Some(1),
2736 Some(CacheScope::Public),
2737 )
2738 .await;
2739 tokio::time::sleep(Duration::from_millis(5)).await;
2740
2741 assert_eq!(peer.list_tools(params).await.unwrap(), expected);
2742 }
2743
2744 #[tokio::test]
2745 async fn discover_serves_a_fresh_cached_response_without_transport_io() {
2746 let peer = disconnected_peer();
2747 let meta = RequestMetaObject::default();
2748 let key = discover_cache_key();
2749 let expected = DiscoverResult::new(vec![ProtocolVersion::default()], Default::default())
2750 .with_server_info(crate::model::Implementation::from_build_env())
2751 .with_ttl_ms(5_000)
2752 .with_cache_scope(CacheScope::Public);
2753 peer.cache_response(
2754 key,
2755 ServerResult::DiscoverResult(expected.clone()),
2756 Some(5_000),
2757 Some(CacheScope::Public),
2758 )
2759 .await;
2760
2761 assert_eq!(peer.discover(meta).await.unwrap(), expected);
2762 }
2763}
2764
2765#[cfg(test)]
2766mod sep2260_association_tests {
2767 use super::*;
2768 use crate::{
2769 model::{
2770 CreateMessageRequest, CreateMessageRequestParams, SamplingMessage, ServerCapabilities,
2771 },
2772 service::PeerRequestAssociation,
2773 };
2774
2775 fn sampling_request() -> ServerRequest {
2776 ServerRequest::CreateMessageRequest(CreateMessageRequest::new(
2777 CreateMessageRequestParams::new(vec![SamplingMessage::user_text("hi")], 16),
2778 ))
2779 }
2780
2781 fn server_info(version: ProtocolVersion) -> ServerPeerInfo {
2782 ServerPeerInfo::new(version, ServerCapabilities::default())
2783 }
2784
2785 fn enforce(
2786 info: &ServerPeerInfo,
2787 association: PeerRequestAssociation,
2788 ) -> Result<(), ErrorData> {
2789 RoleClient::enforce_peer_request_association(&sampling_request(), Some(info), association)
2790 }
2791
2792 #[test]
2793 fn strict_rejects_unassociated() {
2794 let info = server_info(ProtocolVersion::V_2026_07_28);
2795 let err = enforce(&info, PeerRequestAssociation::Unassociated).unwrap_err();
2796 assert_eq!(err.code, crate::model::ErrorCode::INVALID_PARAMS);
2797 }
2798
2799 #[test]
2800 fn strict_accepts_associated() {
2801 let info = server_info(ProtocolVersion::V_2026_07_28);
2802 assert!(enforce(&info, PeerRequestAssociation::Associated).is_ok());
2803 }
2804
2805 #[test]
2806 fn strict_unknown_falls_back_to_coarse_check() {
2807 let info = server_info(ProtocolVersion::V_2026_07_28);
2808 assert!(
2809 enforce(
2810 &info,
2811 PeerRequestAssociation::Unknown {
2812 has_pending_outbound_request: true
2813 }
2814 )
2815 .is_ok()
2816 );
2817 assert!(
2818 enforce(
2819 &info,
2820 PeerRequestAssociation::Unknown {
2821 has_pending_outbound_request: false
2822 }
2823 )
2824 .is_err()
2825 );
2826 }
2827
2828 #[test]
2829 fn legacy_protocol_accepts_even_unassociated() {
2830 let info = server_info(ProtocolVersion::V_2025_11_25);
2831 assert!(enforce(&info, PeerRequestAssociation::Unassociated).is_ok());
2832 }
2833}