fix(core): preserve explicit mcp readiness after refresh

This commit is contained in:
Felipe Coury
2026-05-28 15:44:59 -03:00
parent 174b424616
commit bf6865b072
2 changed files with 88 additions and 8 deletions

View File

@@ -505,6 +505,8 @@ async fn build_skills_and_plugins(
{
explicitly_requested_mcp_servers.insert(CODEX_APPS_MCP_SERVER_NAME.to_string());
}
let explicit_mcp_servers_to_rewait_after_skill_install =
explicitly_requested_mcp_servers.clone();
wait_for_explicit_mcp_servers(
sess,
turn_context,
@@ -553,7 +555,7 @@ async fn build_skills_and_plugins(
&skills_outcome.disabled_paths,
&connector_slug_counts,
);
let skill_dependency_servers = maybe_prompt_and_install_mcp_dependencies(
let mut skill_dependency_servers = maybe_prompt_and_install_mcp_dependencies(
sess,
turn_context,
cancellation_token,
@@ -561,13 +563,18 @@ async fn build_skills_and_plugins(
Some(sess.mcp_elicitation_reviewer()),
)
.await;
wait_for_explicit_mcp_servers(
sess,
turn_context,
cancellation_token,
skill_dependency_servers,
)
.await?;
if !skill_dependency_servers.is_empty() {
// Installing a dependency refreshes the manager and restarts any app or
// plugin server already awaited for this same explicit invocation.
skill_dependency_servers.extend(explicit_mcp_servers_to_rewait_after_skill_install);
wait_for_explicit_mcp_servers(
sess,
turn_context,
cancellation_token,
skill_dependency_servers,
)
.await?;
}
let SkillInjections {
items: skill_injections,

View File

@@ -8,6 +8,8 @@ use std::time::Instant;
use anyhow::Result;
use codex_features::Feature;
use codex_login::CodexAuth;
use codex_protocol::models::PermissionProfile;
use codex_protocol::protocol::AskForApproval;
use codex_protocol::protocol::EventMsg;
use codex_protocol::protocol::Op;
use core_test_support::apps_test_server::AppsTestServer;
@@ -436,6 +438,77 @@ async fn explicitly_selected_skill_waits_for_pending_apps_startup() -> Result<()
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn selected_skill_rewaits_for_app_after_installing_mcp_dependency() -> Result<()> {
skip_if_no_network!(Ok(()));
let server = start_mock_server().await;
let apps_server = AppsTestServer::mount_with_connector_name_and_tools_list_delay(
&server,
"Google Calendar",
Some(Duration::from_secs(/*secs*/ 2)),
)
.await?;
let mock = mount_sse_once(
&server,
sse(vec![ev_response_created("resp-1"), ev_completed("resp-1")]),
)
.await;
let codex_home = Arc::new(TempDir::new()?);
let skill_path = write_plugin_skill_plugin(codex_home.as_ref());
std::fs::write(
&skill_path,
"---\ndescription: inspect sample data\n---\n\nUse [$calendar](app://calendar).\n",
)
.expect("write plugin app skill");
let skill_agents_dir = skill_path.parent().expect("skill dir").join("agents");
std::fs::create_dir_all(&skill_agents_dir).expect("create skill agents dir");
let dependency_command = stdio_server_bin()?;
std::fs::write(
skill_agents_dir.join("openai.yaml"),
format!(
"dependencies:\n tools:\n - type: \"mcp\"\n value: \"dependency\"\n transport: \"stdio\"\n command: \"{dependency_command}\"\n"
),
)
.expect("write plugin skill dependencies");
let codex =
build_apps_enabled_plugin_test_codex(&server, codex_home, apps_server.chatgpt_base_url)
.await?;
codex
.submit(Op::UserInput {
environments: None,
items: vec![codex_protocol::user_input::UserInput::Skill {
name: "sample:sample-search".into(),
path: skill_path,
}],
final_output_json_schema: None,
responsesapi_client_metadata: None,
additional_context: Default::default(),
thread_settings: codex_protocol::protocol::ThreadSettingsOverrides {
approval_policy: Some(AskForApproval::Never),
permission_profile: Some(PermissionProfile::Disabled),
..Default::default()
},
})
.await?;
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TurnComplete(_))).await;
let request = mock.single_request();
assert!(
tool_names(&request.body_json())
.iter()
.any(|name| name == "mcp__codex_apps__google_calendar"),
"expected app referenced by selected skill after dependency installation refresh"
);
assert!(
request.tool_by_name("mcp__dependency", "echo").is_some(),
"expected newly installed MCP dependency on the first turn"
);
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn explicit_plugin_mentions_track_plugin_used_analytics() -> Result<()> {
skip_if_no_network!(Ok(()));