1use std::convert::Infallible;
4use std::fmt;
5use std::future::Future;
6use std::pin::Pin;
7use std::sync::{Arc, Mutex};
8use std::task::{Context, Poll};
9
10use tokio::sync::{RwLock, mpsc};
11use tower::ServiceExt;
12use tower::util::BoxCloneService;
13use tower_service::Service;
14
15use crate::client::ClientTransport;
16use crate::protocol::{
17 CallToolParams, GetPromptParams, Implementation, InitializeResult, ListPromptsResult,
18 ListResourceTemplatesResult, ListResourcesResult, ListToolsResult, McpRequest, McpResponse,
19 PromptDefinition, ReadResourceParams, RequestId, ResourceDefinition,
20 ResourceTemplateDefinition, ServerCapabilities, ToolDefinition, ToolsCapability,
21};
22use crate::router::{Extensions, RouterRequest, RouterResponse};
23use crate::transport::CatchError;
24use tower_mcp_types::JsonRpcError;
25
26use super::backend::{Backend, BackendService, CachedCapabilities, ListChanged};
27
28#[derive(Clone)]
31pub(crate) struct BackendEntry {
32 pub namespace: String,
33 pub separator: String,
34 pub cache: Arc<RwLock<CachedCapabilities>>,
35 pub service: BoxCloneService<RouterRequest, RouterResponse, Infallible>,
38}
39
40impl BackendEntry {
41 pub fn from_backend(backend: &Backend) -> Self {
43 Self {
44 namespace: backend.namespace.clone(),
45 separator: backend.separator.clone(),
46 cache: Arc::clone(&backend.cache),
47 service: BoxCloneService::new(backend.service()),
48 }
49 }
50
51 pub fn from_backend_with_service(
53 backend: &Backend,
54 service: BoxCloneService<RouterRequest, RouterResponse, Infallible>,
55 ) -> Self {
56 Self {
57 namespace: backend.namespace.clone(),
58 separator: backend.separator.clone(),
59 cache: Arc::clone(&backend.cache),
60 service,
61 }
62 }
63
64 fn strip_prefix<'a>(&self, name: &'a str) -> Option<&'a str> {
66 let prefix = format!("{}{}", self.namespace, self.separator);
67 name.strip_prefix(&prefix)
68 }
69
70 fn strip_uri_prefix<'a>(&self, uri: &'a str) -> Option<&'a str> {
72 let prefix = format!("{}{}", self.namespace, self.separator);
73 uri.strip_prefix(&prefix)
74 }
75}
76
77#[derive(Clone)]
90pub struct McpProxy {
91 pub(super) shared: Arc<McpProxyShared>,
92 pub(super) entries: Arc<Mutex<Vec<BackendEntry>>>,
96}
97
98pub(super) struct McpProxyShared {
100 name: String,
101 version: String,
102 pub(super) backends: RwLock<Vec<Backend>>,
103 pub(super) notification_tx: Option<crate::context::NotificationSender>,
105 instructions: Option<String>,
107 separator: String,
109}
110
111#[derive(Debug, Clone)]
113pub struct BackendHealth {
114 pub namespace: String,
116 pub healthy: bool,
118}
119
120struct EntryInfo {
123 namespace: String,
124 separator: String,
125 cache: Arc<RwLock<CachedCapabilities>>,
126}
127
128#[derive(Debug)]
130pub enum AddBackendError {
131 DuplicateNamespace(String),
133 AmbiguousPrefix {
135 new_namespace: String,
137 existing_namespace: String,
139 },
140 Connect(crate::error::Error),
142 Initialize(crate::error::Error),
144}
145
146impl fmt::Display for AddBackendError {
147 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
148 match self {
149 Self::DuplicateNamespace(ns) => write!(f, "namespace \"{}\" already exists", ns),
150 Self::AmbiguousPrefix {
151 new_namespace,
152 existing_namespace,
153 } => write!(
154 f,
155 "namespace \"{}\" creates ambiguous prefix with \"{}\"",
156 new_namespace, existing_namespace
157 ),
158 Self::Connect(e) => write!(f, "failed to connect: {}", e),
159 Self::Initialize(e) => write!(f, "failed to initialize: {}", e),
160 }
161 }
162}
163
164impl std::error::Error for AddBackendError {}
165
166impl McpProxy {
167 pub fn builder(name: impl Into<String>, version: impl Into<String>) -> super::McpProxyBuilder {
169 super::McpProxyBuilder::new(name, version)
170 }
171
172 pub(crate) fn new(
174 name: String,
175 version: String,
176 backends: Vec<Backend>,
177 entries: Vec<BackendEntry>,
178 notification_tx: Option<crate::context::NotificationSender>,
179 instructions: Option<String>,
180 separator: String,
181 ) -> Self {
182 Self {
183 shared: Arc::new(McpProxyShared {
184 name,
185 version,
186 backends: RwLock::new(backends),
187 notification_tx,
188 instructions,
189 separator,
190 }),
191 entries: Arc::new(Mutex::new(entries)),
192 }
193 }
194
195 pub async fn add_backend(
216 &self,
217 namespace: impl Into<String>,
218 transport: impl ClientTransport,
219 ) -> Result<(), AddBackendError> {
220 let namespace = namespace.into();
221 let separator = self.shared.separator.clone();
222
223 {
225 let entries = self.entries.lock().expect("entries lock poisoned");
226 self.validate_namespace(&namespace, &separator, &entries)?;
227 }
228
229 let (invalidation_tx, invalidation_rx) = mpsc::channel(16);
231 let backend = Backend::connect(namespace.clone(), transport, separator, invalidation_tx)
232 .await
233 .map_err(AddBackendError::Connect)?;
234
235 backend
236 .initialize(&self.shared.name, &self.shared.version)
237 .await
238 .map_err(AddBackendError::Initialize)?;
239
240 let entry = BackendEntry::from_backend(&backend);
241
242 {
244 let mut entries = self.entries.lock().expect("entries lock poisoned");
245 entries.push(entry);
246 }
247
248 let backend_idx = {
250 let mut backends = self.shared.backends.write().await;
251 let idx = backends.len();
252 backends.push(backend);
253 idx
254 };
255
256 self.spawn_invalidation_watcher(backend_idx, invalidation_rx);
257
258 self.notify_all_changed().await;
260
261 Ok(())
262 }
263
264 pub async fn add_backend_with_layer<L>(
282 &self,
283 namespace: impl Into<String>,
284 transport: impl ClientTransport,
285 layer: L,
286 ) -> Result<(), AddBackendError>
287 where
288 L: tower::Layer<BackendService> + Send + 'static,
289 L::Service: Service<RouterRequest, Response = RouterResponse> + Clone + Send + 'static,
290 <L::Service as Service<RouterRequest>>::Error: fmt::Display + Send,
291 <L::Service as Service<RouterRequest>>::Future: Send,
292 {
293 let namespace = namespace.into();
294 let separator = self.shared.separator.clone();
295
296 {
297 let entries = self.entries.lock().expect("entries lock poisoned");
298 self.validate_namespace(&namespace, &separator, &entries)?;
299 }
300
301 let (invalidation_tx, invalidation_rx) = mpsc::channel(16);
302 let backend = Backend::connect(namespace.clone(), transport, separator, invalidation_tx)
303 .await
304 .map_err(AddBackendError::Connect)?;
305
306 backend
307 .initialize(&self.shared.name, &self.shared.version)
308 .await
309 .map_err(AddBackendError::Initialize)?;
310
311 let base = backend.service();
313 let layered = layer.layer(base);
314 let caught = CatchError::new(layered);
315 let service = BoxCloneService::new(caught);
316 let entry = BackendEntry::from_backend_with_service(&backend, service);
317
318 {
319 let mut entries = self.entries.lock().expect("entries lock poisoned");
320 entries.push(entry);
321 }
322
323 let backend_idx = {
324 let mut backends = self.shared.backends.write().await;
325 let idx = backends.len();
326 backends.push(backend);
327 idx
328 };
329
330 self.spawn_invalidation_watcher(backend_idx, invalidation_rx);
331
332 if let Some(tx) = &self.shared.notification_tx {
333 let _ = tx
334 .send(crate::context::ServerNotification::ToolsListChanged)
335 .await;
336 let _ = tx
337 .send(crate::context::ServerNotification::ResourcesListChanged)
338 .await;
339 let _ = tx
340 .send(crate::context::ServerNotification::PromptsListChanged)
341 .await;
342 }
343
344 Ok(())
345 }
346
347 fn validate_namespace(
349 &self,
350 namespace: &str,
351 separator: &str,
352 entries: &[BackendEntry],
353 ) -> Result<(), AddBackendError> {
354 let new_prefix = format!("{}{}", namespace, separator);
355
356 for entry in entries {
357 if entry.namespace == namespace {
358 return Err(AddBackendError::DuplicateNamespace(namespace.to_string()));
359 }
360 let existing_prefix = format!("{}{}", entry.namespace, entry.separator);
361 if new_prefix.starts_with(&existing_prefix) || existing_prefix.starts_with(&new_prefix)
362 {
363 return Err(AddBackendError::AmbiguousPrefix {
364 new_namespace: namespace.to_string(),
365 existing_namespace: entry.namespace.clone(),
366 });
367 }
368 }
369 Ok(())
370 }
371
372 pub(super) fn spawn_invalidation_watcher(
375 &self,
376 backend_idx: usize,
377 mut rx: mpsc::Receiver<ListChanged>,
378 ) {
379 let shared = Arc::clone(&self.shared);
380 tokio::spawn(async move {
381 while let Some(changed) = rx.recv().await {
382 let backends = shared.backends.read().await;
383 let Some(backend) = backends.get(backend_idx) else {
384 break;
385 };
386 tracing::debug!(
387 namespace = %backend.namespace,
388 kind = ?changed,
389 "Backend list changed, refreshing cache"
390 );
391 match changed {
392 ListChanged::Tools => {
393 backend.refresh_tools().await;
394 if let Some(tx) = &shared.notification_tx {
395 let _ = tx
396 .send(crate::context::ServerNotification::ToolsListChanged)
397 .await;
398 }
399 }
400 ListChanged::Resources => {
401 backend.refresh_resources().await;
402 if let Some(tx) = &shared.notification_tx {
403 let _ = tx
404 .send(crate::context::ServerNotification::ResourcesListChanged)
405 .await;
406 }
407 }
408 ListChanged::Prompts => {
409 backend.refresh_prompts().await;
410 if let Some(tx) = &shared.notification_tx {
411 let _ = tx
412 .send(crate::context::ServerNotification::PromptsListChanged)
413 .await;
414 }
415 }
416 }
417 }
418 });
419 }
420
421 pub async fn remove_backend(&self, namespace: &str) -> bool {
438 let found = {
440 let mut entries = self.entries.lock().expect("entries lock poisoned");
441 let before = entries.len();
442 entries.retain(|e| e.namespace != namespace);
443 entries.len() < before
444 };
445
446 if !found {
447 return false;
448 }
449
450 {
454 let mut backends = self.shared.backends.write().await;
455 backends.retain(|b| b.namespace != namespace);
456 }
457
458 self.notify_all_changed().await;
460
461 tracing::info!(namespace, "Backend removed");
462 true
463 }
464
465 pub async fn replace_backend(
477 &self,
478 namespace: impl Into<String>,
479 transport: impl ClientTransport,
480 ) -> Result<(), AddBackendError> {
481 let namespace = namespace.into();
482 self.remove_backend(&namespace).await;
483 self.add_backend(namespace, transport).await
484 }
485
486 pub fn backend_namespaces(&self) -> Vec<String> {
495 let entries = self.entries.lock().expect("entries lock poisoned");
496 entries.iter().map(|e| e.namespace.clone()).collect()
497 }
498
499 pub fn backend_count(&self) -> usize {
501 self.entries.lock().expect("entries lock poisoned").len()
502 }
503
504 pub async fn health_check(&self) -> Vec<BackendHealth> {
509 let backends = self.shared.backends.read().await;
510 let futures: Vec<_> = backends
511 .iter()
512 .map(|backend| {
513 let client = Arc::clone(&backend.client);
514 let namespace = backend.namespace.clone();
515 async move {
516 let healthy = client.ping().await.is_ok();
517 BackendHealth { namespace, healthy }
518 }
519 })
520 .collect();
521 drop(backends);
522
523 futures::future::join_all(futures).await
524 }
525
526 async fn notify_all_changed(&self) {
528 if let Some(tx) = &self.shared.notification_tx {
529 let _ = tx
530 .send(crate::context::ServerNotification::ToolsListChanged)
531 .await;
532 let _ = tx
533 .send(crate::context::ServerNotification::ResourcesListChanged)
534 .await;
535 let _ = tx
536 .send(crate::context::ServerNotification::PromptsListChanged)
537 .await;
538 }
539 }
540
541 fn entry_infos(entries: &[BackendEntry]) -> Vec<EntryInfo> {
543 entries
544 .iter()
545 .map(|e| EntryInfo {
546 namespace: e.namespace.clone(),
547 separator: e.separator.clone(),
548 cache: Arc::clone(&e.cache),
549 })
550 .collect()
551 }
552
553 fn route_by_prefix(entries: &[BackendEntry], name: &str) -> Option<(usize, String)> {
555 for (i, entry) in entries.iter().enumerate() {
556 if let Some(stripped) = entry.strip_prefix(name) {
557 return Some((i, stripped.to_string()));
558 }
559 }
560 None
561 }
562
563 fn route_by_uri_prefix(entries: &[BackendEntry], uri: &str) -> Option<(usize, String)> {
565 for (i, entry) in entries.iter().enumerate() {
566 if let Some(stripped) = entry.strip_uri_prefix(uri) {
567 return Some((i, stripped.to_string()));
568 }
569 }
570 None
571 }
572}
573
574impl EntryInfo {
575 fn prefixed_name(&self, name: &str) -> String {
576 format!("{}{}{}", self.namespace, self.separator, name)
577 }
578
579 fn prefixed_uri(&self, uri: &str) -> String {
580 format!("{}{}{}", self.namespace, self.separator, uri)
581 }
582}
583
584async fn handle_initialize(
590 name: String,
591 version: String,
592 instructions: Option<String>,
593) -> Result<McpResponse, JsonRpcError> {
594 Ok(McpResponse::Initialize(InitializeResult {
595 protocol_version: "2025-11-25".to_string(),
596 server_info: Implementation {
597 name,
598 version,
599 title: None,
600 description: None,
601 icons: None,
602 website_url: None,
603 meta: None,
604 },
605 capabilities: ServerCapabilities {
606 tools: Some(ToolsCapability { list_changed: true }),
607 resources: None,
608 prompts: None,
609 logging: None,
610 tasks: None,
611 completions: None,
612 experimental: None,
613 extensions: None,
614 },
615 instructions,
616 meta: None,
617 }))
618}
619
620async fn handle_list_tools(infos: Vec<EntryInfo>) -> Result<McpResponse, JsonRpcError> {
621 let mut tools = Vec::new();
622 for info in &infos {
623 let cache = info.cache.read().await;
624 for t in &cache.tools {
625 let mut def: ToolDefinition = t.clone();
626 def.name = info.prefixed_name(&def.name);
627 tools.push(def);
628 }
629 }
630 Ok(McpResponse::ListTools(ListToolsResult {
631 tools,
632 next_cursor: None,
633 ttl_ms: None,
634 cache_scope: None,
635 meta: None,
636 }))
637}
638
639async fn handle_call_tool(
640 service: BoxCloneService<RouterRequest, RouterResponse, Infallible>,
641 stripped_name: String,
642 params: CallToolParams,
643 id: RequestId,
644 extensions: Extensions,
645) -> Result<McpResponse, JsonRpcError> {
646 let inner_request = McpRequest::CallTool(CallToolParams {
647 name: stripped_name,
648 arguments: params.arguments,
649 input_responses: params.input_responses,
650 request_state: params.request_state,
651 meta: params.meta,
652 task: params.task,
653 });
654
655 let router_req = RouterRequest {
656 id,
657 inner: inner_request,
658 extensions,
659 };
660
661 let resp = service.oneshot(router_req).await.expect("infallible");
662 resp.inner
663}
664
665async fn handle_list_resources(infos: Vec<EntryInfo>) -> Result<McpResponse, JsonRpcError> {
666 let mut resources = Vec::new();
667 for info in &infos {
668 let cache = info.cache.read().await;
669 for r in &cache.resources {
670 let mut def: ResourceDefinition = r.clone();
671 def.uri = info.prefixed_uri(&def.uri);
672 def.name = info.prefixed_name(&def.name);
673 resources.push(def);
674 }
675 }
676 Ok(McpResponse::ListResources(ListResourcesResult {
677 resources,
678 next_cursor: None,
679 ttl_ms: None,
680 cache_scope: None,
681 meta: None,
682 }))
683}
684
685async fn handle_list_resource_templates(
686 infos: Vec<EntryInfo>,
687) -> Result<McpResponse, JsonRpcError> {
688 let mut resource_templates = Vec::new();
689 for info in &infos {
690 let cache = info.cache.read().await;
691 for rt in &cache.resource_templates {
692 let mut def: ResourceTemplateDefinition = rt.clone();
693 def.uri_template = info.prefixed_uri(&def.uri_template);
694 def.name = info.prefixed_name(&def.name);
695 resource_templates.push(def);
696 }
697 }
698 Ok(McpResponse::ListResourceTemplates(
699 ListResourceTemplatesResult {
700 resource_templates,
701 next_cursor: None,
702 ttl_ms: None,
703 cache_scope: None,
704 meta: None,
705 },
706 ))
707}
708
709async fn handle_read_resource(
710 service: BoxCloneService<RouterRequest, RouterResponse, Infallible>,
711 stripped_uri: String,
712 params: ReadResourceParams,
713 id: RequestId,
714 extensions: Extensions,
715) -> Result<McpResponse, JsonRpcError> {
716 let inner_request = McpRequest::ReadResource(ReadResourceParams {
717 uri: stripped_uri,
718 input_responses: params.input_responses,
719 request_state: params.request_state,
720 meta: params.meta,
721 });
722
723 let router_req = RouterRequest {
724 id,
725 inner: inner_request,
726 extensions,
727 };
728
729 let resp = service.oneshot(router_req).await.expect("infallible");
730 resp.inner
731}
732
733async fn handle_list_prompts(infos: Vec<EntryInfo>) -> Result<McpResponse, JsonRpcError> {
734 let mut prompts = Vec::new();
735 for info in &infos {
736 let cache = info.cache.read().await;
737 for p in &cache.prompts {
738 let mut def: PromptDefinition = p.clone();
739 def.name = info.prefixed_name(&def.name);
740 prompts.push(def);
741 }
742 }
743 Ok(McpResponse::ListPrompts(ListPromptsResult {
744 prompts,
745 next_cursor: None,
746 ttl_ms: None,
747 cache_scope: None,
748 meta: None,
749 }))
750}
751
752async fn handle_get_prompt(
753 service: BoxCloneService<RouterRequest, RouterResponse, Infallible>,
754 stripped_name: String,
755 params: GetPromptParams,
756 id: RequestId,
757 extensions: Extensions,
758) -> Result<McpResponse, JsonRpcError> {
759 let inner_request = McpRequest::GetPrompt(GetPromptParams {
760 name: stripped_name,
761 arguments: params.arguments,
762 input_responses: params.input_responses,
763 request_state: params.request_state,
764 meta: params.meta,
765 });
766
767 let router_req = RouterRequest {
768 id,
769 inner: inner_request,
770 extensions,
771 };
772
773 let resp = service.oneshot(router_req).await.expect("infallible");
774 resp.inner
775}
776
777impl Service<RouterRequest> for McpProxy {
782 type Response = RouterResponse;
783 type Error = Infallible;
784 type Future = Pin<Box<dyn Future<Output = Result<RouterResponse, Infallible>> + Send>>;
785
786 fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
787 Poll::Ready(Ok(()))
788 }
789
790 fn call(&mut self, req: RouterRequest) -> Self::Future {
791 let request_id = req.id.clone();
792 let extensions = req.extensions.clone();
793
794 let entries = self.entries.lock().expect("entries lock poisoned");
798
799 let result_future: Pin<Box<dyn Future<Output = Result<McpResponse, JsonRpcError>> + Send>> =
800 match req.inner {
801 McpRequest::Initialize(_params) => {
802 let name = self.shared.name.clone();
803 let version = self.shared.version.clone();
804 let instructions = self.shared.instructions.clone();
805 Box::pin(handle_initialize(name, version, instructions))
806 }
807 McpRequest::Ping => Box::pin(async { Ok(McpResponse::Pong(Default::default())) }),
808 McpRequest::ListTools(_params) => {
809 let infos = Self::entry_infos(&entries);
810 Box::pin(handle_list_tools(infos))
811 }
812 McpRequest::CallTool(params) => {
813 match Self::route_by_prefix(&entries, ¶ms.name) {
814 Some((idx, stripped)) => {
815 let service = entries[idx].service.clone();
816 Box::pin(handle_call_tool(
817 service,
818 stripped,
819 params,
820 request_id.clone(),
821 extensions.clone(),
822 ))
823 }
824 None => Box::pin(async move {
825 Err(JsonRpcError::invalid_params(format!(
826 "Unknown tool: {}",
827 params.name
828 )))
829 }),
830 }
831 }
832 McpRequest::ListResources(_params) => {
833 let infos = Self::entry_infos(&entries);
834 Box::pin(handle_list_resources(infos))
835 }
836 McpRequest::ListResourceTemplates(_params) => {
837 let infos = Self::entry_infos(&entries);
838 Box::pin(handle_list_resource_templates(infos))
839 }
840 McpRequest::ReadResource(params) => {
841 match Self::route_by_uri_prefix(&entries, ¶ms.uri) {
842 Some((idx, stripped)) => {
843 let service = entries[idx].service.clone();
844 Box::pin(handle_read_resource(
845 service,
846 stripped,
847 params,
848 request_id.clone(),
849 extensions.clone(),
850 ))
851 }
852 None => Box::pin(async move {
853 Err(JsonRpcError::invalid_params(format!(
854 "Unknown resource: {}",
855 params.uri
856 )))
857 }),
858 }
859 }
860 McpRequest::ListPrompts(_params) => {
861 let infos = Self::entry_infos(&entries);
862 Box::pin(handle_list_prompts(infos))
863 }
864 McpRequest::GetPrompt(params) => {
865 match Self::route_by_prefix(&entries, ¶ms.name) {
866 Some((idx, stripped)) => {
867 let service = entries[idx].service.clone();
868 Box::pin(handle_get_prompt(
869 service,
870 stripped,
871 params,
872 request_id.clone(),
873 extensions.clone(),
874 ))
875 }
876 None => Box::pin(async move {
877 Err(JsonRpcError::invalid_params(format!(
878 "Unknown prompt: {}",
879 params.name
880 )))
881 }),
882 }
883 }
884 _ => Box::pin(async {
885 Err(JsonRpcError::method_not_found(
886 "Method not supported by proxy",
887 ))
888 }),
889 };
890
891 drop(entries);
893
894 Box::pin(async move {
895 let result = result_future.await;
896 Ok(RouterResponse {
897 id: request_id,
898 inner: result,
899 })
900 })
901 }
902}