11use std:: collections:: HashMap ;
2+ use std:: future;
23use std:: io;
4+ use std:: time:: Duration ;
35
46use mcp_nats:: {
57 ClientJsonRpcMessage , Config , ErrorData , FlushClient , McpPeerId , NatsTransport , PublishClient , RequestClient ,
@@ -11,6 +13,7 @@ use rmcp::transport::Transport;
1113use rmcp:: transport:: streamable_http_server:: session:: local:: LocalSessionManager ;
1214use rmcp:: transport:: streamable_http_server:: { StreamableHttpServerConfig , StreamableHttpService } ;
1315use tokio:: sync:: { mpsc, oneshot} ;
16+ use tokio:: time:: Instant ;
1417use tracing:: warn;
1518use uuid:: Uuid ;
1619
@@ -19,6 +22,11 @@ use crate::allowed_host::AllowedHost;
1922type ProxyResponse = oneshot:: Sender < Result < ServerResult , ErrorData > > ;
2023type ProxyAck = oneshot:: Sender < Result < ( ) , ErrorData > > ;
2124
25+ struct PendingEntry {
26+ response_tx : ProxyResponse ,
27+ deadline : Instant ,
28+ }
29+
2230pub fn streamable_http_config ( allowed_hosts : Vec < AllowedHost > ) -> StreamableHttpServerConfig {
2331 let config = StreamableHttpServerConfig :: default ( ) ;
2432 if allowed_hosts. is_empty ( ) {
8492 N : SubscribeClient + RequestClient + PublishClient + FlushClient ,
8593{
8694 command_tx : mpsc:: Sender < ProxyCommand > ,
95+ operation_timeout : Duration ,
8796 server_info : ServerInfo ,
8897 _nats : std:: marker:: PhantomData < N > ,
8998}
@@ -98,9 +107,11 @@ where
98107{
99108 pub fn new ( nats : N , config : Config , client_id : McpPeerId , server_id : McpPeerId ) -> Self {
100109 let ( command_tx, command_rx) = mpsc:: channel ( 64 ) ;
110+ let operation_timeout = config. operation_timeout ( ) ;
101111 tokio:: spawn ( run_proxy_worker ( nats, config, client_id, server_id, command_rx) ) ;
102112 Self {
103113 command_tx,
114+ operation_timeout,
104115 server_info : ServerInfo :: default ( ) ,
105116 _nats : std:: marker:: PhantomData ,
106117 }
@@ -126,8 +137,9 @@ where
126137 } )
127138 . await
128139 . map_err ( |_| ErrorData :: internal_error ( "MCP NATS proxy is unavailable" , None ) ) ?;
129- response_rx
140+ tokio :: time :: timeout ( self . operation_timeout , response_rx)
130141 . await
142+ . map_err ( |_| ErrorData :: internal_error ( "MCP NATS proxy timed out waiting for a response" , None ) ) ?
131143 . map_err ( |_| ErrorData :: internal_error ( "MCP NATS proxy dropped the request" , None ) ) ?
132144 }
133145
@@ -145,8 +157,9 @@ where
145157 } )
146158 . await
147159 . map_err ( |_| ErrorData :: internal_error ( "MCP NATS proxy is unavailable" , None ) ) ?;
148- response_rx
160+ tokio :: time :: timeout ( self . operation_timeout , response_rx)
149161 . await
162+ . map_err ( |_| ErrorData :: internal_error ( "MCP NATS proxy timed out waiting for the notification" , None ) ) ?
150163 . map_err ( |_| ErrorData :: internal_error ( "MCP NATS proxy dropped the notification" , None ) ) ?
151164 }
152165
@@ -189,16 +202,18 @@ async fn run_proxy_worker<N>(
189202 return ;
190203 }
191204 } ;
205+ let operation_timeout = config. operation_timeout ( ) ;
192206 let mut peer = None ;
193- let mut pending = HashMap :: new ( ) ;
207+ let mut pending: HashMap < RequestId , PendingEntry > = HashMap :: new ( ) ;
194208
195209 loop {
210+ let next_deadline = pending. values ( ) . map ( |entry| entry. deadline ) . min ( ) ;
196211 tokio:: select! {
197212 command = command_rx. recv( ) => {
198213 let Some ( command) = command else {
199214 break ;
200215 } ;
201- handle_proxy_command( command, & mut transport, & mut peer, & mut pending) . await ;
216+ handle_proxy_command( command, & mut transport, & mut peer, & mut pending, operation_timeout ) . await ;
202217 }
203218 message = transport. receive( ) => {
204219 let Some ( message) = message else {
@@ -207,6 +222,9 @@ async fn run_proxy_worker<N>(
207222 } ;
208223 handle_remote_message( message, & mut transport, peer. as_ref( ) , & mut pending) . await ;
209224 }
225+ ( ) = wait_for_deadline( next_deadline) => {
226+ evict_expired_pending( & mut pending) ;
227+ }
210228 }
211229 }
212230
@@ -219,7 +237,8 @@ async fn handle_proxy_command<N>(
219237 command : ProxyCommand ,
220238 transport : & mut NatsTransport < RoleClient , N > ,
221239 peer : & mut Option < Peer < RoleServer > > ,
222- pending : & mut HashMap < RequestId , ProxyResponse > ,
240+ pending : & mut HashMap < RequestId , PendingEntry > ,
241+ operation_timeout : Duration ,
223242) where
224243 N : SubscribeClient + RequestClient + PublishClient + FlushClient ,
225244 N :: RequestError : ' static ,
@@ -235,11 +254,19 @@ async fn handle_proxy_command<N>(
235254 } => {
236255 * peer = Some ( request_peer) ;
237256 let message = ClientJsonRpcMessage :: request ( * request, request_id. clone ( ) ) ;
238- pending. insert ( request_id. clone ( ) , response_tx) ;
257+ pending. insert (
258+ request_id. clone ( ) ,
259+ PendingEntry {
260+ response_tx,
261+ deadline : Instant :: now ( ) + operation_timeout,
262+ } ,
263+ ) ;
239264 if let Err ( error) = transport. send ( message) . await
240- && let Some ( response_tx ) = pending. remove ( & request_id)
265+ && let Some ( entry ) = pending. remove ( & request_id)
241266 {
242- let _ = response_tx. send ( Err ( ErrorData :: internal_error ( error. to_string ( ) , None ) ) ) ;
267+ let _ = entry
268+ . response_tx
269+ . send ( Err ( ErrorData :: internal_error ( error. to_string ( ) , None ) ) ) ;
243270 }
244271 }
245272 ProxyCommand :: Notification {
@@ -261,7 +288,7 @@ async fn handle_remote_message<N>(
261288 message : ServerJsonRpcMessage ,
262289 transport : & mut NatsTransport < RoleClient , N > ,
263290 peer : Option < & Peer < RoleServer > > ,
264- pending : & mut HashMap < RequestId , ProxyResponse > ,
291+ pending : & mut HashMap < RequestId , PendingEntry > ,
265292) where
266293 N : SubscribeClient + RequestClient + PublishClient + FlushClient ,
267294 N :: RequestError : ' static ,
@@ -270,13 +297,13 @@ async fn handle_remote_message<N>(
270297{
271298 match message {
272299 ServerJsonRpcMessage :: Response ( response) => {
273- if let Some ( response_tx ) = pending. remove ( & response. id ) {
274- let _ = response_tx. send ( Ok ( response. result ) ) ;
300+ if let Some ( entry ) = pending. remove ( & response. id ) {
301+ let _ = entry . response_tx . send ( Ok ( response. result ) ) ;
275302 }
276303 }
277304 ServerJsonRpcMessage :: Error ( error) => {
278- if let Some ( response_tx ) = pending. remove ( & error. id ) {
279- let _ = response_tx. send ( Err ( error. error ) ) ;
305+ if let Some ( entry ) = pending. remove ( & error. id ) {
306+ let _ = entry . response_tx . send ( Err ( error. error ) ) ;
280307 }
281308 }
282309 ServerJsonRpcMessage :: Notification ( notification) => {
@@ -323,9 +350,33 @@ fn service_error_to_error_data(error: ServiceError) -> ErrorData {
323350 ErrorData :: internal_error ( error. to_string ( ) , None )
324351}
325352
326- fn fail_pending ( pending : HashMap < RequestId , ProxyResponse > , error : ErrorData ) {
327- for response_tx in pending. into_values ( ) {
328- let _ = response_tx. send ( Err ( error. clone ( ) ) ) ;
353+ fn fail_pending ( pending : HashMap < RequestId , PendingEntry > , error : ErrorData ) {
354+ for entry in pending. into_values ( ) {
355+ let _ = entry. response_tx . send ( Err ( error. clone ( ) ) ) ;
356+ }
357+ }
358+
359+ async fn wait_for_deadline ( deadline : Option < Instant > ) {
360+ match deadline {
361+ Some ( deadline) => tokio:: time:: sleep_until ( deadline) . await ,
362+ None => future:: pending ( ) . await ,
363+ }
364+ }
365+
366+ fn evict_expired_pending ( pending : & mut HashMap < RequestId , PendingEntry > ) {
367+ let now = Instant :: now ( ) ;
368+ let expired = pending
369+ . iter ( )
370+ . filter ( |( _, entry) | entry. deadline <= now)
371+ . map ( |( request_id, _) | request_id. clone ( ) )
372+ . collect :: < Vec < _ > > ( ) ;
373+ for request_id in expired {
374+ if let Some ( entry) = pending. remove ( & request_id) {
375+ let _ = entry. response_tx . send ( Err ( ErrorData :: internal_error (
376+ "MCP NATS proxy timed out waiting for a response" ,
377+ None ,
378+ ) ) ) ;
379+ }
329380 }
330381}
331382
0 commit comments