Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions codex-rs/codex-mcp/src/connection_manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ use crate::codex_apps::CodexAppsToolsCacheContext;
use crate::codex_apps::CodexAppsToolsCacheKey;
use crate::codex_apps::write_cached_codex_apps_tools_if_needed;
use crate::elicitation::ElicitationRequestManager;
use crate::elicitation::ElicitationReviewerHandle;
use crate::mcp::CODEX_APPS_MCP_SERVER_NAME;
use crate::mcp::ToolPluginProvenance;
use crate::rmcp_client::AsyncManagedClient;
Expand Down Expand Up @@ -87,6 +88,7 @@ impl McpConnectionManager {
elicitation_requests: ElicitationRequestManager::new(
approval_policy.value(),
permission_profile.get().clone(),
/*reviewer*/ None,
),
startup_cancellation_token: CancellationToken::new(),
}
Expand Down Expand Up @@ -157,13 +159,17 @@ impl McpConnectionManager {
host_owned_codex_apps_enabled: bool,
tool_plugin_provenance: ToolPluginProvenance,
auth: Option<&CodexAuth>,
elicitation_reviewer: Option<ElicitationReviewerHandle>,
) -> (Self, CancellationToken) {
let cancel_token = CancellationToken::new();
let mut clients = HashMap::new();
let mut server_origins = HashMap::new();
let mut join_set = JoinSet::new();
let elicitation_requests =
ElicitationRequestManager::new(approval_policy.value(), initial_permission_profile);
let elicitation_requests = ElicitationRequestManager::new(
approval_policy.value(),
initial_permission_profile,
elicitation_reviewer,
);
let tool_plugin_provenance = Arc::new(tool_plugin_provenance);
let startup_submit_id = submit_id.clone();
let codex_apps_auth_provider = auth
Expand Down
14 changes: 10 additions & 4 deletions codex-rs/codex-mcp/src/connection_manager_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -203,8 +203,11 @@ fn elicitation_granular_policy_respects_never_and_config() {

#[tokio::test]
async fn disabled_permissions_auto_accept_elicitation_with_empty_form_schema() {
let manager =
ElicitationRequestManager::new(AskForApproval::Never, PermissionProfile::Disabled);
let manager = ElicitationRequestManager::new(
AskForApproval::Never,
PermissionProfile::Disabled,
/*reviewer*/ None,
);
let (tx_event, _rx_event) = async_channel::bounded(1);
let sender = manager.make_sender("server".to_string(), tx_event);

Expand Down Expand Up @@ -233,8 +236,11 @@ async fn disabled_permissions_auto_accept_elicitation_with_empty_form_schema() {

#[tokio::test]
async fn disabled_permissions_do_not_auto_accept_elicitation_with_requested_fields() {
let manager =
ElicitationRequestManager::new(AskForApproval::Never, PermissionProfile::Disabled);
let manager = ElicitationRequestManager::new(
AskForApproval::Never,
PermissionProfile::Disabled,
/*reviewer*/ None,
);
let (tx_event, _rx_event) = async_channel::bounded(1);
let sender = manager.make_sender("server".to_string(), tx_event);

Expand Down
33 changes: 33 additions & 0 deletions codex-rs/codex-mcp/src/elicitation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,31 +24,51 @@ use codex_protocol::protocol::Event;
use codex_protocol::protocol::EventMsg;
use codex_rmcp_client::ElicitationResponse;
use codex_rmcp_client::SendElicitation;
use futures::future::BoxFuture;
use futures::future::FutureExt;
use rmcp::model::CreateElicitationRequestParams;
use rmcp::model::ElicitationAction;
use rmcp::model::RequestId;
use tokio::sync::Mutex;
use tokio::sync::oneshot;

#[derive(Debug, Clone)]
pub struct ElicitationReviewRequest {
pub server_name: String,
pub request_id: RequestId,
pub elicitation: CreateElicitationRequestParams,
}

pub trait ElicitationReviewer: Send + Sync {
fn review(
&self,
request: ElicitationReviewRequest,
) -> BoxFuture<'static, Result<Option<ElicitationResponse>>>;
}

pub type ElicitationReviewerHandle = Arc<dyn ElicitationReviewer>;

#[derive(Clone)]
pub(crate) struct ElicitationRequestManager {
requests: Arc<Mutex<ResponderMap>>,
pub(crate) approval_policy: Arc<StdMutex<AskForApproval>>,
pub(crate) permission_profile: Arc<StdMutex<PermissionProfile>>,
auto_deny: Arc<StdMutex<bool>>,
reviewer: Option<ElicitationReviewerHandle>,
}

impl ElicitationRequestManager {
pub(crate) fn new(
approval_policy: AskForApproval,
permission_profile: PermissionProfile,
reviewer: Option<ElicitationReviewerHandle>,
) -> Self {
Self {
requests: Arc::new(Mutex::new(HashMap::new())),
approval_policy: Arc::new(StdMutex::new(approval_policy)),
permission_profile: Arc::new(StdMutex::new(permission_profile)),
auto_deny: Arc::new(StdMutex::new(false)),
reviewer,
}
}

Expand Down Expand Up @@ -89,13 +109,15 @@ impl ElicitationRequestManager {
let approval_policy = self.approval_policy.clone();
let permission_profile = self.permission_profile.clone();
let auto_deny = self.auto_deny.clone();
let reviewer = self.reviewer.clone();
Box::new(move |id, elicitation| {
let elicitation_requests = elicitation_requests.clone();
let tx_event = tx_event.clone();
let server_name = server_name.clone();
let approval_policy = approval_policy.clone();
let permission_profile = permission_profile.clone();
let auto_deny = auto_deny.clone();
let reviewer = reviewer.clone();
async move {
let auto_deny = auto_deny
.lock()
Expand Down Expand Up @@ -138,6 +160,17 @@ impl ElicitationRequestManager {
});
}

if let Some(reviewer) = reviewer.as_ref() {
let request = ElicitationReviewRequest {
server_name: server_name.clone(),
request_id: id.clone(),
elicitation: elicitation.clone(),
};
if let Some(response) = reviewer.review(request).await? {
return Ok(response);
}
}

let request = match elicitation {
CreateElicitationRequestParams::FormElicitationParams {
meta,
Expand Down
3 changes: 3 additions & 0 deletions codex-rs/codex-mcp/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
pub use connection_manager::McpConnectionManager;
pub use elicitation::ElicitationReviewRequest;
pub use elicitation::ElicitationReviewer;
pub use elicitation::ElicitationReviewerHandle;
pub use rmcp_client::MCP_SANDBOX_STATE_META_CAPABILITY;
pub use runtime::McpRuntimeEnvironment;
pub use runtime::SandboxState;
Expand Down
2 changes: 2 additions & 0 deletions codex-rs/codex-mcp/src/mcp/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,7 @@ pub async fn read_mcp_resource(
host_owned_codex_apps_enabled,
tool_plugin_provenance(config),
auth,
/*elicitation_reviewer*/ None,
)
.await;

Expand Down Expand Up @@ -331,6 +332,7 @@ pub async fn collect_mcp_server_status_snapshot_with_detail(
host_owned_codex_apps_enabled,
tool_plugin_provenance,
auth,
/*elicitation_reviewer*/ None,
)
.await;

Expand Down
1 change: 1 addition & 0 deletions codex-rs/core/src/connectors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,7 @@ pub async fn list_accessible_connectors_from_mcp_tools_with_environment_manager(
host_owned_codex_apps_enabled,
ToolPluginProvenance::default(),
auth.as_ref(),
/*elicitation_reviewer*/ None,
)
.await;

Expand Down
13 changes: 12 additions & 1 deletion codex-rs/core/src/mcp_skill_dependencies.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ use crate::SkillMetadata;
use crate::session::session::Session;
use crate::session::turn_context::TurnContext;
use crate::skills::model::SkillToolDependency;
use codex_mcp::ElicitationReviewerHandle;
use codex_mcp::McpOAuthLoginSupport;
use codex_mcp::McpPermissionPromptAutoApproveContext;
use codex_mcp::mcp_permission_prompt_is_auto_approved;
Expand All @@ -35,6 +36,7 @@ pub(crate) async fn maybe_prompt_and_install_mcp_dependencies(
turn_context: &TurnContext,
cancellation_token: &CancellationToken,
mentioned_skills: &[SkillMetadata],
elicitation_reviewer: Option<ElicitationReviewerHandle>,
) {
let originator_value = originator().value;
if !is_first_party_originator(originator_value.as_str()) {
Expand Down Expand Up @@ -69,7 +71,14 @@ pub(crate) async fn maybe_prompt_and_install_mcp_dependencies(
if should_install_mcp_dependencies(sess, turn_context, &unprompted_missing, cancellation_token)
.await
{
maybe_install_mcp_dependencies(sess, turn_context, config.as_ref(), mentioned_skills).await;
maybe_install_mcp_dependencies(
sess,
turn_context,
config.as_ref(),
mentioned_skills,
elicitation_reviewer,
)
.await;
}
}

Expand All @@ -78,6 +87,7 @@ pub(crate) async fn maybe_install_mcp_dependencies(
turn_context: &TurnContext,
config: &crate::config::Config,
mentioned_skills: &[SkillMetadata],
elicitation_reviewer: Option<ElicitationReviewerHandle>,
) {
if mentioned_skills.is_empty()
|| !config
Expand Down Expand Up @@ -194,6 +204,7 @@ pub(crate) async fn maybe_install_mcp_dependencies(
turn_context,
refresh_servers,
config.mcp_oauth_credentials_store_mode,
elicitation_reviewer,
)
.await;
}
Expand Down
28 changes: 14 additions & 14 deletions codex-rs/core/src/mcp_tool_call.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,20 @@ use codex_protocol::items::McpToolCallItem;
use codex_protocol::items::McpToolCallStatus;
use codex_protocol::items::TurnItem;
use codex_protocol::mcp::CallToolResult;
use codex_protocol::mcp_approval_meta::APPROVAL_KIND_KEY as MCP_TOOL_APPROVAL_KIND_KEY;
use codex_protocol::mcp_approval_meta::APPROVAL_KIND_MCP_TOOL_CALL as MCP_TOOL_APPROVAL_KIND_MCP_TOOL_CALL;
use codex_protocol::mcp_approval_meta::CONNECTOR_DESCRIPTION_KEY as MCP_TOOL_APPROVAL_CONNECTOR_DESCRIPTION_KEY;
use codex_protocol::mcp_approval_meta::CONNECTOR_ID_KEY as MCP_TOOL_APPROVAL_CONNECTOR_ID_KEY;
use codex_protocol::mcp_approval_meta::CONNECTOR_NAME_KEY as MCP_TOOL_APPROVAL_CONNECTOR_NAME_KEY;
use codex_protocol::mcp_approval_meta::PERSIST_ALWAYS as MCP_TOOL_APPROVAL_PERSIST_ALWAYS;
use codex_protocol::mcp_approval_meta::PERSIST_KEY as MCP_TOOL_APPROVAL_PERSIST_KEY;
use codex_protocol::mcp_approval_meta::PERSIST_SESSION as MCP_TOOL_APPROVAL_PERSIST_SESSION;
use codex_protocol::mcp_approval_meta::SOURCE_CONNECTOR as MCP_TOOL_APPROVAL_SOURCE_CONNECTOR;
use codex_protocol::mcp_approval_meta::SOURCE_KEY as MCP_TOOL_APPROVAL_SOURCE_KEY;
use codex_protocol::mcp_approval_meta::TOOL_DESCRIPTION_KEY as MCP_TOOL_APPROVAL_TOOL_DESCRIPTION_KEY;
use codex_protocol::mcp_approval_meta::TOOL_PARAMS_DISPLAY_KEY as MCP_TOOL_APPROVAL_TOOL_PARAMS_DISPLAY_KEY;
use codex_protocol::mcp_approval_meta::TOOL_PARAMS_KEY as MCP_TOOL_APPROVAL_TOOL_PARAMS_KEY;
use codex_protocol::mcp_approval_meta::TOOL_TITLE_KEY as MCP_TOOL_APPROVAL_TOOL_TITLE_KEY;
use codex_protocol::openai_models::InputModality;
use codex_protocol::protocol::AskForApproval;
use codex_protocol::protocol::McpInvocation;
Expand Down Expand Up @@ -1097,20 +1111,6 @@ pub(crate) const MCP_TOOL_APPROVAL_ACCEPT_FOR_SESSION: &str = "Allow for this se
pub(crate) const MCP_TOOL_APPROVAL_DECLINE_SYNTHETIC: &str = "__codex_mcp_decline__";
const MCP_TOOL_APPROVAL_ACCEPT_AND_REMEMBER: &str = "Allow and don't ask me again";
const MCP_TOOL_APPROVAL_CANCEL: &str = "Cancel";
const MCP_TOOL_APPROVAL_KIND_KEY: &str = "codex_approval_kind";
const MCP_TOOL_APPROVAL_KIND_MCP_TOOL_CALL: &str = "mcp_tool_call";
const MCP_TOOL_APPROVAL_PERSIST_KEY: &str = "persist";
const MCP_TOOL_APPROVAL_PERSIST_SESSION: &str = "session";
const MCP_TOOL_APPROVAL_PERSIST_ALWAYS: &str = "always";
const MCP_TOOL_APPROVAL_SOURCE_KEY: &str = "source";
const MCP_TOOL_APPROVAL_SOURCE_CONNECTOR: &str = "connector";
const MCP_TOOL_APPROVAL_CONNECTOR_ID_KEY: &str = "connector_id";
const MCP_TOOL_APPROVAL_CONNECTOR_NAME_KEY: &str = "connector_name";
const MCP_TOOL_APPROVAL_CONNECTOR_DESCRIPTION_KEY: &str = "connector_description";
const MCP_TOOL_APPROVAL_TOOL_TITLE_KEY: &str = "tool_title";
const MCP_TOOL_APPROVAL_TOOL_DESCRIPTION_KEY: &str = "tool_description";
const MCP_TOOL_APPROVAL_TOOL_PARAMS_KEY: &str = "tool_params";
const MCP_TOOL_APPROVAL_TOOL_PARAMS_DISPLAY_KEY: &str = "tool_params_display";
const MCP_TOOL_CALL_ARC_MONITOR_CALLSITE_DEFAULT: &str = "mcp_tool_call__default";
const MCP_TOOL_CALL_ARC_MONITOR_CALLSITE_ALWAYS_ALLOW: &str = "mcp_tool_call__always_allow";

Expand Down
1 change: 1 addition & 0 deletions codex-rs/core/src/mcp_tool_call_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1124,6 +1124,7 @@ async fn install_host_owned_codex_apps_manager(session: &Session, turn_context:
/*host_owned_codex_apps_enabled*/ true,
codex_mcp::ToolPluginProvenance::default(),
auth.as_ref(),
/*elicitation_reviewer*/ None,
)
.await;
*session.services.mcp_connection_manager.write().await = manager;
Expand Down
10 changes: 7 additions & 3 deletions codex-rs/core/src/session/handlers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -255,8 +255,11 @@ pub(super) async fn user_input_or_turn_inner(
.set_responsesapi_client_metadata(responsesapi_client_metadata);
}
current_context.session_telemetry.user_prompt(&items);
sess.refresh_mcp_servers_if_requested(&current_context)
.await;
sess.refresh_mcp_servers_if_requested(
&current_context,
Some(sess.mcp_elicitation_reviewer()),
)
.await;
let accepted_items = items.clone();
sess.spawn_task(
Arc::clone(&current_context),
Expand Down Expand Up @@ -679,7 +682,8 @@ pub async fn review(
let turn_context = sess.new_default_turn_with_sub_id(sub_id.clone()).await;
sess.maybe_emit_unknown_model_warning_for_turn(turn_context.as_ref())
.await;
sess.refresh_mcp_servers_if_requested(&turn_context).await;
sess.refresh_mcp_servers_if_requested(&turn_context, Some(sess.mcp_elicitation_reviewer()))
.await;
match resolve_review_request(review_request, &turn_context.cwd) {
Ok(resolved) => {
spawn_review_thread(
Expand Down
Loading
Loading