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