1use dashmap::DashMap;
23use futures::Stream;
24use rmcp::{
25 model::{ClientJsonRpcMessage, ServerJsonRpcMessage},
26 transport::{
27 WorkerTransport,
28 common::server_side_http::ServerSseMessage,
29 streamable_http_server::session::{
30 SessionId, SessionManager,
31 local::{LocalSessionManager, LocalSessionManagerError, LocalSessionWorker},
32 },
33 },
34};
35use std::sync::Arc;
36use std::time::Duration;
37use tracing::{debug, info, warn};
38
39use super::proxy_handler::ProxyHandler;
40
41#[derive(Debug, Clone)]
43struct SessionMetadata {
44 backend_version: u64,
45}
46
47pub struct ProxyAwareSessionManager {
55 inner: LocalSessionManager,
56 handler: Arc<ProxyHandler>,
57 session_versions: DashMap<String, SessionMetadata>,
58 max_sessions: Option<usize>,
59}
60
61impl ProxyAwareSessionManager {
62 const DEFAULT_SESSION_KEEP_ALIVE_SECS: u64 = 30 * 60;
65
66 pub fn new(handler: Arc<ProxyHandler>) -> Self {
67 Self::with_keep_alive(
68 handler,
69 Duration::from_secs(Self::DEFAULT_SESSION_KEEP_ALIVE_SECS),
70 )
71 }
72
73 pub fn with_keep_alive(handler: Arc<ProxyHandler>, keep_alive: Duration) -> Self {
74 info!(
75 "[Session Manager] Create ProxyAwareSessionManager - MCP ID: {}, keep_alive: {}s",
76 handler.mcp_id(),
77 keep_alive.as_secs()
78 );
79 let mut inner = LocalSessionManager::default();
80 inner.session_config.keep_alive = Some(keep_alive);
81 Self {
82 inner,
83 handler,
84 session_versions: DashMap::new(),
85 max_sessions: None,
86 }
87 }
88
89 pub fn with_max_sessions(mut self, max_sessions: Option<usize>) -> Self {
91 self.max_sessions = max_sessions;
92 self
93 }
94
95 fn check_backend_version(&self, session_id: &SessionId) -> bool {
96 if self.handler.isolation()
98 == crate::backend_connector::BackendIsolation::PerSession
99 {
100 return true;
101 }
102 if let Some(meta) = self.session_versions.get(session_id.as_ref()) {
103 let current_version = self.handler.get_backend_version();
104 if meta.backend_version != current_version {
105 warn!(
106 "[Session version mismatch] session_id={}, creation version={}, current version={}, MCP ID: {}",
107 session_id,
108 meta.backend_version,
109 current_version,
110 self.handler.mcp_id()
111 );
112 return false;
113 }
114 }
115 true
116 }
117
118 fn backend_ready_for_session_ops(&self) -> bool {
119 match self.handler.isolation() {
120 crate::backend_connector::BackendIsolation::PerSession => true,
121 crate::backend_connector::BackendIsolation::Shared => {
122 self.handler.is_backend_available()
123 }
124 }
125 }
126}
127
128impl SessionManager for ProxyAwareSessionManager {
130 type Error = LocalSessionManagerError;
131 type Transport = WorkerTransport<LocalSessionWorker>;
132
133 async fn create_session(&self) -> Result<(SessionId, Self::Transport), Self::Error> {
134 if let Some(max) = self.max_sessions {
135 let current = self.session_versions.len();
136 if current >= max {
137 warn!(
138 "[Session limit] rejecting create_session: {} >= max {}, MCP ID: {}",
139 current,
140 max,
141 self.handler.mcp_id()
142 );
143 return Err(LocalSessionManagerError::SessionNotFound(
144 format!("session-limit-{max}").into(),
145 ));
146 }
147 }
148
149 let (session_id, transport) = self.inner.create_session().await?;
150
151 let version = self.handler.get_backend_version();
152 self.session_versions.insert(
153 session_id.to_string(),
154 SessionMetadata {
155 backend_version: version,
156 },
157 );
158
159 info!(
160 "[SessionCreated] session_id={}, backend_version={}, MCP ID: {}",
161 session_id,
162 version,
163 self.handler.mcp_id()
164 );
165
166 Ok((session_id, transport))
167 }
168
169 async fn initialize_session(
170 &self,
171 id: &SessionId,
172 message: ClientJsonRpcMessage,
173 ) -> Result<ServerJsonRpcMessage, Self::Error> {
174 if !self.backend_ready_for_session_ops() {
175 warn!(
176 "[Session initialization failed] session_id={}, reason: backend is unavailable, MCP ID: {}",
177 id,
178 self.handler.mcp_id()
179 );
180 return Err(LocalSessionManagerError::SessionNotFound(id.clone()));
181 }
182
183 if !self.check_backend_version(id) {
184 warn!(
185 "[Session initialization failed] session_id={}, reason: version mismatch, MCP ID: {}",
186 id,
187 self.handler.mcp_id()
188 );
189 return Err(LocalSessionManagerError::SessionNotFound(id.clone()));
190 }
191
192 debug!(
193 "[Session initialization] session_id={}, MCP ID: {}",
194 id,
195 self.handler.mcp_id()
196 );
197 self.inner.initialize_session(id, message).await
198 }
199
200 async fn has_session(&self, id: &SessionId) -> Result<bool, Self::Error> {
201 if !self.check_backend_version(id) {
202 return Ok(false);
203 }
204 self.inner.has_session(id).await
205 }
206
207 async fn close_session(&self, id: &SessionId) -> Result<(), Self::Error> {
208 info!(
209 "[SessionClosed] session_id={}, MCP ID: {}",
210 id,
211 self.handler.mcp_id()
212 );
213 self.session_versions.remove(id.as_ref());
214 self.handler.upstream_peers().reap_closed_peers();
216 self.inner.close_session(id).await
217 }
218
219 async fn create_stream(
220 &self,
221 id: &SessionId,
222 message: ClientJsonRpcMessage,
223 ) -> Result<impl Stream<Item = ServerSseMessage> + Send + 'static, Self::Error> {
224 if !self.backend_ready_for_session_ops() {
225 warn!(
226 "[Stream creation failed] session_id={}, reason: backend is unavailable, MCP ID: {}",
227 id,
228 self.handler.mcp_id()
229 );
230 return Err(LocalSessionManagerError::SessionNotFound(id.clone()));
231 }
232
233 if !self.check_backend_version(id) {
234 warn!(
235 "[Stream creation failed] session_id={}, reason: version mismatch, MCP ID: {}",
236 id,
237 self.handler.mcp_id()
238 );
239 return Err(LocalSessionManagerError::SessionNotFound(id.clone()));
240 }
241
242 debug!(
243 "[Stream creation] session_id={}, MCP ID: {}",
244 id,
245 self.handler.mcp_id()
246 );
247 self.inner.create_stream(id, message).await
248 }
249
250 async fn accept_message(
251 &self,
252 id: &SessionId,
253 message: ClientJsonRpcMessage,
254 ) -> Result<(), Self::Error> {
255 if !self.backend_ready_for_session_ops() {
256 warn!(
257 "[Message rejected] session_id={}, reason: backend unavailable, MCP ID: {}",
258 id,
259 self.handler.mcp_id()
260 );
261 return Err(LocalSessionManagerError::SessionNotFound(id.clone()));
262 }
263
264 if !self.check_backend_version(id) {
265 warn!(
266 "[Message rejected] session_id={}, reason: version mismatch, MCP ID: {}",
267 id,
268 self.handler.mcp_id()
269 );
270 return Err(LocalSessionManagerError::SessionNotFound(id.clone()));
271 }
272
273 self.inner.accept_message(id, message).await
274 }
275
276 async fn create_standalone_stream(
277 &self,
278 id: &SessionId,
279 ) -> Result<impl Stream<Item = ServerSseMessage> + Send + 'static, Self::Error> {
280 self.inner.create_standalone_stream(id).await
281 }
282
283 async fn resume(
284 &self,
285 id: &SessionId,
286 last_event_id: String,
287 ) -> Result<impl Stream<Item = ServerSseMessage> + Send + 'static, Self::Error> {
288 if let Some(meta) = self.session_versions.get(id.as_ref()) {
290 let current_version = self.handler.get_backend_version();
291 if meta.backend_version != current_version {
292 warn!(
293 "[Session recovery failed] session_id={}, reason: backend version change ({} -> {}), MCP ID: {}",
294 id,
295 meta.backend_version,
296 current_version,
297 self.handler.mcp_id()
298 );
299
300 drop(meta); self.session_versions.remove(id.as_ref());
303 let _ = self.inner.close_session(id).await;
304
305 return Err(LocalSessionManagerError::SessionNotFound(id.clone()));
306 }
307 }
308
309 if !self.backend_ready_for_session_ops() {
310 warn!(
311 "[Session recovery failed] session_id={}, reason: backend is unavailable, MCP ID: {}",
312 id,
313 self.handler.mcp_id()
314 );
315 return Err(LocalSessionManagerError::SessionNotFound(id.clone()));
316 }
317
318 debug!(
319 "[SessionResumed] session_id={}, last_event_id={}, MCP ID: {}",
320 id,
321 last_event_id,
322 self.handler.mcp_id()
323 );
324 self.inner.resume(id, last_event_id).await
325 }
326}