mirror of
https://github.com/openai/codex.git
synced 2026-11-02 19:43:24 +00:00
feat: declare sandbox state MCP capability
This commit is contained in:
@@ -8,8 +8,8 @@ use std::time::Duration;
|
||||
use anyhow::Context as _;
|
||||
use path_absolutize::Absolutize as _;
|
||||
|
||||
use codex_core::SandboxState;
|
||||
use codex_core::exec::process_exec_tool_call;
|
||||
use codex_core::protocol::SandboxPolicy;
|
||||
use tokio::process::Command;
|
||||
use tokio_util::sync::CancellationToken;
|
||||
|
||||
@@ -48,6 +48,7 @@ impl EscalateServer {
|
||||
&self,
|
||||
params: ExecParams,
|
||||
cancel_rx: CancellationToken,
|
||||
sandbox_state: &SandboxState,
|
||||
) -> anyhow::Result<ExecResult> {
|
||||
let (escalate_server, escalate_client) = AsyncDatagramSocket::pair()?;
|
||||
let client_socket = escalate_client.into_inner();
|
||||
@@ -64,12 +65,6 @@ impl EscalateServer {
|
||||
self.execve_wrapper.to_string_lossy().to_string(),
|
||||
);
|
||||
|
||||
// TODO: use the sandbox policy and cwd from the calling client.
|
||||
// Note that sandbox_cwd is ignored for ReadOnly, but needs to be legit
|
||||
// for `SandboxPolicy::WorkspaceWrite`.
|
||||
let sandbox_policy = SandboxPolicy::ReadOnly;
|
||||
let sandbox_cwd = PathBuf::from("/__NONEXISTENT__");
|
||||
|
||||
let ExecParams {
|
||||
command,
|
||||
workdir,
|
||||
@@ -94,9 +89,9 @@ impl EscalateServer {
|
||||
justification: None,
|
||||
arg0: None,
|
||||
},
|
||||
&sandbox_policy,
|
||||
&sandbox_cwd,
|
||||
&None,
|
||||
&sandbox_state.sandbox_policy,
|
||||
&sandbox_state.sandbox_cwd,
|
||||
&sandbox_state.codex_linux_sandbox_exe,
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
use anyhow::Context as _;
|
||||
use anyhow::Result;
|
||||
use codex_core::MCP_SANDBOX_STATE_CAPABILITY;
|
||||
use codex_core::MCP_SANDBOX_STATE_NOTIFICATION;
|
||||
use codex_core::SandboxState;
|
||||
use codex_core::protocol::SandboxPolicy;
|
||||
use rmcp::ErrorData as McpError;
|
||||
use rmcp::RoleServer;
|
||||
use rmcp::ServerHandler;
|
||||
@@ -17,6 +22,8 @@ use rmcp::tool;
|
||||
use rmcp::tool_handler;
|
||||
use rmcp::tool_router;
|
||||
use rmcp::transport::stdio;
|
||||
use tokio::sync::RwLock;
|
||||
use tracing::debug;
|
||||
|
||||
use crate::posix::escalate_server::EscalateServer;
|
||||
use crate::posix::escalate_server::{self};
|
||||
@@ -27,6 +34,8 @@ use crate::posix::stopwatch::Stopwatch;
|
||||
/// Path to our patched bash.
|
||||
const CODEX_BASH_PATH_ENV_VAR: &str = "CODEX_BASH_PATH";
|
||||
|
||||
const SANDBOX_STATE_CAPABILITY_VERSION: &str = "1.0.0";
|
||||
|
||||
pub(crate) fn get_bash_path() -> Result<PathBuf> {
|
||||
std::env::var(CODEX_BASH_PATH_ENV_VAR)
|
||||
.map(PathBuf::from)
|
||||
@@ -70,6 +79,7 @@ pub struct ExecTool {
|
||||
bash_path: PathBuf,
|
||||
execve_wrapper: PathBuf,
|
||||
policy: ExecPolicy,
|
||||
sandbox_state: Arc<RwLock<Option<SandboxState>>>,
|
||||
}
|
||||
|
||||
#[tool_router]
|
||||
@@ -80,6 +90,7 @@ impl ExecTool {
|
||||
bash_path,
|
||||
execve_wrapper,
|
||||
policy,
|
||||
sandbox_state: Arc::new(RwLock::new(None)),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,13 +108,24 @@ impl ExecTool {
|
||||
);
|
||||
let stopwatch = Stopwatch::new(effective_timeout);
|
||||
let cancel_token = stopwatch.cancellation_token();
|
||||
let sandbox_state =
|
||||
self.sandbox_state
|
||||
.read()
|
||||
.await
|
||||
.clone()
|
||||
.unwrap_or_else(|| SandboxState {
|
||||
sandbox_policy: SandboxPolicy::ReadOnly,
|
||||
codex_linux_sandbox_exe: None,
|
||||
sandbox_cwd: PathBuf::from(¶ms.workdir),
|
||||
});
|
||||
let escalate_server = EscalateServer::new(
|
||||
self.bash_path.clone(),
|
||||
self.execve_wrapper.clone(),
|
||||
McpEscalationPolicy::new(self.policy, context, stopwatch.clone()),
|
||||
);
|
||||
|
||||
let result = escalate_server
|
||||
.exec(params, cancel_token)
|
||||
.exec(params, cancel_token, &sandbox_state)
|
||||
.await
|
||||
.map_err(|e| McpError::internal_error(e.to_string(), None))?;
|
||||
Ok(CallToolResult::success(vec![Content::json(
|
||||
@@ -115,9 +137,22 @@ impl ExecTool {
|
||||
#[tool_handler]
|
||||
impl ServerHandler for ExecTool {
|
||||
fn get_info(&self) -> ServerInfo {
|
||||
let mut experimental_capabilities = ExperimentalCapabilities::new();
|
||||
let mut sandbox_state_capability = JsonObject::new();
|
||||
sandbox_state_capability.insert(
|
||||
"version".to_string(),
|
||||
serde_json::Value::String(SANDBOX_STATE_CAPABILITY_VERSION.to_string()),
|
||||
);
|
||||
experimental_capabilities.insert(
|
||||
MCP_SANDBOX_STATE_CAPABILITY.to_string(),
|
||||
sandbox_state_capability,
|
||||
);
|
||||
ServerInfo {
|
||||
protocol_version: ProtocolVersion::V_2025_06_18,
|
||||
capabilities: ServerCapabilities::builder().enable_tools().build(),
|
||||
capabilities: ServerCapabilities::builder()
|
||||
.enable_tools()
|
||||
.enable_experimental_with(experimental_capabilities)
|
||||
.build(),
|
||||
server_info: Implementation::from_build_env(),
|
||||
instructions: Some(
|
||||
"This server provides a tool to execute shell commands and return their output."
|
||||
@@ -133,6 +168,31 @@ impl ServerHandler for ExecTool {
|
||||
) -> Result<InitializeResult, McpError> {
|
||||
Ok(self.get_info())
|
||||
}
|
||||
|
||||
async fn on_custom_notification(
|
||||
&self,
|
||||
notification: rmcp::model::CustomClientNotification,
|
||||
_context: rmcp::service::NotificationContext<rmcp::RoleServer>,
|
||||
) {
|
||||
let rmcp::model::CustomClientNotification { method, params, .. } = notification;
|
||||
if method == MCP_SANDBOX_STATE_NOTIFICATION
|
||||
&& let Some(params) = params
|
||||
{
|
||||
match serde_json::from_value::<SandboxState>(params) {
|
||||
Ok(sandbox_state) => {
|
||||
debug!(
|
||||
?sandbox_state.sandbox_policy,
|
||||
"received sandbox state notification"
|
||||
);
|
||||
let mut state = self.sandbox_state.write().await;
|
||||
*state = Some(sandbox_state);
|
||||
}
|
||||
Err(err) => {
|
||||
tracing::warn!(?err, "failed to deserialize sandbox state notification");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn serve(
|
||||
|
||||
Reference in New Issue
Block a user