@@ -10,6 +10,7 @@ use std::collections::HashMap;
1010use std:: collections:: HashSet ;
1111use std:: env;
1212use std:: ffi:: OsString ;
13+ use std:: path:: PathBuf ;
1314use std:: sync:: Arc ;
1415use std:: time:: Duration ;
1516
@@ -26,6 +27,7 @@ use codex_protocol::protocol::McpStartupCompleteEvent;
2627use codex_protocol:: protocol:: McpStartupFailure ;
2728use codex_protocol:: protocol:: McpStartupStatus ;
2829use codex_protocol:: protocol:: McpStartupUpdateEvent ;
30+ use codex_protocol:: protocol:: SandboxPolicy ;
2931use codex_rmcp_client:: OAuthCredentialsStoreMode ;
3032use codex_rmcp_client:: RmcpClient ;
3133use futures:: future:: BoxFuture ;
@@ -43,6 +45,8 @@ use mcp_types::Resource;
4345use mcp_types:: ResourceTemplate ;
4446use mcp_types:: Tool ;
4547
48+ use serde:: Deserialize ;
49+ use serde:: Serialize ;
4650use serde_json:: json;
4751use sha1:: Digest ;
4852use sha1:: Sha1 ;
@@ -116,6 +120,7 @@ struct ManagedClient {
116120 tools : Vec < ToolInfo > ,
117121 tool_filter : ToolFilter ,
118122 tool_timeout : Option < Duration > ,
123+ server_supports_sandbox_state_capability : bool ,
119124}
120125
121126#[ derive( Clone ) ]
@@ -150,6 +155,35 @@ impl AsyncManagedClient {
150155 async fn client ( & self ) -> Result < ManagedClient , StartupOutcomeError > {
151156 self . client . clone ( ) . await
152157 }
158+
159+ async fn notify_sandbox_state_change ( & self , sandbox_state : & SandboxState ) -> Result < ( ) > {
160+ let managed = self . client ( ) . await ?;
161+ if !managed. server_supports_sandbox_state_capability {
162+ return Ok ( ( ) ) ;
163+ }
164+
165+ managed
166+ . client
167+ . send_custom_notification (
168+ MCP_SANDBOX_STATE_NOTIFICATION ,
169+ Some ( serde_json:: to_value ( sandbox_state) ?) ,
170+ )
171+ . await
172+ }
173+ }
174+
175+ pub const MCP_SANDBOX_STATE_CAPABILITY : & str = "codex/sandbox-state" ;
176+
177+ /// Custom MCP notification for sandbox state updates.
178+ /// When used, the `params` field of the notification is [`SandboxState`].
179+ pub const MCP_SANDBOX_STATE_NOTIFICATION : & str = "codex/sandbox-state/update" ;
180+
181+ #[ derive( Debug , Clone , Serialize , Deserialize ) ]
182+ #[ serde( rename_all = "camelCase" ) ]
183+ pub struct SandboxState {
184+ pub sandbox_policy : SandboxPolicy ,
185+ pub codex_linux_sandbox_exe : Option < PathBuf > ,
186+ pub sandbox_cwd : PathBuf ,
153187}
154188
155189/// A thin wrapper around a set of running [`RmcpClient`] instances.
@@ -477,6 +511,34 @@ impl McpConnectionManager {
477511 . get ( tool_name)
478512 . map ( |tool| ( tool. server_name . clone ( ) , tool. tool_name . clone ( ) ) )
479513 }
514+
515+ pub async fn notify_sandbox_state_change ( & self , sandbox_state : & SandboxState ) -> Result < ( ) > {
516+ let mut join_set = JoinSet :: new ( ) ;
517+
518+ for async_managed_client in self . clients . values ( ) {
519+ let sandbox_state = sandbox_state. clone ( ) ;
520+ let async_managed_client = async_managed_client. clone ( ) ;
521+ join_set. spawn ( async move {
522+ async_managed_client
523+ . notify_sandbox_state_change ( & sandbox_state)
524+ . await
525+ } ) ;
526+ }
527+
528+ while let Some ( join_res) = join_set. join_next ( ) . await {
529+ match join_res {
530+ Ok ( Ok ( ( ) ) ) => { }
531+ Ok ( Err ( err) ) => {
532+ warn ! ( "Failed to notify sandbox state change to MCP server: {err:#}" ) ;
533+ }
534+ Err ( err) => {
535+ warn ! ( "Task panic when notifying sandbox state change to MCP server: {err:#}" ) ;
536+ }
537+ }
538+ }
539+
540+ Ok ( ( ) )
541+ }
480542}
481543
482544async fn emit_update (
@@ -639,7 +701,7 @@ async fn start_server_work(
639701 protocol_version : mcp_types:: MCP_SCHEMA_VERSION . to_owned ( ) ,
640702 } ;
641703
642- let client_result = match transport {
704+ let ( client , initialize_result ) = match transport {
643705 McpServerTransportConfig :: Stdio {
644706 command,
645707 args,
@@ -655,7 +717,7 @@ async fn start_server_work(
655717 client
656718 . initialize ( params. clone ( ) , Some ( startup_timeout) )
657719 . await
658- . map ( |_| client)
720+ . map ( |initialize_result| ( client, initialize_result ) )
659721 }
660722 Err ( err) => Err ( err. into ( ) ) ,
661723 }
@@ -686,19 +748,12 @@ async fn start_server_work(
686748 client
687749 . initialize ( params. clone ( ) , Some ( startup_timeout) )
688750 . await
689- . map ( |_| client)
751+ . map ( |initialize_result| ( client, initialize_result ) )
690752 }
691753 Err ( err) => Err ( err) ,
692754 }
693755 }
694- } ;
695-
696- let client = match client_result {
697- Ok ( client) => client,
698- Err ( error) => {
699- return Err ( error. into ( ) ) ;
700- }
701- } ;
756+ } ?;
702757
703758 let tools = match list_tools_for_client ( & server_name, & client, startup_timeout) . await {
704759 Ok ( tools) => tools,
@@ -707,11 +762,19 @@ async fn start_server_work(
707762 }
708763 } ;
709764
765+ let server_supports_sandbox_state_capability = initialize_result
766+ . capabilities
767+ . experimental
768+ . as_ref ( )
769+ . and_then ( |exp| exp. get ( MCP_SANDBOX_STATE_CAPABILITY ) )
770+ . is_some ( ) ;
771+
710772 let managed = ManagedClient {
711773 client : Arc :: clone ( & client) ,
712774 tools,
713775 tool_timeout : Some ( tool_timeout) ,
714776 tool_filter,
777+ server_supports_sandbox_state_capability,
715778 } ;
716779
717780 Ok ( managed)
0 commit comments