mcp_streamable_proxy/proxy_handler/
lifecycle.rs1use super::*;
2
3impl ProxyHandler {
4 pub fn upstream_peers(&self) -> Arc<UpstreamPeerRegistry> {
6 self.upstream_peers.clone()
7 }
8
9 fn default_server_info(mcp_id: &str) -> ServerConfig {
11 warn!(
12 "[ProxyHandler] Create default ServerConfig - MCP ID: {}",
13 mcp_id
14 );
15 ServerConfig::new(rmcp::model::ServerCapabilities::default())
16 .with_server_info(Implementation::new("MCP Proxy", "0.1.0"))
17 }
18
19 pub(super) fn extract_server_info(
21 client: &BackendRunningService,
22 mcp_id: &str,
23 ) -> ServerConfig {
24 client
25 .peer_info()
26 .map(|peer_info| super::peer_info_to_server_info((*peer_info).clone()))
27 .unwrap_or_else(|| Self::default_server_info(mcp_id))
28 }
29
30 pub fn new_disconnected(
33 mcp_id: String,
34 tool_filter: ToolFilter,
35 default_info: ServerConfig,
36 ) -> Self {
37 info!(
38 "[ProxyHandler] Create a disconnected handler - MCP ID: {}",
39 mcp_id
40 );
41
42 if tool_filter.is_enabled() {
44 if let Some(ref allow_list) = tool_filter.allow_tools {
45 info!(
46 "[ProxyHandler] Tool whitelist enabled - MCP ID: {}, allowed tools: {:?}",
47 mcp_id, allow_list
48 );
49 }
50 if let Some(ref deny_list) = tool_filter.deny_tools {
51 info!(
52 "[ProxyHandler] Tool blacklist enabled - MCP ID: {}, excluded tools: {:?}",
53 mcp_id, deny_list
54 );
55 }
56 }
57
58 Self::new_disconnected_with_fallback(mcp_id, tool_filter, default_info, None)
59 }
60
61 pub fn new_disconnected_with_fallback(
62 mcp_id: String,
63 tool_filter: ToolFilter,
64 default_info: ServerConfig,
65 fallback_tools: Option<ListToolsResult>,
66 ) -> Self {
67 Self {
68 peer: Arc::new(ArcSwapOption::empty()),
69 discovery: DiscoveryCache::new(default_info, fallback_tools),
70 mcp_id,
71 tool_filter,
72 backend_version: Arc::new(AtomicU64::new(0)), upstream_peers: Arc::new(UpstreamPeerRegistry::new()),
74 isolation: BackendIsolation::Shared,
75 per_session: None,
76 }
77 }
78
79 pub fn new_per_session(
84 connector: Arc<dyn BackendConnector>,
85 mcp_id: String,
86 tool_filter: ToolFilter,
87 ) -> Self {
88 info!(
89 "[ProxyHandler] Create per-session disconnected handler - MCP ID: {}",
90 mcp_id
91 );
92 let default_info = Self::default_server_info(&mcp_id);
93 Self {
94 peer: Arc::new(ArcSwapOption::empty()),
95 discovery: DiscoveryCache::new(default_info, None),
96 mcp_id,
97 tool_filter,
98 backend_version: Arc::new(AtomicU64::new(0)),
99 upstream_peers: Arc::new(UpstreamPeerRegistry::new()),
100 isolation: BackendIsolation::PerSession,
101 per_session: Some(PerSessionState {
102 connector,
103 notify_slot: UpstreamPeerSlot::new(),
104 connect_lock: Arc::new(tokio::sync::Mutex::new(())),
105 }),
106 }
107 }
108
109 pub fn isolation(&self) -> BackendIsolation {
111 self.isolation
112 }
113
114 pub fn new(client: BackendRunningService) -> Self {
115 Self::with_mcp_id(client, "unknown".to_string())
116 }
117
118 pub fn with_mcp_id(client: BackendRunningService, mcp_id: String) -> Self {
119 Self::with_tool_filter(client, mcp_id, ToolFilter::default())
120 }
121
122 pub fn with_tool_filter(
124 client: BackendRunningService,
125 mcp_id: String,
126 tool_filter: ToolFilter,
127 ) -> Self {
128 use std::ops::Deref;
129
130 let cached_info = Self::extract_server_info(&client, &mcp_id);
132 let upstream_peers = client
133 .service()
134 .upstream_peers()
135 .cloned()
136 .unwrap_or_else(|| Arc::new(UpstreamPeerRegistry::new()));
137
138 let peer = client.deref().clone();
140
141 if tool_filter.is_enabled() {
143 if let Some(ref allow_list) = tool_filter.allow_tools {
144 info!(
145 "[ProxyHandler] Tool whitelist enabled - MCP ID: {}, allowed tools: {:?}",
146 mcp_id, allow_list
147 );
148 }
149 if let Some(ref deny_list) = tool_filter.deny_tools {
150 info!(
151 "[ProxyHandler] Tool blacklist enabled - MCP ID: {}, excluded tools: {:?}",
152 mcp_id, deny_list
153 );
154 }
155 }
156
157 let inner = PeerInner {
159 peer,
160 _running: Arc::new(client),
161 };
162
163 Self {
164 peer: Arc::new(ArcSwapOption::from(Some(Arc::new(inner)))),
165 discovery: DiscoveryCache::new(cached_info, None),
166 mcp_id,
167 tool_filter,
168 backend_version: Arc::new(AtomicU64::new(1)), upstream_peers,
170 isolation: BackendIsolation::Shared,
171 per_session: None,
172 }
173 }
174
175 pub fn swap_backend(&self, new_client: Option<BackendRunningService>) {
187 self.swap_backend_with_discovery(new_client, None);
188 }
189
190 pub fn swap_backend_with_discovery(
191 &self,
192 new_client: Option<BackendRunningService>,
193 complete_tools: Option<ListToolsResult>,
194 ) {
195 use std::ops::Deref;
196
197 if self.isolation == BackendIsolation::PerSession && self.peer.load().is_none() {
198 warn!(
199 "[ProxyHandler] swap_backend on disconnected per-session handler \
200 (likely management stub) - MCP ID: {}",
201 self.mcp_id
202 );
203 }
204
205 match new_client {
206 Some(client) => {
207 if let Some(reg) = client.service().upstream_peers()
208 && !Arc::ptr_eq(reg, &self.upstream_peers)
209 {
210 warn!(
211 "[ProxyHandler] swap_backend registry Arc differs from handler - MCP ID: {}",
212 self.mcp_id
213 );
214 }
215 let info = Self::extract_server_info(&client, &self.mcp_id);
217 self.update_discovery(info, complete_tools);
218
219 let peer = client.deref().clone();
220 let inner = PeerInner {
221 peer,
222 _running: Arc::new(client),
223 };
224 self.peer.store(Some(Arc::new(inner)));
225 info!(
226 "[ProxyHandler] Backend connection updated - MCP ID: {}",
227 self.mcp_id
228 );
229 }
230 None => {
231 self.peer.store(None);
232 info!(
233 "[ProxyHandler] Backend connection disconnected - MCP ID: {}",
234 self.mcp_id
235 );
236 }
237 }
238
239 let new_version = self.backend_version.fetch_add(1, Ordering::SeqCst) + 1;
241 info!(
242 "[ProxyHandler] Backend version update: {} - MCP ID: {}",
243 new_version, self.mcp_id
244 );
245 }
246
247 pub fn is_backend_available(&self) -> bool {
249 let inner_guard = self.peer.load();
250 match inner_guard.as_ref() {
251 Some(inner) => !inner.peer.is_transport_closed(),
252 None => false,
253 }
254 }
255
256 pub async fn is_mcp_server_ready(&self) -> bool {
258 !self.is_terminated_async().await
259 }
260
261 pub fn is_terminated(&self) -> bool {
263 !self.is_backend_available()
264 }
265
266 pub async fn is_terminated_async(&self) -> bool {
268 let peer = {
269 let inner_guard = self.peer.load();
270 let inner = match inner_guard.as_ref() {
271 Some(inner) => inner,
272 None => return true,
273 };
274 if inner.peer.is_transport_closed() {
275 return true;
276 }
277 inner.peer.clone()
278 };
279
280 match peer.list_tools(None).await {
281 Ok(_) => {
282 debug!("Backend connection status check: OK");
283 false
284 }
285 Err(e) => {
286 info!("Backend connection status check: Disconnected, reason: {e}");
287 true
288 }
289 }
290 }
291
292 pub fn mcp_id(&self) -> &str {
294 &self.mcp_id
295 }
296
297 pub fn get_server_info_json(&self) -> serde_json::Value {
302 match serde_json::to_value(self.discovery.info()) {
303 Ok(value) => value,
304 Err(error) => {
305 tracing::error!(%error, "failed to serialize cached server info");
306 serde_json::Value::Null
307 }
308 }
309 }
310
311 pub fn get_backend_version(&self) -> u64 {
319 self.backend_version.load(Ordering::SeqCst)
320 }
321}