mcp_streamable_proxy/proxy_handler/
mod.rs1use 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 CallToolRequestParams, CallToolResponse, CallToolResult, CancelTaskParams, ClientRequest,
14 ContentBlock, GetPromptResponse, GetTaskParams, GetTaskRequest, GetTaskResult,
15 Implementation, InitializeRequestParams, InitializeResult, ListToolsResult,
16 PaginatedRequestParams, ProtocolVersion, ReadResourceResponse, RequestMetaObject,
17 RequestParamsMeta, ServerInfo, ServerResult, SetLevelRequestMethod, SubscribeRequestMethod,
18 SubscribeRequestParams, UnsubscribeRequestMethod, UnsubscribeRequestParams,
19 },
20 service::{NotificationContext, Peer, RequestContext, RunningService},
21};
22use std::future::Future;
23use std::sync::Arc;
24use std::sync::atomic::{AtomicU64, Ordering};
25use std::time::Instant;
26use tracing::{debug, error, info, warn};
27
28static REQUEST_COUNTER: AtomicU64 = AtomicU64::new(1);
30
31fn merge_context_meta_into_params<P: RequestParamsMeta>(
37 params: &mut P,
38 context_meta: &RequestMetaObject,
39) {
40 if context_meta.is_empty() {
41 return;
42 }
43 let dest = params.meta_or_default();
44 for (key, value) in context_meta.iter() {
45 dest.entry(key.clone()).or_insert_with(|| value.clone());
46 }
47}
48
49fn negotiate_protocol_version(
51 client_requested: &ProtocolVersion,
52 server_fallback: ProtocolVersion,
53) -> ProtocolVersion {
54 if ProtocolVersion::KNOWN_VERSIONS.contains(client_requested) {
55 client_requested.clone()
56 } else {
57 warn!(
58 client_requested = %client_requested,
59 server_fallback = %server_fallback,
60 "client requested unsupported protocol version; falling back to server default"
61 );
62 server_fallback
63 }
64}
65
66pub(crate) fn peer_info_to_server_info(peer: rmcp::model::ServerPeerInfo) -> ServerInfo {
75 let mut info =
76 InitializeResult::new(peer.capabilities).with_protocol_version(peer.protocol_version);
77 if let Some(server_info) = peer.server_info {
78 info = info.with_server_info(server_info);
79 }
80 if let Some(instructions) = peer.instructions {
81 info = info.with_instructions(instructions);
82 }
83 info
84}
85
86pub type BackendRunningService = RunningService<RoleClient, BackendNotificationBridge>;
88
89#[derive(Debug)]
92struct PeerInner {
93 peer: Peer<RoleClient>,
95 #[allow(dead_code)]
97 _running: Arc<BackendRunningService>,
98}
99
100#[derive(Clone)]
102struct PerSessionState {
103 connector: Arc<dyn BackendConnector>,
104 notify_slot: UpstreamPeerSlot,
105 connect_lock: Arc<tokio::sync::Mutex<()>>,
106}
107
108impl std::fmt::Debug for PerSessionState {
109 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
110 f.debug_struct("PerSessionState")
111 .field("notify_slot", &self.notify_slot)
112 .finish_non_exhaustive()
113 }
114}
115
116#[derive(Clone, Debug)]
123pub struct ProxyHandler {
124 peer: Arc<ArcSwapOption<PeerInner>>,
127 discovery: DiscoveryCache,
129 mcp_id: String,
131 tool_filter: ToolFilter,
133 backend_version: Arc<AtomicU64>,
135 upstream_peers: Arc<UpstreamPeerRegistry>,
137 isolation: BackendIsolation,
139 per_session: Option<PerSessionState>,
141}
142
143mod backend_bridge;
144mod connection_swap;
145mod discovery;
146mod forwarding;
147mod lifecycle;
148mod raw_dispatch;
149mod server_handler;
150
151use discovery::DiscoveryCache;