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