mirror of
https://github.com/openai/codex.git
synced 2026-09-04 15:08:45 +00:00
[codex] preserve runtime MCP servers across client capability refresh
This commit is contained in:
@@ -232,7 +232,7 @@ Example with notification opt-out:
|
||||
- `mcpServer/oauth/login` — start an OAuth login for a configured MCP server; returns an `authorization_url` and later emits `mcpServer/oauthLogin/completed` once the browser flow finishes.
|
||||
- `tool/requestUserInput` — prompt the user with 1–3 short questions for a tool call and return their answers (experimental).
|
||||
- `config/mcpServer/reload` — reload MCP server config from disk and queue a refresh for loaded threads (applied on each thread's next active turn); returns `{}`. Use this after editing `config.toml` without restarting the server.
|
||||
- `mcpServerStatus/list` — enumerate configured MCP servers with their tools, auth status, server info, plus resources/resource templates for `full` detail; supports optional `threadId` and cursor+limit pagination. If `threadId` is omitted, the server reads from the latest global config directly. If `detail` is omitted, the server defaults to `full`.
|
||||
- `mcpServerStatus/list` — enumerate configured MCP servers with their tools, auth status, server info, plus resources/resource templates for `full` detail; supports optional `threadId` and cursor+limit pagination. With `threadId`, the response is a point-in-time snapshot of the MCP manager generation currently installed for that thread. If `threadId` is omitted, the server reads from the latest global config directly. If `detail` is omitted, the server defaults to `full`.
|
||||
- `mcpServer/resource/read` — read a resource from a configured MCP server by optional `threadId`, `server`, and `uri`, returning text/blob resource `contents`. If `threadId` is omitted, the server reads from the latest MCP config directly.
|
||||
- `mcpServer/tool/call` — call a tool on a thread's configured MCP server by `threadId`, `server`, `tool`, optional `arguments`, and optional `_meta`, returning the MCP tool result.
|
||||
- `windowsSandbox/setupStart` — start Windows sandbox setup for the selected mode (`elevated` or `unelevated`); accepts an optional absolute `cwd` to target setup for a specific workspace, returns `{ started: true }` immediately, and later emits `windowsSandbox/setupCompleted`.
|
||||
|
||||
@@ -214,7 +214,7 @@ url = "{mcp_server_url}/mcp"
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn mcp_server_status_list_never_advertises_uninstalled_codex_apps() -> Result<()> {
|
||||
async fn mcp_server_status_list_reflects_installed_manager_generation() -> Result<()> {
|
||||
let server = create_mock_responses_server_sequence_unchecked(Vec::new()).await;
|
||||
let (apps_server_url, apps_server_handle) = start_apps_mcp_server("calendar_lookup").await?;
|
||||
let codex_home = TempDir::new()?;
|
||||
|
||||
@@ -593,7 +593,7 @@ impl CodexThread {
|
||||
self.codex.session.runtime_mcp_config(config).await
|
||||
}
|
||||
|
||||
/// Returns the MCP inventory installed for this thread.
|
||||
/// Returns a point-in-time snapshot of the MCP inventory installed for this thread.
|
||||
pub async fn mcp_server_status_snapshot(
|
||||
&self,
|
||||
config: &crate::config::Config,
|
||||
|
||||
@@ -444,8 +444,9 @@ impl Session {
|
||||
}
|
||||
|
||||
let config = self.get_config().await;
|
||||
let mcp_servers = self.runtime_mcp_servers(config.as_ref()).await;
|
||||
let refresh_config = McpServerRefreshConfig {
|
||||
mcp_servers: serde_json::to_value(config.mcp_servers.get())?,
|
||||
mcp_servers: serde_json::to_value(mcp_servers)?,
|
||||
mcp_oauth_credentials_store_mode: serde_json::to_value(
|
||||
config.mcp_oauth_credentials_store_mode,
|
||||
)?,
|
||||
|
||||
@@ -6,6 +6,10 @@ use std::time::Duration;
|
||||
use codex_config::DEFAULT_MCP_SERVER_ENVIRONMENT_ID;
|
||||
use codex_config::types::McpServerConfig;
|
||||
use codex_config::types::McpServerTransportConfig;
|
||||
use codex_mcp::CODEX_APPS_MCP_SERVER_NAME;
|
||||
use codex_protocol::mcp::CallToolResult;
|
||||
use core_test_support::apps_test_server::AppsTestServer;
|
||||
use core_test_support::apps_test_server::apps_enabled_builder;
|
||||
use core_test_support::process::process_is_alive;
|
||||
use core_test_support::process::wait_for_pid_file;
|
||||
use core_test_support::process::wait_for_process_exit;
|
||||
@@ -14,6 +18,62 @@ use core_test_support::skip_if_no_network;
|
||||
use core_test_support::stdio_server_bin;
|
||||
use core_test_support::test_codex::test_codex;
|
||||
use core_test_support::wait_for_mcp_server;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn elicitation_capability_refresh_preserves_host_owned_codex_apps() -> anyhow::Result<()> {
|
||||
skip_if_no_network!(Ok(()));
|
||||
|
||||
let server = responses::start_mock_server().await;
|
||||
let apps_server = AppsTestServer::mount(&server).await?;
|
||||
let fixture = apps_enabled_builder(apps_server.chatgpt_base_url)
|
||||
.build(&server)
|
||||
.await?;
|
||||
wait_for_mcp_server(&fixture.codex, CODEX_APPS_MCP_SERVER_NAME).await?;
|
||||
|
||||
responses::mount_sse_once(
|
||||
&server,
|
||||
responses::sse(vec![
|
||||
responses::ev_response_created("resp-1"),
|
||||
responses::ev_assistant_message("msg-1", "done"),
|
||||
responses::ev_completed("resp-1"),
|
||||
]),
|
||||
)
|
||||
.await;
|
||||
fixture
|
||||
.codex
|
||||
.set_openai_form_elicitation_support(/*supported*/ true)
|
||||
.await?;
|
||||
fixture.submit_turn("refresh MCP servers").await?;
|
||||
|
||||
let result = fixture
|
||||
.codex
|
||||
.call_mcp_tool(
|
||||
CODEX_APPS_MCP_SERVER_NAME,
|
||||
"calendar_create_event",
|
||||
Some(serde_json::json!({
|
||||
"title": "Team sync",
|
||||
"starts_at": "2026-06-24T17:00:00Z",
|
||||
})),
|
||||
/*meta*/ None,
|
||||
)
|
||||
.await?;
|
||||
assert_eq!(
|
||||
result,
|
||||
CallToolResult {
|
||||
content: vec![serde_json::json!({
|
||||
"type": "text",
|
||||
"text": "called calendar_create_event for Team sync at 2026-06-24T17:00:00Z with ",
|
||||
})],
|
||||
structured_content: Some(serde_json::json!({ "_codex_apps": null })),
|
||||
is_error: Some(false),
|
||||
meta: None,
|
||||
}
|
||||
);
|
||||
|
||||
fixture.codex.shutdown_and_wait().await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn refresh_shuts_down_superseded_mcp_stdio_server() -> anyhow::Result<()> {
|
||||
|
||||
Reference in New Issue
Block a user