mirror of
https://github.com/openai/codex.git
synced 2026-11-14 21:40:39 +00:00
feat: declare sandbox state MCP capability
This commit is contained in:
@@ -5,6 +5,7 @@ use std::sync::Arc;
|
||||
use std::sync::atomic::AtomicU64;
|
||||
|
||||
use crate::AuthManager;
|
||||
use crate::SandboxState;
|
||||
use crate::client_common::REVIEW_PROMPT;
|
||||
use crate::compact;
|
||||
use crate::compact::run_inline_auto_compact_task;
|
||||
@@ -612,6 +613,22 @@ impl Session {
|
||||
)
|
||||
.await;
|
||||
|
||||
let sandbox_state = SandboxState {
|
||||
sandbox_policy: session_configuration.sandbox_policy.clone(),
|
||||
codex_linux_sandbox_exe: config.codex_linux_sandbox_exe.clone(),
|
||||
sandbox_cwd: session_configuration.cwd.clone(),
|
||||
};
|
||||
if let Err(e) = sess
|
||||
.services
|
||||
.mcp_connection_manager
|
||||
.read()
|
||||
.await
|
||||
.notify_sandbox_state_change(&sandbox_state)
|
||||
.await
|
||||
{
|
||||
tracing::error!("Failed to notify sandbox state change: {e}");
|
||||
}
|
||||
|
||||
// record_initial_history can emit events. We record only after the SessionConfiguredEvent is emitted.
|
||||
sess.record_initial_history(initial_history).await;
|
||||
|
||||
|
||||
@@ -32,6 +32,9 @@ pub mod git_info;
|
||||
pub mod landlock;
|
||||
pub mod mcp;
|
||||
mod mcp_connection_manager;
|
||||
pub use mcp_connection_manager::MCP_SANDBOX_STATE_CAPABILITY;
|
||||
pub use mcp_connection_manager::MCP_SANDBOX_STATE_NOTIFICATION;
|
||||
pub use mcp_connection_manager::SandboxState;
|
||||
mod mcp_tool_call;
|
||||
mod message_history;
|
||||
mod model_provider_info;
|
||||
|
||||
@@ -10,6 +10,7 @@ use std::collections::HashMap;
|
||||
use std::collections::HashSet;
|
||||
use std::env;
|
||||
use std::ffi::OsString;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
@@ -26,6 +27,7 @@ use codex_protocol::protocol::McpStartupCompleteEvent;
|
||||
use codex_protocol::protocol::McpStartupFailure;
|
||||
use codex_protocol::protocol::McpStartupStatus;
|
||||
use codex_protocol::protocol::McpStartupUpdateEvent;
|
||||
use codex_protocol::protocol::SandboxPolicy;
|
||||
use codex_rmcp_client::OAuthCredentialsStoreMode;
|
||||
use codex_rmcp_client::RmcpClient;
|
||||
use futures::future::BoxFuture;
|
||||
@@ -43,6 +45,8 @@ use mcp_types::Resource;
|
||||
use mcp_types::ResourceTemplate;
|
||||
use mcp_types::Tool;
|
||||
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use serde_json::json;
|
||||
use sha1::Digest;
|
||||
use sha1::Sha1;
|
||||
@@ -116,6 +120,7 @@ struct ManagedClient {
|
||||
tools: Vec<ToolInfo>,
|
||||
tool_filter: ToolFilter,
|
||||
tool_timeout: Option<Duration>,
|
||||
server_supports_sandbox_state_capability: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
@@ -150,6 +155,35 @@ impl AsyncManagedClient {
|
||||
async fn client(&self) -> Result<ManagedClient, StartupOutcomeError> {
|
||||
self.client.clone().await
|
||||
}
|
||||
|
||||
async fn notify_sandbox_state_change(&self, sandbox_state: &SandboxState) -> Result<()> {
|
||||
let managed = self.client().await?;
|
||||
if !managed.server_supports_sandbox_state_capability {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
managed
|
||||
.client
|
||||
.send_custom_notification(
|
||||
MCP_SANDBOX_STATE_NOTIFICATION,
|
||||
Some(serde_json::to_value(sandbox_state)?),
|
||||
)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
pub const MCP_SANDBOX_STATE_CAPABILITY: &str = "codex/sandbox-state";
|
||||
|
||||
/// Custom MCP notification for sandbox state updates.
|
||||
/// When used, the `params` field of the notification is [`SandboxState`].
|
||||
pub const MCP_SANDBOX_STATE_NOTIFICATION: &str = "codex/sandbox-state/update";
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct SandboxState {
|
||||
pub sandbox_policy: SandboxPolicy,
|
||||
pub codex_linux_sandbox_exe: Option<PathBuf>,
|
||||
pub sandbox_cwd: PathBuf,
|
||||
}
|
||||
|
||||
/// A thin wrapper around a set of running [`RmcpClient`] instances.
|
||||
@@ -477,6 +511,34 @@ impl McpConnectionManager {
|
||||
.get(tool_name)
|
||||
.map(|tool| (tool.server_name.clone(), tool.tool_name.clone()))
|
||||
}
|
||||
|
||||
pub async fn notify_sandbox_state_change(&self, sandbox_state: &SandboxState) -> Result<()> {
|
||||
let mut join_set = JoinSet::new();
|
||||
|
||||
for async_managed_client in self.clients.values() {
|
||||
let sandbox_state = sandbox_state.clone();
|
||||
let async_managed_client = async_managed_client.clone();
|
||||
join_set.spawn(async move {
|
||||
async_managed_client
|
||||
.notify_sandbox_state_change(&sandbox_state)
|
||||
.await
|
||||
});
|
||||
}
|
||||
|
||||
while let Some(join_res) = join_set.join_next().await {
|
||||
match join_res {
|
||||
Ok(Ok(())) => {}
|
||||
Ok(Err(err)) => {
|
||||
warn!("Failed to notify sandbox state change to MCP server: {err:#}");
|
||||
}
|
||||
Err(err) => {
|
||||
warn!("Task panic when notifying sandbox state change to MCP server: {err:#}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
async fn emit_update(
|
||||
@@ -639,7 +701,7 @@ async fn start_server_work(
|
||||
protocol_version: mcp_types::MCP_SCHEMA_VERSION.to_owned(),
|
||||
};
|
||||
|
||||
let client_result = match transport {
|
||||
let (client, initialize_result) = match transport {
|
||||
McpServerTransportConfig::Stdio {
|
||||
command,
|
||||
args,
|
||||
@@ -655,7 +717,7 @@ async fn start_server_work(
|
||||
client
|
||||
.initialize(params.clone(), Some(startup_timeout))
|
||||
.await
|
||||
.map(|_| client)
|
||||
.map(|initialize_result| (client, initialize_result))
|
||||
}
|
||||
Err(err) => Err(err.into()),
|
||||
}
|
||||
@@ -686,19 +748,12 @@ async fn start_server_work(
|
||||
client
|
||||
.initialize(params.clone(), Some(startup_timeout))
|
||||
.await
|
||||
.map(|_| client)
|
||||
.map(|initialize_result| (client, initialize_result))
|
||||
}
|
||||
Err(err) => Err(err),
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let client = match client_result {
|
||||
Ok(client) => client,
|
||||
Err(error) => {
|
||||
return Err(error.into());
|
||||
}
|
||||
};
|
||||
}?;
|
||||
|
||||
let tools = match list_tools_for_client(&server_name, &client, startup_timeout).await {
|
||||
Ok(tools) => tools,
|
||||
@@ -707,11 +762,19 @@ async fn start_server_work(
|
||||
}
|
||||
};
|
||||
|
||||
let server_supports_sandbox_state_capability = initialize_result
|
||||
.capabilities
|
||||
.experimental
|
||||
.as_ref()
|
||||
.and_then(|exp| exp.get(MCP_SANDBOX_STATE_CAPABILITY))
|
||||
.is_some();
|
||||
|
||||
let managed = ManagedClient {
|
||||
client: Arc::clone(&client),
|
||||
tools,
|
||||
tool_timeout: Some(tool_timeout),
|
||||
tool_filter,
|
||||
server_supports_sandbox_state_capability,
|
||||
};
|
||||
|
||||
Ok(managed)
|
||||
|
||||
Reference in New Issue
Block a user