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() == crate::backend_connector::BackendIsolation::PerSession {
98 return true;
99 }
100 if let Some(meta) = self.session_versions.get(session_id.as_ref()) {
101 let current_version = self.handler.get_backend_version();
102 if meta.backend_version != current_version {
103 warn!(
104 "[Session version mismatch] session_id={}, creation version={}, current version={}, MCP ID: {}",
105 session_id,
106 meta.backend_version,
107 current_version,
108 self.handler.mcp_id()
109 );
110 return false;
111 }
112 }
113 true
114 }
115
116 fn backend_ready_for_session_ops(&self) -> bool {
117 match self.handler.isolation() {
118 crate::backend_connector::BackendIsolation::PerSession => true,
119 crate::backend_connector::BackendIsolation::Shared => {
120 self.handler.is_backend_available()
121 }
122 }
123 }
124}
125
126impl SessionManager for ProxyAwareSessionManager {
128 type Error = LocalSessionManagerError;
129 type Transport = WorkerTransport<LocalSessionWorker>;
130
131 async fn create_session(&self) -> Result<(SessionId, Self::Transport), Self::Error> {
132 if let Some(max) = self.max_sessions {
133 let current = self.session_versions.len();
134 if current >= max {
135 warn!(
136 "[Session limit] rejecting create_session: {} >= max {}, MCP ID: {}",
137 current,
138 max,
139 self.handler.mcp_id()
140 );
141 return Err(LocalSessionManagerError::SessionNotFound(
142 format!("session-limit-{max}").into(),
143 ));
144 }
145 }
146
147 let (session_id, transport) = self.inner.create_session().await?;
148
149 let version = self.handler.get_backend_version();
150 self.session_versions.insert(
151 session_id.to_string(),
152 SessionMetadata {
153 backend_version: version,
154 },
155 );
156
157 info!(
158 "[SessionCreated] session_id={}, backend_version={}, MCP ID: {}",
159 session_id,
160 version,
161 self.handler.mcp_id()
162 );
163
164 Ok((session_id, transport))
165 }
166
167 async fn initialize_session(
168 &self,
169 id: &SessionId,
170 message: ClientJsonRpcMessage,
171 ) -> Result<ServerJsonRpcMessage, Self::Error> {
172 if !self.backend_ready_for_session_ops() {
173 warn!(
174 "[Session initialization failed] session_id={}, reason: backend is unavailable, MCP ID: {}",
175 id,
176 self.handler.mcp_id()
177 );
178 return Err(LocalSessionManagerError::SessionNotFound(id.clone()));
179 }
180
181 if !self.check_backend_version(id) {
182 warn!(
183 "[Session initialization failed] session_id={}, reason: version mismatch, MCP ID: {}",
184 id,
185 self.handler.mcp_id()
186 );
187 return Err(LocalSessionManagerError::SessionNotFound(id.clone()));
188 }
189
190 debug!(
191 "[Session initialization] session_id={}, MCP ID: {}",
192 id,
193 self.handler.mcp_id()
194 );
195 self.inner.initialize_session(id, message).await
196 }
197
198 async fn has_session(&self, id: &SessionId) -> Result<bool, Self::Error> {
199 if !self.check_backend_version(id) {
200 return Ok(false);
201 }
202 self.inner.has_session(id).await
203 }
204
205 async fn close_session(&self, id: &SessionId) -> Result<(), Self::Error> {
206 info!(
207 "[SessionClosed] session_id={}, MCP ID: {}",
208 id,
209 self.handler.mcp_id()
210 );
211 self.session_versions.remove(id.as_ref());
212 self.handler.upstream_peers().reap_closed_peers();
214 self.inner.close_session(id).await
215 }
216
217 async fn create_stream(
218 &self,
219 id: &SessionId,
220 message: ClientJsonRpcMessage,
221 ) -> Result<impl Stream<Item = ServerSseMessage> + Send + 'static, Self::Error> {
222 if !self.backend_ready_for_session_ops() {
223 warn!(
224 "[Stream creation failed] session_id={}, reason: backend is unavailable, MCP ID: {}",
225 id,
226 self.handler.mcp_id()
227 );
228 return Err(LocalSessionManagerError::SessionNotFound(id.clone()));
229 }
230
231 if !self.check_backend_version(id) {
232 warn!(
233 "[Stream creation failed] session_id={}, reason: version mismatch, MCP ID: {}",
234 id,
235 self.handler.mcp_id()
236 );
237 return Err(LocalSessionManagerError::SessionNotFound(id.clone()));
238 }
239
240 debug!(
241 "[Stream creation] session_id={}, MCP ID: {}",
242 id,
243 self.handler.mcp_id()
244 );
245 self.inner.create_stream(id, message).await
246 }
247
248 async fn accept_message(
249 &self,
250 id: &SessionId,
251 message: ClientJsonRpcMessage,
252 ) -> Result<(), Self::Error> {
253 if !self.backend_ready_for_session_ops() {
254 warn!(
255 "[Message rejected] session_id={}, reason: backend unavailable, MCP ID: {}",
256 id,
257 self.handler.mcp_id()
258 );
259 return Err(LocalSessionManagerError::SessionNotFound(id.clone()));
260 }
261
262 if !self.check_backend_version(id) {
263 warn!(
264 "[Message rejected] session_id={}, reason: version mismatch, MCP ID: {}",
265 id,
266 self.handler.mcp_id()
267 );
268 return Err(LocalSessionManagerError::SessionNotFound(id.clone()));
269 }
270
271 self.inner.accept_message(id, message).await
272 }
273
274 async fn create_standalone_stream(
275 &self,
276 id: &SessionId,
277 ) -> Result<impl Stream<Item = ServerSseMessage> + Send + 'static, Self::Error> {
278 self.inner.create_standalone_stream(id).await
279 }
280
281 async fn resume(
282 &self,
283 id: &SessionId,
284 last_event_id: String,
285 ) -> Result<impl Stream<Item = ServerSseMessage> + Send + 'static, Self::Error> {
286 if let Some(meta) = self.session_versions.get(id.as_ref()) {
288 let current_version = self.handler.get_backend_version();
289 if meta.backend_version != current_version {
290 warn!(
291 "[Session recovery failed] session_id={}, reason: backend version change ({} -> {}), MCP ID: {}",
292 id,
293 meta.backend_version,
294 current_version,
295 self.handler.mcp_id()
296 );
297
298 drop(meta); self.session_versions.remove(id.as_ref());
301 let _ = self.inner.close_session(id).await;
302
303 return Err(LocalSessionManagerError::SessionNotFound(id.clone()));
304 }
305 }
306
307 if !self.backend_ready_for_session_ops() {
308 warn!(
309 "[Session recovery failed] session_id={}, reason: backend is unavailable, MCP ID: {}",
310 id,
311 self.handler.mcp_id()
312 );
313 return Err(LocalSessionManagerError::SessionNotFound(id.clone()));
314 }
315
316 debug!(
317 "[SessionResumed] session_id={}, last_event_id={}, MCP ID: {}",
318 id,
319 last_event_id,
320 self.handler.mcp_id()
321 );
322 self.inner.resume(id, last_event_id).await
323 }
324}