1use crate::backend_client::{
5 BackendNotificationBridge, ProgressRouteGuard, UpstreamPeerRegistry, UpstreamPeerSlot,
6};
7use crate::backend_connector::{BackendConnector, BackendIsolation};
8use arc_swap::ArcSwapOption;
9pub use mcp_common::ToolFilter;
10use rmcp::{
11 ErrorData, RoleClient, RoleServer, ServerHandler, ServiceError,
12 model::{
13 CallToolRequest, CallToolRequestParams, CallToolResponse, CallToolResult, CancelTaskParams,
14 CancelTaskRequest, ClientRequest, ContentBlock, CreateTaskResult, GetPromptResponse,
15 GetTaskParams, GetTaskPayloadParams, GetTaskPayloadRequest, GetTaskRequest, GetTaskResult,
16 Implementation, InitializeRequestParams, InitializeResult, ListTasksRequest,
17 ListTasksResult, ListToolsResult, PaginatedRequestParams, ProtocolVersion,
18 ReadResourceResponse, RequestMetaObject, RequestParamsMeta, ServerInfo, ServerResult,
19 SetLevelRequestMethod, SubscribeRequestMethod, SubscribeRequestParams,
20 UnsubscribeRequestMethod, UnsubscribeRequestParams,
21 },
22 service::{NotificationContext, Peer, RequestContext, RunningService},
23};
24use std::future::Future;
25use std::sync::Arc;
26use std::sync::atomic::{AtomicU64, Ordering};
27use std::time::Instant;
28use tracing::{debug, error, info, warn};
29
30static REQUEST_COUNTER: AtomicU64 = AtomicU64::new(1);
32
33fn merge_context_meta_into_params<P: RequestParamsMeta>(
39 params: &mut P,
40 context_meta: &RequestMetaObject,
41) {
42 if context_meta.is_empty() {
43 return;
44 }
45 let dest = params.meta_or_default();
46 for (key, value) in context_meta.iter() {
47 dest.entry(key.clone()).or_insert_with(|| value.clone());
48 }
49}
50
51fn negotiate_protocol_version(
53 client_requested: &ProtocolVersion,
54 server_fallback: ProtocolVersion,
55) -> ProtocolVersion {
56 if ProtocolVersion::KNOWN_VERSIONS.contains(client_requested) {
57 client_requested.clone()
58 } else {
59 warn!(
60 client_requested = %client_requested,
61 server_fallback = %server_fallback,
62 "client requested unsupported protocol version; falling back to server default"
63 );
64 server_fallback
65 }
66}
67
68pub type BackendRunningService = RunningService<RoleClient, BackendNotificationBridge>;
70
71#[derive(Debug)]
74struct PeerInner {
75 peer: Peer<RoleClient>,
77 #[allow(dead_code)]
79 _running: Arc<BackendRunningService>,
80}
81
82#[derive(Clone)]
84struct PerSessionState {
85 connector: Arc<dyn BackendConnector>,
86 notify_slot: UpstreamPeerSlot,
87 connect_lock: Arc<tokio::sync::Mutex<()>>,
88}
89
90impl std::fmt::Debug for PerSessionState {
91 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
92 f.debug_struct("PerSessionState")
93 .field("notify_slot", &self.notify_slot)
94 .finish_non_exhaustive()
95 }
96}
97
98#[derive(Clone, Debug)]
105pub struct ProxyHandler {
106 peer: Arc<ArcSwapOption<PeerInner>>,
109 cached_info: Arc<arc_swap::ArcSwap<ServerInfo>>,
111 mcp_id: String,
113 tool_filter: ToolFilter,
115 backend_version: Arc<AtomicU64>,
117 upstream_peers: Arc<UpstreamPeerRegistry>,
119 isolation: BackendIsolation,
121 per_session: Option<PerSessionState>,
123}
124
125impl ServerHandler for ProxyHandler {
126 fn get_info(&self) -> ServerInfo {
127 (**self.cached_info.load()).clone()
128 }
129
130 async fn initialize(
132 &self,
133 request: InitializeRequestParams,
134 context: RequestContext<RoleServer>,
135 ) -> Result<InitializeResult, ErrorData> {
136 context.peer.set_peer_info(request.clone());
137
138 if let Some(session) = &self.per_session {
139 session.notify_slot.set(context.peer.clone());
141 self.ensure_connected().await?;
142 info!(
143 "[initialize] Per-session backend ready - MCP ID: {}",
144 self.mcp_id
145 );
146 }
147
148 let mut info = self.get_info();
149 info.protocol_version =
150 negotiate_protocol_version(&request.protocol_version, info.protocol_version);
151 Ok(info)
152 }
153
154 #[tracing::instrument(skip(self, request, context), fields(
155 mcp_id = %self.mcp_id,
156 request = ?request,
157 ))]
158 async fn list_tools(
159 &self,
160 request: Option<PaginatedRequestParams>,
161 context: RequestContext<RoleServer>,
162 ) -> Result<ListToolsResult, ErrorData> {
163 match self.capabilities().tools {
164 Some(_) => {
165 let result = self
166 .forward_backend(
167 &context,
168 |peer| async move { peer.list_tools(request).await },
169 )
170 .await?;
171
172 let filtered_tools: Vec<_> = if self.tool_filter.is_enabled() {
173 result
174 .tools
175 .into_iter()
176 .filter(|tool| self.tool_filter.is_allowed(&tool.name))
177 .collect()
178 } else {
179 result.tools
180 };
181
182 info!(
183 "[list_tools] Tool list results - MCP ID: {}, number of tools: {}{}",
184 self.mcp_id,
185 filtered_tools.len(),
186 if self.tool_filter.is_enabled() {
187 " (filtered)"
188 } else {
189 ""
190 }
191 );
192
193 debug!(
194 "Proxying list_tools response with {} tools",
195 filtered_tools.len()
196 );
197 Ok(ListToolsResult {
198 tools: filtered_tools,
199 next_cursor: result.next_cursor,
200 meta: result.meta,
201 result_type: result.result_type,
202 ttl_ms: result.ttl_ms,
203 cache_scope: result.cache_scope,
204 })
205 }
206 None => {
207 warn!("Server doesn't support tools capability");
208 Ok(ListToolsResult::default())
209 }
210 }
211 }
212
213 #[tracing::instrument(skip(self, request, context), fields(
214 mcp_id = %self.mcp_id,
215 tool_name = %request.name,
216 tool_arguments = ?request.arguments,
217 ))]
218 async fn call_tool(
219 &self,
220 mut request: CallToolRequestParams,
221 context: RequestContext<RoleServer>,
222 ) -> Result<CallToolResponse, ErrorData> {
223 let request_id = REQUEST_COUNTER.fetch_add(1, Ordering::Relaxed);
224 let start = Instant::now();
225 let tool_name = request.name.clone();
226
227 info!(
228 "[call_tool:{}] Start - Tool: {}, MCP ID: {}",
229 request_id, tool_name, self.mcp_id
230 );
231
232 if !self.tool_filter.is_allowed(&tool_name) {
233 info!(
234 "[call_tool:{}] Tool is filtered - MCP ID: {}, Tool: {}",
235 request_id, self.mcp_id, tool_name
236 );
237 return Ok(CallToolResult::error(vec![ContentBlock::text(format!(
238 "Tool '{tool_name}' is not allowed by filter configuration"
239 ))])
240 .into());
241 }
242
243 if self.capabilities().tools.is_none() {
244 error!(
245 "[call_tool:{}] The server does not support tools capability - MCP ID: {}",
246 request_id, self.mcp_id
247 );
248 return Ok(CallToolResult::error(vec![ContentBlock::text(
249 "Server doesn't support tools capability",
250 )])
251 .into());
252 }
253
254 merge_context_meta_into_params(&mut request, &context.meta);
255 let _progress_guard = self.maybe_progress_guard(&context);
256
257 info!(
258 "[call_tool:{}] Send request to backend... - Tool: {}, Elapsed time: {}ms",
259 request_id,
260 tool_name,
261 start.elapsed().as_millis()
262 );
263
264 let call_result = self
265 .forward_backend_with_heartbeat(&context, request_id, tool_name.as_ref(), |peer| {
266 async move { peer.call_tool_once(request).await }
267 })
268 .await;
269
270 let elapsed = start.elapsed();
271 let result = match call_result {
272 Ok(response) => {
273 match &response {
274 CallToolResponse::Complete(call_result) => {
275 let is_error = call_result.is_error.unwrap_or(false);
276 info!(
277 "[call_tool:{}] Response received - tool: {}, time taken: {}ms, is_error: {}, MCP ID: {}",
278 request_id,
279 tool_name,
280 elapsed.as_millis(),
281 is_error,
282 self.mcp_id
283 );
284 if is_error {
285 debug!(
286 "[call_tool:{}] Error response content: {:?}",
287 request_id, call_result.content
288 );
289 }
290 }
291 CallToolResponse::InputRequired(_) => {
292 info!(
293 "[call_tool:{}] InputRequired received - tool: {}, time taken: {}ms, MCP ID: {}",
294 request_id,
295 tool_name,
296 elapsed.as_millis(),
297 self.mcp_id
298 );
299 }
300 _ => {
301 info!(
302 "[call_tool:{}] Response received - tool: {}, time taken: {}ms, MCP ID: {}",
303 request_id,
304 tool_name,
305 elapsed.as_millis(),
306 self.mcp_id
307 );
308 }
309 }
310 Ok(response)
311 }
312 Err(err) => {
313 let message = err.message.clone();
315 if message == "Request cancelled" {
316 warn!(
317 "[call_tool:{}] Request canceled - Tool: {}, Time taken: {}ms, MCP ID: {}",
318 request_id,
319 tool_name,
320 elapsed.as_millis(),
321 self.mcp_id
322 );
323 Ok(CallToolResult::error(vec![ContentBlock::text(
324 "Request cancelled",
325 )])
326 .into())
327 } else if message.contains("Backend connection is not available")
328 || message.contains("Backend unavailable")
329 || message.contains("Backend connect")
330 {
331 error!(
332 "[call_tool:{}] Backend unavailable - Tool: {}, Error: {}, MCP ID: {}",
333 request_id, tool_name, message, self.mcp_id
334 );
335 Ok(CallToolResult::error(vec![ContentBlock::text(format!(
336 "Backend unavailable: {message}"
337 ))])
338 .into())
339 } else if message.contains("Backend connection closed") {
340 error!(
341 "[call_tool:{}] Backend transport is closed - MCP ID: {}",
342 request_id, self.mcp_id
343 );
344 Ok(CallToolResult::error(vec![ContentBlock::text(
345 "Backend connection closed, please retry",
346 )])
347 .into())
348 } else {
349 error!(
350 "[call_tool:{}] Backend returns error - Tool: {}, Time: {}ms, Error: {}, MCP ID: {}",
351 request_id,
352 tool_name,
353 elapsed.as_millis(),
354 message,
355 self.mcp_id
356 );
357 Ok(CallToolResult::error(vec![ContentBlock::text(format!(
358 "Error: {message}"
359 ))])
360 .into())
361 }
362 }
363 };
364
365 info!(
366 "[call_tool:{}] Completed - Tool: {}, total time taken: {}ms",
367 request_id,
368 tool_name,
369 start.elapsed().as_millis()
370 );
371 result
372 }
373
374 async fn list_resources(
375 &self,
376 request: Option<PaginatedRequestParams>,
377 context: RequestContext<RoleServer>,
378 ) -> Result<rmcp::model::ListResourcesResult, ErrorData> {
379 match self.capabilities().resources {
380 Some(_) => {
381 let result = self
382 .forward_backend(&context, |peer| async move {
383 peer.list_resources(request).await
384 })
385 .await?;
386 info!(
387 "[list_resources] Resource list results - MCP ID: {}, resource quantity: {}",
388 self.mcp_id,
389 result.resources.len()
390 );
391 debug!("Proxying list_resources response");
392 Ok(result)
393 }
394 None => {
395 warn!("Server doesn't support resources capability");
396 Ok(rmcp::model::ListResourcesResult::default())
397 }
398 }
399 }
400
401 async fn read_resource(
402 &self,
403 mut request: rmcp::model::ReadResourceRequestParams,
404 context: RequestContext<RoleServer>,
405 ) -> Result<ReadResourceResponse, ErrorData> {
406 match self.capabilities().resources {
407 Some(_) => {
408 merge_context_meta_into_params(&mut request, &context.meta);
409 let _progress_guard = self.maybe_progress_guard(&context);
410 let uri = request.uri.clone();
411 let result = self
412 .forward_backend(&context, |peer| async move {
413 peer.read_resource_once(request).await
414 })
415 .await?;
416 info!(
417 "[read_resource] Resource read result - MCP ID: {}, URI: {}",
418 self.mcp_id, uri
419 );
420 debug!("Proxying read_resource response for {}", uri);
421 Ok(result)
422 }
423 None => {
424 error!("Server doesn't support resources capability");
425 Ok(rmcp::model::ReadResourceResult::new(vec![]).into())
426 }
427 }
428 }
429
430 async fn list_resource_templates(
431 &self,
432 request: Option<PaginatedRequestParams>,
433 context: RequestContext<RoleServer>,
434 ) -> Result<rmcp::model::ListResourceTemplatesResult, ErrorData> {
435 match self.capabilities().resources {
436 Some(_) => {
437 let result = self
438 .forward_backend(&context, |peer| async move {
439 peer.list_resource_templates(request).await
440 })
441 .await?;
442 debug!("Proxying list_resource_templates response");
443 Ok(result)
444 }
445 None => {
446 warn!("Server doesn't support resources capability");
447 Ok(rmcp::model::ListResourceTemplatesResult::default())
448 }
449 }
450 }
451
452 async fn list_prompts(
453 &self,
454 request: Option<PaginatedRequestParams>,
455 context: RequestContext<RoleServer>,
456 ) -> Result<rmcp::model::ListPromptsResult, ErrorData> {
457 match self.capabilities().prompts {
458 Some(_) => {
459 let result =
460 self.forward_backend(&context, |peer| async move {
461 peer.list_prompts(request).await
462 })
463 .await?;
464 debug!("Proxying list_prompts response");
465 Ok(result)
466 }
467 None => {
468 warn!("Server doesn't support prompts capability");
469 Ok(rmcp::model::ListPromptsResult::default())
470 }
471 }
472 }
473
474 async fn get_prompt(
475 &self,
476 mut request: rmcp::model::GetPromptRequestParams,
477 context: RequestContext<RoleServer>,
478 ) -> Result<GetPromptResponse, ErrorData> {
479 match self.capabilities().prompts {
480 Some(_) => {
481 merge_context_meta_into_params(&mut request, &context.meta);
482 let _progress_guard = self.maybe_progress_guard(&context);
483 let result = self
484 .forward_backend(&context, |peer| async move {
485 peer.get_prompt_once(request).await
486 })
487 .await?;
488 debug!("Proxying get_prompt response");
489 Ok(result)
490 }
491 None => {
492 warn!("Server doesn't support prompts capability");
493 let messages = Vec::new();
494 Ok(rmcp::model::GetPromptResult::new(messages).into())
495 }
496 }
497 }
498
499 async fn complete(
500 &self,
501 request: rmcp::model::CompleteRequestParams,
502 context: RequestContext<RoleServer>,
503 ) -> Result<rmcp::model::CompleteResult, ErrorData> {
504 let result = self
505 .forward_backend(&context, |peer| async move { peer.complete(request).await })
506 .await?;
507 debug!("Proxying complete response");
508 Ok(result)
509 }
510
511 async fn subscribe(
512 &self,
513 request: SubscribeRequestParams,
514 context: RequestContext<RoleServer>,
515 ) -> Result<(), ErrorData> {
516 match self
517 .capabilities()
518 .resources
519 .as_ref()
520 .and_then(|r| r.subscribe)
521 {
522 Some(true) => {
523 self.forward_backend(
524 &context,
525 |peer| async move { peer.subscribe(request).await },
526 )
527 .await
528 }
529 _ => Err(ErrorData::method_not_found::<SubscribeRequestMethod>()),
530 }
531 }
532
533 async fn unsubscribe(
534 &self,
535 request: UnsubscribeRequestParams,
536 context: RequestContext<RoleServer>,
537 ) -> Result<(), ErrorData> {
538 match self
539 .capabilities()
540 .resources
541 .as_ref()
542 .and_then(|r| r.subscribe)
543 {
544 Some(true) => {
545 self.forward_backend(
546 &context,
547 |peer| async move { peer.unsubscribe(request).await },
548 )
549 .await
550 }
551 _ => Err(ErrorData::method_not_found::<UnsubscribeRequestMethod>()),
552 }
553 }
554
555 #[allow(deprecated)]
556 async fn set_level(
557 &self,
558 request: rmcp::model::SetLevelRequestParams,
559 context: RequestContext<RoleServer>,
560 ) -> Result<(), ErrorData> {
561 if self.capabilities().logging.is_none() {
562 return Err(ErrorData::method_not_found::<SetLevelRequestMethod>());
563 }
564 self.forward_backend(
565 &context,
566 |peer| async move { peer.set_level(request).await },
567 )
568 .await
569 }
570
571 async fn enqueue_task(
572 &self,
573 mut request: CallToolRequestParams,
574 context: RequestContext<RoleServer>,
575 ) -> Result<CreateTaskResult, ErrorData> {
576 let tasks = self.capabilities().tasks.clone();
577 if tasks.as_ref().is_none_or(|t| !t.supports_tools_call()) {
578 return Err(ErrorData::internal_error(
579 "Backend does not support task-based tools/call".to_string(),
580 None,
581 ));
582 }
583 merge_context_meta_into_params(&mut request, &context.meta);
584 let _progress_guard = self.maybe_progress_guard(&context);
585 let result = self
586 .forward_backend(&context, |peer| async move {
587 let result = peer
588 .send_request(ClientRequest::CallToolRequest(CallToolRequest::new(
589 request,
590 )))
591 .await?;
592 match result {
593 ServerResult::CreateTaskResult(r) => Ok(r),
594 _ => Err(ServiceError::UnexpectedResponse),
595 }
596 })
597 .await?;
598
599 if self.isolation == BackendIsolation::Shared {
602 self.upstream_peers
603 .register_task(result.task.task_id.clone(), context.peer.clone());
604 }
605 Ok(result)
606 }
607
608 async fn list_tasks(
609 &self,
610 request: Option<PaginatedRequestParams>,
611 context: RequestContext<RoleServer>,
612 ) -> Result<ListTasksResult, ErrorData> {
613 let tasks = self.capabilities().tasks.clone();
614 if tasks.as_ref().is_none_or(|t| !t.supports_list()) {
615 return Err(ErrorData::method_not_found::<rmcp::model::ListTasksMethod>());
616 }
617 self.forward_backend(&context, |peer| async move {
618 let result = peer
619 .send_request(ClientRequest::ListTasksRequest(ListTasksRequest {
620 method: Default::default(),
621 params: request,
622 extensions: Default::default(),
623 }))
624 .await?;
625 match result {
626 ServerResult::ListTasksResult(r) => Ok(r),
627 _ => Err(ServiceError::UnexpectedResponse),
628 }
629 })
630 .await
631 }
632
633 async fn get_task_info(
634 &self,
635 request: GetTaskParams,
636 context: RequestContext<RoleServer>,
637 ) -> Result<GetTaskResult, ErrorData> {
638 if self.capabilities().tasks.is_none() {
639 return Err(ErrorData::method_not_found::<rmcp::model::GetTaskMethod>());
640 }
641 self.forward_backend(&context, |peer| async move {
642 let result = peer
643 .send_request(ClientRequest::GetTaskRequest(GetTaskRequest::new(request)))
644 .await?;
645 match result {
646 ServerResult::GetTaskResult(r) => Ok(r),
647 _ => Err(ServiceError::UnexpectedResponse),
648 }
649 })
650 .await
651 }
652
653 async fn get_task_result(
654 &self,
655 request: GetTaskPayloadParams,
656 context: RequestContext<RoleServer>,
657 ) -> Result<rmcp::model::GetTaskPayloadResult, ErrorData> {
658 if self.capabilities().tasks.is_none() {
659 return Err(ErrorData::method_not_found::<
660 rmcp::model::GetTaskPayloadMethod,
661 >());
662 }
663 self.forward_backend(&context, |peer| async move {
664 let result = peer
665 .send_request(ClientRequest::GetTaskPayloadRequest(
666 GetTaskPayloadRequest::new(request),
667 ))
668 .await?;
669 match result {
670 ServerResult::GetTaskPayloadResult(r) => Ok(r),
671 _ => Err(ServiceError::UnexpectedResponse),
672 }
673 })
674 .await
675 }
676
677 async fn cancel_task(
678 &self,
679 request: CancelTaskParams,
680 context: RequestContext<RoleServer>,
681 ) -> Result<rmcp::model::CancelTaskResult, ErrorData> {
682 let tasks = self.capabilities().tasks.clone();
683 if tasks.as_ref().is_none_or(|t| !t.supports_cancel()) {
684 return Err(ErrorData::method_not_found::<rmcp::model::CancelTaskMethod>());
685 }
686 let task_id = request.task_id.clone();
687 let result = self
688 .forward_backend(&context, |peer| async move {
689 let result = peer
690 .send_request(ClientRequest::CancelTaskRequest(CancelTaskRequest::new(
691 request,
692 )))
693 .await?;
694 match result {
695 ServerResult::CancelTaskResult(r) => Ok(r),
696 _ => Err(ServiceError::UnexpectedResponse),
697 }
698 })
699 .await?;
700 self.upstream_peers.unregister_task(&task_id);
701 Ok(result)
702 }
703
704 async fn on_initialized(&self, context: NotificationContext<RoleServer>) {
705 if let Some(session) = &self.per_session {
706 session.notify_slot.set(context.peer.clone());
708 if let Err(err) = self.ensure_connected().await {
709 error!(
710 "[on_initialized] Per-session backend ensure failed - MCP ID: {}, err: {:?}",
711 self.mcp_id, err
712 );
713 }
714 return;
715 }
716
717 let id = self.upstream_peers.register(context.peer.clone());
718 info!(
719 "[on_initialized] Registered upstream peer {} - MCP ID: {}, peers: {}",
720 id,
721 self.mcp_id,
722 self.upstream_peers.len()
723 );
724 }
725
726 async fn on_progress(
727 &self,
728 notification: rmcp::model::ProgressNotificationParam,
729 _context: NotificationContext<RoleServer>,
730 ) {
731 let peer = {
732 let inner_guard = self.peer.load();
733 let inner = match inner_guard.as_ref() {
734 Some(inner) => inner,
735 None => {
736 error!(
737 "Backend connection is not available, cannot forward progress notification"
738 );
739 return;
740 }
741 };
742 if inner.peer.is_transport_closed() {
743 error!("Backend transport is closed, cannot forward progress notification");
744 return;
745 }
746 inner.peer.clone()
747 };
748
749 match peer.notify_progress(notification).await {
750 Ok(_) => {
751 debug!("Proxying progress notification");
752 }
753 Err(err) => {
754 error!("Error notifying progress: {:?}", err);
755 }
756 }
757 }
758
759 async fn on_cancelled(
760 &self,
761 notification: rmcp::model::CancelledNotificationParam,
762 _context: NotificationContext<RoleServer>,
763 ) {
764 let peer = {
765 let inner_guard = self.peer.load();
766 let inner = match inner_guard.as_ref() {
767 Some(inner) => inner,
768 None => {
769 error!(
770 "Backend connection is not available, cannot forward cancelled notification"
771 );
772 return;
773 }
774 };
775 if inner.peer.is_transport_closed() {
776 error!("Backend transport is closed, cannot forward cancelled notification");
777 return;
778 }
779 inner.peer.clone()
780 };
781
782 match peer.notify_cancelled(notification).await {
783 Ok(_) => {
784 debug!("Proxying cancelled notification");
785 }
786 Err(err) => {
787 error!("Error notifying cancelled: {:?}", err);
788 }
789 }
790 }
791}
792
793impl ProxyHandler {
794 #[inline]
796 fn capabilities(&self) -> rmcp::model::ServerCapabilities {
797 self.cached_info.load().capabilities.clone()
798 }
799
800 async fn ensure_connected(&self) -> Result<(), ErrorData> {
802 if self.is_backend_available() {
803 return Ok(());
804 }
805 let Some(session) = &self.per_session else {
806 return Err(ErrorData::internal_error(
807 "Backend connection is not available, reconnecting...".to_string(),
808 None,
809 ));
810 };
811
812 let _lock = session.connect_lock.lock().await;
813 if self.is_backend_available() {
814 return Ok(());
815 }
816
817 let timeout = session.connector.connect_timeout();
818 let slot = session.notify_slot.clone();
819 let connect = session.connector.connect_session(slot);
820 let result = tokio::time::timeout(timeout, connect).await;
821
822 match result {
823 Ok(Ok(running)) => {
824 self.install_backend(running, true);
825 Ok(())
826 }
827 Ok(Err(err)) => {
828 error!(error = %err, "Per-session backend connect failed");
829 Err(ErrorData::internal_error(
830 format!("Backend connect failed: {err}"),
831 None,
832 ))
833 }
834 Err(_) => {
835 error!(?timeout, "Per-session backend connect timed out");
836 Err(ErrorData::internal_error(
837 format!("Backend connect timed out after {timeout:?}"),
838 None,
839 ))
840 }
841 }
842 }
843
844 fn install_backend(&self, client: BackendRunningService, bump_version: bool) {
846 use std::ops::Deref;
847 let info = Self::extract_server_info(&client, &self.mcp_id);
848 self.cached_info.store(Arc::new(info));
849 let peer = client.deref().clone();
850 let inner = PeerInner {
851 peer,
852 _running: Arc::new(client),
853 };
854 self.peer.store(Some(Arc::new(inner)));
855 if bump_version {
856 let new_version = self.backend_version.fetch_add(1, Ordering::SeqCst) + 1;
857 info!(
858 "[ProxyHandler] Backend version update: {} - MCP ID: {}",
859 new_version, self.mcp_id
860 );
861 }
862 }
863
864 fn maybe_progress_guard(
865 &self,
866 context: &RequestContext<RoleServer>,
867 ) -> Option<ProgressRouteGuard> {
868 if self.isolation != BackendIsolation::Shared {
869 return None;
870 }
871 ProgressRouteGuard::try_from_meta(
872 self.upstream_peers.clone(),
873 &context.meta,
874 context.peer.clone(),
875 )
876 }
877
878 async fn forward_backend<T, Fut, F>(
880 &self,
881 context: &RequestContext<RoleServer>,
882 op: F,
883 ) -> Result<T, ErrorData>
884 where
885 F: FnOnce(Peer<RoleClient>) -> Fut,
886 Fut: Future<Output = Result<T, ServiceError>>,
887 {
888 self.ensure_connected().await?;
889
890 let inner_guard = self.peer.load();
891 let inner = inner_guard.as_ref().ok_or_else(|| {
892 error!("Backend connection is not available (reconnecting)");
893 ErrorData::internal_error(
894 "Backend connection is not available, reconnecting...".to_string(),
895 None,
896 )
897 })?;
898
899 if inner.peer.is_transport_closed() {
900 error!("Backend transport is closed");
901 return Err(ErrorData::internal_error(
902 "Backend connection closed, please retry".to_string(),
903 None,
904 ));
905 }
906
907 let peer = inner.peer.clone();
908 drop(inner_guard);
909
910 tokio::select! {
911 result = op(peer) => {
912 result.map_err(|err| {
913 error!("Backend request error: {:?}", err);
914 ErrorData::internal_error(format!("Backend error: {err}"), None)
915 })
916 }
917 _ = context.ct.cancelled() => {
918 info!("[forward_backend] Request canceled - MCP ID: {}", self.mcp_id);
919 Err(ErrorData::internal_error(
920 "Request cancelled".to_string(),
921 None,
922 ))
923 }
924 }
925 }
926
927 async fn forward_backend_with_heartbeat<T, Fut, F>(
929 &self,
930 context: &RequestContext<RoleServer>,
931 request_id: u64,
932 tool_name: &str,
933 op: F,
934 ) -> Result<T, ErrorData>
935 where
936 F: FnOnce(Peer<RoleClient>) -> Fut,
937 Fut: Future<Output = Result<T, ServiceError>>,
938 {
939 self.ensure_connected().await?;
940
941 let peer = {
942 let inner_guard = self.peer.load();
943 let inner = inner_guard.as_ref().ok_or_else(|| {
944 error!("Backend connection is not available (reconnecting)");
945 ErrorData::internal_error(
946 "Backend connection is not available, reconnecting...".to_string(),
947 None,
948 )
949 })?;
950
951 if inner.peer.is_transport_closed() {
952 error!("Backend transport is closed");
953 return Err(ErrorData::internal_error(
954 "Backend connection closed, please retry".to_string(),
955 None,
956 ));
957 }
958
959 let peer = inner.peer.clone();
960 drop(inner_guard);
961 peer
962 };
963
964 let call_future = op(peer.clone());
965 tokio::pin!(call_future);
966
967 const HEARTBEAT_INTERVAL: std::time::Duration = std::time::Duration::from_secs(30);
968 let mut heartbeat_interval = tokio::time::interval(HEARTBEAT_INTERVAL);
969 heartbeat_interval.tick().await;
970
971 loop {
972 tokio::select! {
973 biased;
974 result = &mut call_future => {
975 return result.map_err(|err| {
976 error!("Backend request error: {:?}", err);
977 ErrorData::internal_error(format!("Backend error: {err}"), None)
978 });
979 }
980 _ = context.ct.cancelled() => {
981 info!(
982 "[forward_backend_with_heartbeat:{}] Request canceled - Tool: {}, MCP ID: {}",
983 request_id, tool_name, self.mcp_id
984 );
985 return Err(ErrorData::internal_error(
986 "Request cancelled".to_string(),
987 None,
988 ));
989 }
990 _ = heartbeat_interval.tick() => {
991 info!(
992 "[forward_backend_with_heartbeat:{}] Waiting for backend... - Tool: {}, transport_closed: {}, MCP ID: {}",
993 request_id,
994 tool_name,
995 peer.is_transport_closed(),
996 self.mcp_id
997 );
998 }
999 }
1000 }
1001 }
1002
1003 pub fn upstream_peers(&self) -> Arc<UpstreamPeerRegistry> {
1005 self.upstream_peers.clone()
1006 }
1007
1008 fn default_server_info(mcp_id: &str) -> ServerInfo {
1010 warn!(
1011 "[ProxyHandler] Create default ServerInfo - MCP ID: {}",
1012 mcp_id
1013 );
1014 ServerInfo::new(rmcp::model::ServerCapabilities::default())
1015 .with_server_info(Implementation::new("MCP Proxy", "0.1.0"))
1016 }
1017
1018 fn extract_server_info(client: &BackendRunningService, mcp_id: &str) -> ServerInfo {
1020 client
1021 .peer_info()
1022 .map(|peer_info| {
1023 ServerInfo::new(peer_info.capabilities.clone())
1024 .with_protocol_version(peer_info.protocol_version.clone())
1025 .with_server_info(Implementation::new(
1026 peer_info.server_info.name.clone(),
1027 peer_info.server_info.version.clone(),
1028 ))
1029 .with_instructions(peer_info.instructions.clone().unwrap_or_default())
1030 })
1031 .unwrap_or_else(|| Self::default_server_info(mcp_id))
1032 }
1033
1034 pub fn new_disconnected(
1037 mcp_id: String,
1038 tool_filter: ToolFilter,
1039 default_info: ServerInfo,
1040 ) -> Self {
1041 info!(
1042 "[ProxyHandler] Create a disconnected handler - MCP ID: {}",
1043 mcp_id
1044 );
1045
1046 if tool_filter.is_enabled() {
1048 if let Some(ref allow_list) = tool_filter.allow_tools {
1049 info!(
1050 "[ProxyHandler] Tool whitelist enabled - MCP ID: {}, allowed tools: {:?}",
1051 mcp_id, allow_list
1052 );
1053 }
1054 if let Some(ref deny_list) = tool_filter.deny_tools {
1055 info!(
1056 "[ProxyHandler] Tool blacklist enabled - MCP ID: {}, excluded tools: {:?}",
1057 mcp_id, deny_list
1058 );
1059 }
1060 }
1061
1062 Self {
1063 peer: Arc::new(ArcSwapOption::empty()),
1064 cached_info: Arc::new(arc_swap::ArcSwap::from_pointee(default_info)),
1065 mcp_id,
1066 tool_filter,
1067 backend_version: Arc::new(AtomicU64::new(0)), upstream_peers: Arc::new(UpstreamPeerRegistry::new()),
1069 isolation: BackendIsolation::Shared,
1070 per_session: None,
1071 }
1072 }
1073
1074 pub fn new_per_session(
1079 connector: Arc<dyn BackendConnector>,
1080 mcp_id: String,
1081 tool_filter: ToolFilter,
1082 ) -> Self {
1083 info!(
1084 "[ProxyHandler] Create per-session disconnected handler - MCP ID: {}",
1085 mcp_id
1086 );
1087 let default_info = Self::default_server_info(&mcp_id);
1088 Self {
1089 peer: Arc::new(ArcSwapOption::empty()),
1090 cached_info: Arc::new(arc_swap::ArcSwap::from_pointee(default_info)),
1091 mcp_id,
1092 tool_filter,
1093 backend_version: Arc::new(AtomicU64::new(0)),
1094 upstream_peers: Arc::new(UpstreamPeerRegistry::new()),
1095 isolation: BackendIsolation::PerSession,
1096 per_session: Some(PerSessionState {
1097 connector,
1098 notify_slot: UpstreamPeerSlot::new(),
1099 connect_lock: Arc::new(tokio::sync::Mutex::new(())),
1100 }),
1101 }
1102 }
1103
1104 pub fn isolation(&self) -> BackendIsolation {
1106 self.isolation
1107 }
1108
1109 pub fn new(client: BackendRunningService) -> Self {
1110 Self::with_mcp_id(client, "unknown".to_string())
1111 }
1112
1113 pub fn with_mcp_id(client: BackendRunningService, mcp_id: String) -> Self {
1114 Self::with_tool_filter(client, mcp_id, ToolFilter::default())
1115 }
1116
1117 pub fn with_tool_filter(
1119 client: BackendRunningService,
1120 mcp_id: String,
1121 tool_filter: ToolFilter,
1122 ) -> Self {
1123 use std::ops::Deref;
1124
1125 let cached_info = Self::extract_server_info(&client, &mcp_id);
1127 let upstream_peers = client
1128 .service()
1129 .upstream_peers()
1130 .cloned()
1131 .unwrap_or_else(|| Arc::new(UpstreamPeerRegistry::new()));
1132
1133 let peer = client.deref().clone();
1135
1136 if tool_filter.is_enabled() {
1138 if let Some(ref allow_list) = tool_filter.allow_tools {
1139 info!(
1140 "[ProxyHandler] Tool whitelist enabled - MCP ID: {}, allowed tools: {:?}",
1141 mcp_id, allow_list
1142 );
1143 }
1144 if let Some(ref deny_list) = tool_filter.deny_tools {
1145 info!(
1146 "[ProxyHandler] Tool blacklist enabled - MCP ID: {}, excluded tools: {:?}",
1147 mcp_id, deny_list
1148 );
1149 }
1150 }
1151
1152 let inner = PeerInner {
1154 peer,
1155 _running: Arc::new(client),
1156 };
1157
1158 Self {
1159 peer: Arc::new(ArcSwapOption::from(Some(Arc::new(inner)))),
1160 cached_info: Arc::new(arc_swap::ArcSwap::from_pointee(cached_info)),
1161 mcp_id,
1162 tool_filter,
1163 backend_version: Arc::new(AtomicU64::new(1)), upstream_peers,
1165 isolation: BackendIsolation::Shared,
1166 per_session: None,
1167 }
1168 }
1169
1170 pub fn swap_backend(&self, new_client: Option<BackendRunningService>) {
1182 use std::ops::Deref;
1183
1184 if self.isolation == BackendIsolation::PerSession && self.peer.load().is_none() {
1185 warn!(
1186 "[ProxyHandler] swap_backend on disconnected per-session handler \
1187 (likely management stub) - MCP ID: {}",
1188 self.mcp_id
1189 );
1190 }
1191
1192 match new_client {
1193 Some(client) => {
1194 if let Some(reg) = client.service().upstream_peers()
1195 && !Arc::ptr_eq(reg, &self.upstream_peers)
1196 {
1197 warn!(
1198 "[ProxyHandler] swap_backend registry Arc differs from handler - MCP ID: {}",
1199 self.mcp_id
1200 );
1201 }
1202 let info = Self::extract_server_info(&client, &self.mcp_id);
1204 self.cached_info.store(Arc::new(info));
1205
1206 let peer = client.deref().clone();
1207 let inner = PeerInner {
1208 peer,
1209 _running: Arc::new(client),
1210 };
1211 self.peer.store(Some(Arc::new(inner)));
1212 info!(
1213 "[ProxyHandler] Backend connection updated - MCP ID: {}",
1214 self.mcp_id
1215 );
1216 }
1217 None => {
1218 self.peer.store(None);
1219 info!(
1220 "[ProxyHandler] Backend connection disconnected - MCP ID: {}",
1221 self.mcp_id
1222 );
1223 }
1224 }
1225
1226 let new_version = self.backend_version.fetch_add(1, Ordering::SeqCst) + 1;
1228 info!(
1229 "[ProxyHandler] Backend version update: {} - MCP ID: {}",
1230 new_version, self.mcp_id
1231 );
1232 }
1233
1234 pub fn is_backend_available(&self) -> bool {
1236 let inner_guard = self.peer.load();
1237 match inner_guard.as_ref() {
1238 Some(inner) => !inner.peer.is_transport_closed(),
1239 None => false,
1240 }
1241 }
1242
1243 pub async fn is_mcp_server_ready(&self) -> bool {
1245 !self.is_terminated_async().await
1246 }
1247
1248 pub fn is_terminated(&self) -> bool {
1250 !self.is_backend_available()
1251 }
1252
1253 pub async fn is_terminated_async(&self) -> bool {
1255 let peer = {
1256 let inner_guard = self.peer.load();
1257 let inner = match inner_guard.as_ref() {
1258 Some(inner) => inner,
1259 None => return true,
1260 };
1261 if inner.peer.is_transport_closed() {
1262 return true;
1263 }
1264 inner.peer.clone()
1265 };
1266
1267 match peer.list_tools(None).await {
1268 Ok(_) => {
1269 debug!("Backend connection status check: OK");
1270 false
1271 }
1272 Err(e) => {
1273 info!("Backend connection status check: Disconnected, reason: {e}");
1274 true
1275 }
1276 }
1277 }
1278
1279 pub fn mcp_id(&self) -> &str {
1281 &self.mcp_id
1282 }
1283
1284 pub fn get_server_info_json(&self) -> serde_json::Value {
1289 serde_json::to_value(&**self.cached_info.load()).unwrap_or_default()
1290 }
1291
1292 pub fn get_backend_version(&self) -> u64 {
1300 self.backend_version.load(Ordering::SeqCst)
1301 }
1302
1303 pub async fn call_peer_method(
1307 &self,
1308 method: &str,
1309 params: serde_json::Value,
1310 ) -> Result<serde_json::Value, String> {
1311 use rmcp::model::PaginatedRequestParams;
1312
1313 let peer = {
1314 let inner_guard = self.peer.load();
1315 let inner = inner_guard
1316 .as_ref()
1317 .ok_or_else(|| "Backend connection is not available (reconnecting)".to_string())?;
1318
1319 if inner.peer.is_transport_closed() {
1320 return Err("Backend transport is closed".to_string());
1321 }
1322 inner.peer.clone()
1323 };
1324
1325 match method {
1326 "tools/list" => {
1327 let request: Option<PaginatedRequestParams> = serde_json::from_value(params).ok();
1328 let result = peer
1329 .list_tools(request)
1330 .await
1331 .map_err(|e| format!("list_tools error: {:?}", e))?;
1332 serde_json::to_value(result).map_err(|e| format!("serialize error: {}", e))
1333 }
1334 "tools/call" => {
1335 let request: rmcp::model::CallToolRequestParams = serde_json::from_value(params)
1336 .map_err(|e| format!("Invalid params for tools/call: {}", e))?;
1337 if request.task.is_some() {
1338 let result = peer
1339 .send_request(ClientRequest::CallToolRequest(CallToolRequest::new(
1340 request,
1341 )))
1342 .await
1343 .map_err(|e| format!("enqueue_task error: {:?}", e))?;
1344 match result {
1345 ServerResult::CreateTaskResult(r) => {
1346 serde_json::to_value(r).map_err(|e| format!("serialize error: {}", e))
1347 }
1348 other => Err(format!("unexpected enqueue_task response: {other:?}")),
1349 }
1350 } else {
1351 let result = peer
1352 .call_tool_once(request)
1353 .await
1354 .map_err(|e| format!("call_tool error: {:?}", e))?;
1355 let value = match result {
1356 CallToolResponse::Complete(r) => serde_json::to_value(r),
1357 CallToolResponse::InputRequired(r) => serde_json::to_value(r),
1358 other => {
1359 return Err(format!("unsupported CallToolResponse variant: {other:?}"));
1360 }
1361 };
1362 value.map_err(|e| format!("serialize error: {}", e))
1363 }
1364 }
1365 "resources/subscribe" => {
1366 let request: SubscribeRequestParams = serde_json::from_value(params)
1367 .map_err(|e| format!("Invalid params for resources/subscribe: {}", e))?;
1368 peer.subscribe(request)
1369 .await
1370 .map_err(|e| format!("subscribe error: {:?}", e))?;
1371 Ok(serde_json::json!({}))
1372 }
1373 "resources/unsubscribe" => {
1374 let request: UnsubscribeRequestParams = serde_json::from_value(params)
1375 .map_err(|e| format!("Invalid params for resources/unsubscribe: {}", e))?;
1376 peer.unsubscribe(request)
1377 .await
1378 .map_err(|e| format!("unsubscribe error: {:?}", e))?;
1379 Ok(serde_json::json!({}))
1380 }
1381 "logging/setLevel" => {
1382 #[allow(deprecated)]
1383 {
1384 let request: rmcp::model::SetLevelRequestParams =
1385 serde_json::from_value(params)
1386 .map_err(|e| format!("Invalid params for logging/setLevel: {}", e))?;
1387 peer.set_level(request)
1388 .await
1389 .map_err(|e| format!("set_level error: {:?}", e))?;
1390 }
1391 Ok(serde_json::json!({}))
1392 }
1393 "tasks/list" => {
1394 let request: Option<PaginatedRequestParams> = serde_json::from_value(params).ok();
1395 let result = peer
1396 .send_request(ClientRequest::ListTasksRequest(ListTasksRequest {
1397 method: Default::default(),
1398 params: request,
1399 extensions: Default::default(),
1400 }))
1401 .await
1402 .map_err(|e| format!("list_tasks error: {:?}", e))?;
1403 match result {
1404 ServerResult::ListTasksResult(r) => {
1405 serde_json::to_value(r).map_err(|e| format!("serialize error: {}", e))
1406 }
1407 other => Err(format!("unexpected list_tasks response: {other:?}")),
1408 }
1409 }
1410 "tasks/get" => {
1411 let request: GetTaskParams = serde_json::from_value(params)
1412 .map_err(|e| format!("Invalid params for tasks/get: {}", e))?;
1413 let result = peer
1414 .send_request(ClientRequest::GetTaskRequest(GetTaskRequest::new(request)))
1415 .await
1416 .map_err(|e| format!("get_task error: {:?}", e))?;
1417 match result {
1418 ServerResult::GetTaskResult(r) => {
1419 serde_json::to_value(r).map_err(|e| format!("serialize error: {}", e))
1420 }
1421 other => Err(format!("unexpected get_task response: {other:?}")),
1422 }
1423 }
1424 "tasks/result" => {
1425 let request: GetTaskPayloadParams = serde_json::from_value(params)
1426 .map_err(|e| format!("Invalid params for tasks/result: {}", e))?;
1427 let result = peer
1428 .send_request(ClientRequest::GetTaskPayloadRequest(
1429 GetTaskPayloadRequest::new(request),
1430 ))
1431 .await
1432 .map_err(|e| format!("get_task_result error: {:?}", e))?;
1433 match result {
1434 ServerResult::GetTaskPayloadResult(r) => {
1435 serde_json::to_value(r).map_err(|e| format!("serialize error: {}", e))
1436 }
1437 other => Err(format!("unexpected get_task_result response: {other:?}")),
1438 }
1439 }
1440 "tasks/cancel" => {
1441 let request: CancelTaskParams = serde_json::from_value(params)
1442 .map_err(|e| format!("Invalid params for tasks/cancel: {}", e))?;
1443 let result = peer
1444 .send_request(ClientRequest::CancelTaskRequest(CancelTaskRequest::new(
1445 request,
1446 )))
1447 .await
1448 .map_err(|e| format!("cancel_task error: {:?}", e))?;
1449 match result {
1450 ServerResult::CancelTaskResult(r) => {
1451 serde_json::to_value(r).map_err(|e| format!("serialize error: {}", e))
1452 }
1453 other => Err(format!("unexpected cancel_task response: {other:?}")),
1454 }
1455 }
1456 "resources/list" => {
1457 let request: Option<PaginatedRequestParams> = serde_json::from_value(params).ok();
1458 let result = peer
1459 .list_resources(request)
1460 .await
1461 .map_err(|e| format!("list_resources error: {:?}", e))?;
1462 serde_json::to_value(result).map_err(|e| format!("serialize error: {}", e))
1463 }
1464 "resources/read" => {
1465 let request: rmcp::model::ReadResourceRequestParams =
1466 serde_json::from_value(params)
1467 .map_err(|e| format!("Invalid params for resources/read: {}", e))?;
1468 let result = peer
1469 .read_resource_once(request)
1470 .await
1471 .map_err(|e| format!("read_resource error: {:?}", e))?;
1472 let value = match result {
1473 ReadResourceResponse::Complete(r) => serde_json::to_value(r),
1474 ReadResourceResponse::InputRequired(r) => serde_json::to_value(r),
1475 other => {
1476 return Err(format!(
1477 "unsupported ReadResourceResponse variant: {other:?}"
1478 ));
1479 }
1480 };
1481 value.map_err(|e| format!("serialize error: {}", e))
1482 }
1483 "prompts/list" => {
1484 let request: Option<PaginatedRequestParams> = serde_json::from_value(params).ok();
1485 let result = peer
1486 .list_prompts(request)
1487 .await
1488 .map_err(|e| format!("list_prompts error: {:?}", e))?;
1489 serde_json::to_value(result).map_err(|e| format!("serialize error: {}", e))
1490 }
1491 "prompts/get" => {
1492 let request: rmcp::model::GetPromptRequestParams =
1493 serde_json::from_value(params)
1494 .map_err(|e| format!("Invalid params for prompts/get: {}", e))?;
1495 let result = peer
1496 .get_prompt_once(request)
1497 .await
1498 .map_err(|e| format!("get_prompt error: {:?}", e))?;
1499 let value = match result {
1500 GetPromptResponse::Complete(r) => serde_json::to_value(r),
1501 GetPromptResponse::InputRequired(r) => serde_json::to_value(r),
1502 other => {
1503 return Err(format!("unsupported GetPromptResponse variant: {other:?}"));
1504 }
1505 };
1506 value.map_err(|e| format!("serialize error: {}", e))
1507 }
1508 "resources/templates/list" => {
1509 let request: Option<PaginatedRequestParams> = serde_json::from_value(params).ok();
1510 let result = peer
1511 .list_resource_templates(request)
1512 .await
1513 .map_err(|e| format!("list_resource_templates error: {:?}", e))?;
1514 serde_json::to_value(result).map_err(|e| format!("serialize error: {}", e))
1515 }
1516 "completion/complete" => {
1517 let request: rmcp::model::CompleteRequestParams = serde_json::from_value(params)
1518 .map_err(|e| format!("Invalid params for completion/complete: {}", e))?;
1519 let result = peer
1520 .complete(request)
1521 .await
1522 .map_err(|e| format!("complete error: {:?}", e))?;
1523 serde_json::to_value(result).map_err(|e| format!("serialize error: {}", e))
1524 }
1525 _ => Err(format!("Unsupported method: {}", method)),
1526 }
1527 }
1528
1529 pub fn swap_backend_from_connection(
1538 &self,
1539 conn: Option<crate::client::StreamClientConnection>,
1540 ) {
1541 match conn {
1542 Some(c) => {
1543 let running = c.into_running_service();
1544 self.swap_backend(Some(running));
1545 }
1546 None => {
1547 self.swap_backend(None);
1548 }
1549 }
1550 }
1551}
1552
1553impl mcp_common::BackendBridge for ProxyHandler {
1554 fn mcp_id(&self) -> &str {
1555 self.mcp_id()
1556 }
1557
1558 fn get_server_info_json(&self) -> serde_json::Value {
1559 self.get_server_info_json()
1560 }
1561
1562 fn is_backend_available(&self) -> bool {
1563 self.is_backend_available()
1564 }
1565
1566 fn is_mcp_server_ready(
1567 &self,
1568 ) -> std::pin::Pin<Box<dyn std::future::Future<Output = bool> + Send + '_>> {
1569 Box::pin(self.is_mcp_server_ready())
1570 }
1571
1572 fn is_terminated_async(
1573 &self,
1574 ) -> std::pin::Pin<Box<dyn std::future::Future<Output = bool> + Send + '_>> {
1575 Box::pin(self.is_terminated_async())
1576 }
1577
1578 fn call_peer_method(
1579 &self,
1580 method: &str,
1581 params: serde_json::Value,
1582 ) -> std::pin::Pin<
1583 Box<dyn std::future::Future<Output = Result<serde_json::Value, String>> + Send + '_>,
1584 > {
1585 let method = method.to_string();
1586 Box::pin(async move { ProxyHandler::call_peer_method(self, &method, params).await })
1587 }
1588}
1589
1590#[cfg(test)]
1591mod meta_merge_tests {
1592 use super::merge_context_meta_into_params;
1593 use rmcp::model::{
1594 CallToolRequestParams, NumberOrString, ProgressToken, RequestMetaObject, RequestParamsMeta,
1595 };
1596
1597 #[test]
1598 fn merge_copies_progress_token_from_context() {
1599 let mut params = CallToolRequestParams::new("echo");
1600 assert!(params.meta.is_none());
1601
1602 let mut context_meta = RequestMetaObject::new();
1603 context_meta.set_progress_token(ProgressToken(NumberOrString::Number(42)));
1604
1605 merge_context_meta_into_params(&mut params, &context_meta);
1606
1607 assert_eq!(
1608 params.progress_token(),
1609 Some(ProgressToken(NumberOrString::Number(42)))
1610 );
1611 }
1612
1613 #[test]
1614 fn merge_keeps_existing_params_meta_keys() {
1615 let mut params = CallToolRequestParams::new("echo");
1616 params.set_progress_token(ProgressToken(NumberOrString::Number(1)));
1617
1618 let mut context_meta = RequestMetaObject::new();
1619 context_meta.set_progress_token(ProgressToken(NumberOrString::Number(99)));
1620 context_meta
1621 .insert("traceId".to_string(), serde_json::json!("abc"));
1622
1623 merge_context_meta_into_params(&mut params, &context_meta);
1624
1625 assert_eq!(
1626 params.progress_token(),
1627 Some(ProgressToken(NumberOrString::Number(1))),
1628 "params win on conflict"
1629 );
1630 assert_eq!(
1631 params.meta.as_ref().and_then(|m| m.get("traceId")),
1632 Some(&serde_json::json!("abc"))
1633 );
1634 }
1635}