mirror of
https://github.com/openai/codex.git
synced 2026-04-30 03:12:20 +03:00
fix: change codex/sandbox-state/update from a notification to a request (#8142)
Historically, `accept_elicitation_for_prompt_rule()` was flaky because we were using a notification to update the sandbox followed by a `shell` tool request that we expected to be subject to the new sandbox config, but because [rmcp](https://crates.io/crates/rmcp) MCP servers delegate each incoming message to a new Tokio task, messages are not guaranteed to be processed in order, so sometimes the `shell` tool call would run before the notification was processed. Prior to this PR, we relied on a generous `sleep()` between the notification and the request to reduce the change of the test flaking out. This PR implements a proper fix, which is to use a _request_ instead of a notification for the sandbox update so that we can wait for the response to the sandbox request before sending the request to the `shell` tool call. Previously, `rmcp` did not support custom requests, but I fixed that in https://github.com/modelcontextprotocol/rust-sdk/pull/590, which made it into the `0.12.0` release (see #8288). This PR updates `shell-tool-mcp` to expect `"codex/sandbox-state/update"` as a _request_ instead of a notification and sends the appropriate ack. Note this behavior is tied to our custom `codex/sandbox-state` capability, which Codex honors as an MCP client, which is why `core/src/mcp_connection_manager.rs` had to be updated as part of this PR, as well. This PR also updates the docs at `shell-tool-mcp/README.md`.
This commit is contained in:
@@ -5,7 +5,7 @@ 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::MCP_SANDBOX_STATE_METHOD;
|
||||
use codex_core::SandboxState;
|
||||
use codex_core::protocol::SandboxPolicy;
|
||||
use codex_execpolicy::Policy;
|
||||
@@ -15,6 +15,8 @@ use rmcp::ServerHandler;
|
||||
use rmcp::ServiceExt;
|
||||
use rmcp::handler::server::router::tool::ToolRouter;
|
||||
use rmcp::handler::server::wrapper::Parameters;
|
||||
use rmcp::model::CustomRequest;
|
||||
use rmcp::model::CustomResult;
|
||||
use rmcp::model::*;
|
||||
use rmcp::schemars;
|
||||
use rmcp::service::RequestContext;
|
||||
@@ -23,8 +25,8 @@ use rmcp::tool;
|
||||
use rmcp::tool_handler;
|
||||
use rmcp::tool_router;
|
||||
use rmcp::transport::stdio;
|
||||
use serde_json::json;
|
||||
use tokio::sync::RwLock;
|
||||
use tracing::debug;
|
||||
|
||||
use crate::posix::escalate_server::EscalateServer;
|
||||
use crate::posix::escalate_server::{self};
|
||||
@@ -146,6 +148,13 @@ impl ExecTool {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct CodexSandboxStateUpdateMethod;
|
||||
|
||||
impl rmcp::model::ConstString for CodexSandboxStateUpdateMethod {
|
||||
const VALUE: &'static str = MCP_SANDBOX_STATE_METHOD;
|
||||
}
|
||||
|
||||
#[tool_handler]
|
||||
impl ServerHandler for ExecTool {
|
||||
fn get_info(&self) -> ServerInfo {
|
||||
@@ -181,29 +190,33 @@ impl ServerHandler for ExecTool {
|
||||
Ok(self.get_info())
|
||||
}
|
||||
|
||||
async fn on_custom_notification(
|
||||
async fn on_custom_request(
|
||||
&self,
|
||||
notification: rmcp::model::CustomNotification,
|
||||
_context: rmcp::service::NotificationContext<rmcp::RoleServer>,
|
||||
) {
|
||||
let rmcp::model::CustomNotification { 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");
|
||||
}
|
||||
}
|
||||
request: CustomRequest,
|
||||
_context: rmcp::service::RequestContext<rmcp::RoleServer>,
|
||||
) -> Result<CustomResult, McpError> {
|
||||
let CustomRequest { method, params, .. } = request;
|
||||
if method != MCP_SANDBOX_STATE_METHOD {
|
||||
return Err(McpError::method_not_found::<CodexSandboxStateUpdateMethod>());
|
||||
}
|
||||
|
||||
let Some(params) = params else {
|
||||
return Err(McpError::invalid_params(
|
||||
"missing params for sandbox state request".to_string(),
|
||||
None,
|
||||
));
|
||||
};
|
||||
|
||||
let Ok(sandbox_state) = serde_json::from_value::<SandboxState>(params.clone()) else {
|
||||
return Err(McpError::invalid_params(
|
||||
"failed to deserialize sandbox state".to_string(),
|
||||
Some(params),
|
||||
));
|
||||
};
|
||||
|
||||
*self.sandbox_state.write().await = Some(sandbox_state);
|
||||
|
||||
Ok(CustomResult::new(json!({})))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user