From de8fab6d7adfcef8b4ce6f02f3b5c8be4092015a Mon Sep 17 00:00:00 2001 From: alishobeiri-oai Date: Sat, 10 Oct 2026 19:34:21 +0000 Subject: [PATCH] Upgrade `rmcp` to `3.5.1` and harden lifecycle tests (#52972) ## What changed - Update the pinned `rmcp` dependency and Cargo/Bazel lockfiles, and migrate MCP handlers and fixtures to `ClientConfig` and `ServerConfig`. - Stop requiring responses for cancelled elicitation requests in cancellation tests while retaining checks for cleanup and continued connection usability. - Handle client disconnects during WebSocket writes in the TUI test server, use an asynchronous HTTP mock for loopback proxy testing, and improve remote executor test diagnostics and the Windows subprocess fixture. - Use `invalid_prompt` for the memory worker failure fixture. ## Testing Add a regression test covering transport EOF during both form elicitation and user verification. Keep the client alive while checking that pending response channels close, the timeout pause clears, and the service exits with `QuitReason::Closed`. GitOrigin-RevId: c0166b83fab3b59d84a5207c6630cb6adcf3b230 --- MODULE.bazel.lock | 4 +- codex-rs/Cargo.lock | 8 +- codex-rs/Cargo.toml | 3 +- .../tests/suite/v2/app_installed.rs | 6 +- .../app-server/tests/suite/v2/app_list.rs | 6 +- .../app-server/tests/suite/v2/executor_mcp.rs | 6 +- .../app-server/tests/suite/v2/mcp_resource.rs | 6 +- .../tests/suite/v2/mcp_server_elicitation.rs | 6 +- .../tests/suite/v2/mcp_server_status.rs | 10 +- .../app-server/tests/suite/v2/mcp_tool.rs | 6 +- .../tests/suite/v2/plugin_install.rs | 6 +- .../tests/suite/v2/user_verification_mcp.rs | 6 +- codex-rs/cli/tests/exec_server.rs | 28 ++--- codex-rs/cli/tests/sandbox_network_proxy.rs | 63 +++-------- .../codex-mcp/src/connection_manager_tests.rs | 10 +- codex-rs/memories/write/src/startup_tests.rs | 2 +- .../rmcp-client/src/bin/rmcp_test_server.rs | 6 +- .../rmcp-client/src/bin/test_stdio_server.rs | 6 +- .../src/bin/test_streamable_http_server.rs | 6 +- .../src/elicitation_client_service.rs | 6 +- .../rmcp-client/src/logging_client_handler.rs | 8 +- codex-rs/rmcp-client/src/tool_input_tests.rs | 4 +- .../user_verification_cancellation_tests.rs | 106 ++++++++++++++---- .../src/user_verification_dispatch_tests.rs | 2 +- .../app/tests/session_lifecycle_requests.rs | 48 +++++--- codex-rs/tui/src/dynamic_tools_mcp.rs | 6 +- 26 files changed, 212 insertions(+), 162 deletions(-) diff --git a/MODULE.bazel.lock b/MODULE.bazel.lock index af33be4d35..5be913b8ed 100644 --- a/MODULE.bazel.lock +++ b/MODULE.bazel.lock @@ -1087,8 +1087,8 @@ "git+https://github.com/microsoft/mxc?rev=6cd3d58f05d3447e67109cfb75e042803b843ca4#6cd3d58f05d3447e67109cfb75e042803b843ca4_process_security_environment_spec": "{\"dependencies\":[{\"name\":\"flatbuffers\",\"req\":\"25\"}],\"features\":{},\"strip_prefix\":\"core/generated/process_security_environment_specification\"}", "git+https://github.com/microsoft/mxc?rev=6cd3d58f05d3447e67109cfb75e042803b843ca4#6cd3d58f05d3447e67109cfb75e042803b843ca4_sandbox_spec": "{\"dependencies\":[{\"name\":\"flatbuffers\",\"req\":\"25\"}],\"features\":{},\"strip_prefix\":\"core/generated/base_container_specification\"}", "git+https://github.com/microsoft/mxc?rev=6cd3d58f05d3447e67109cfb75e042803b843ca4#6cd3d58f05d3447e67109cfb75e042803b843ca4_wxc_common": "{\"dependencies\":[{\"name\":\"base64\",\"req\":\"0.22\"},{\"name\":\"cidr\",\"req\":\"0.3.2\"},{\"name\":\"getrandom\",\"req\":\"0.2\"},{\"default_features\":true,\"features\":[],\"name\":\"mxc_config_contract\",\"optional\":false},{\"default_features\":true,\"features\":[],\"name\":\"mxc_schema_support\",\"optional\":true},{\"default_features\":true,\"features\":[],\"name\":\"mxc_telemetry\",\"optional\":false},{\"default_features\":true,\"features\":[],\"name\":\"nanvix_common\",\"optional\":true},{\"name\":\"schemars\",\"optional\":true,\"req\":\"0.8\"},{\"name\":\"semver\",\"req\":\"1\"},{\"default_features\":true,\"features\":[\"derive\"],\"name\":\"serde\",\"optional\":false,\"req\":\"1\"},{\"features\":[\"raw_value\"],\"name\":\"serde_json\",\"req\":\"1\"},{\"name\":\"serde_path_to_error\",\"req\":\"0.1\"},{\"name\":\"thiserror\",\"req\":\"2\"},{\"name\":\"unicode-general-category\",\"req\":\"1\"},{\"name\":\"url\",\"req\":\"2\"},{\"default_features\":true,\"features\":[\"v4\"],\"name\":\"uuid\",\"optional\":true,\"req\":\"1\"},{\"name\":\"widestring\",\"req\":\"1\",\"target\":\"cfg(target_os = \\\"windows\\\")\"},{\"default_features\":true,\"features\":[\"Win32_Foundation\",\"Win32_Globalization\",\"Win32_NetworkManagement_WindowsFirewall\",\"Win32_Networking_WinSock\",\"Win32_Security\",\"Win32_Security_Authorization\",\"Win32_Security_Credentials\",\"Win32_Security_Isolation\",\"Win32_Storage_FileSystem\",\"Win32_System_Com\",\"Win32_System_Console\",\"Win32_System_Diagnostics_Debug\",\"Win32_System_Diagnostics_Etw\",\"Win32_System_Diagnostics_ToolHelp\",\"Win32_System_Environment\",\"Win32_System_IO\",\"Win32_System_JobObjects\",\"Win32_System_LibraryLoader\",\"Win32_System_Memory\",\"Win32_System_Ole\",\"Win32_System_Pipes\",\"Win32_System_ProcessStatus\",\"Win32_System_Registry\",\"Win32_System_SystemInformation\",\"Win32_System_SystemServices\",\"Win32_System_Threading\",\"Win32_System_Time\",\"Win32_System_Variant\",\"Win32_System_WindowsProgramming\",\"Win32_UI_Shell\"],\"name\":\"windows\",\"optional\":false,\"req\":\"0.62\",\"target\":\"cfg(target_os = \\\"windows\\\")\"},{\"name\":\"windows-core\",\"req\":\"0.62\",\"target\":\"cfg(target_os = \\\"windows\\\")\"},{\"name\":\"winreg\",\"req\":\"0.55\",\"target\":\"cfg(target_os = \\\"windows\\\")\"},{\"name\":\"libc\",\"req\":\"0.2\",\"target\":\"cfg(unix)\"}],\"features\":{\"microvm\":[\"dep:nanvix_common\",\"dep:uuid\"],\"schema-gen\":[\"dep:mxc_schema_support\",\"dep:schemars\"]},\"strip_prefix\":\"core/wxc_common\"}", - "git+https://github.com/modelcontextprotocol/rust-sdk?rev=3e636cab26c013eca5131103c03d20237f12c4df#3e636cab26c013eca5131103c03d20237f12c4df_rmcp": "{\"dependencies\":[{\"default_features\":true,\"features\":[],\"name\":\"async-trait\",\"optional\":true,\"req\":\"0.1.89\"},{\"default_features\":true,\"features\":[],\"name\":\"base64\",\"optional\":true,\"req\":\"0.23\"},{\"default_features\":true,\"features\":[],\"name\":\"bytes\",\"optional\":true,\"req\":\"1\"},{\"name\":\"futures\",\"req\":\"0.3\"},{\"default_features\":true,\"features\":[],\"name\":\"hmac\",\"optional\":true,\"req\":\"0.13\"},{\"default_features\":true,\"features\":[],\"name\":\"http\",\"optional\":true,\"req\":\"1\"},{\"default_features\":true,\"features\":[],\"name\":\"http-body\",\"optional\":true,\"req\":\"1\"},{\"default_features\":true,\"features\":[],\"name\":\"http-body-util\",\"optional\":true,\"req\":\"0.1\"},{\"default_features\":true,\"features\":[\"client\",\"http1\"],\"name\":\"hyper\",\"optional\":true,\"req\":\"1\"},{\"default_features\":true,\"features\":[\"tokio\"],\"name\":\"hyper-util\",\"optional\":true,\"req\":\"0.1\"},{\"default_features\":true,\"features\":[\"serde\"],\"name\":\"indexmap\",\"optional\":false,\"req\":\"2\"},{\"default_features\":true,\"features\":[\"aws_lc_rs\"],\"name\":\"jsonwebtoken\",\"optional\":true,\"req\":\"11\"},{\"default_features\":false,\"features\":[],\"name\":\"oauth2\",\"optional\":true,\"req\":\"5.0\"},{\"default_features\":true,\"features\":[],\"name\":\"pastey\",\"optional\":true,\"req\":\"0.2.0\"},{\"name\":\"pin-project-lite\",\"req\":\"0.2\"},{\"default_features\":true,\"features\":[\"tokio1\"],\"name\":\"process-wrap\",\"optional\":true,\"req\":\"10.0\"},{\"default_features\":true,\"features\":[],\"name\":\"rand\",\"optional\":true,\"req\":\"0.10\"},{\"default_features\":false,\"features\":[\"json\",\"stream\"],\"name\":\"reqwest\",\"optional\":true,\"req\":\"0.13.2\"},{\"default_features\":true,\"features\":[],\"name\":\"rmcp-macros\",\"optional\":true,\"req\":\"3.3.0\"},{\"default_features\":true,\"features\":[\"chrono04\"],\"name\":\"schemars\",\"optional\":true,\"req\":\"1.0\"},{\"default_features\":true,\"features\":[\"derive\",\"rc\"],\"name\":\"serde\",\"optional\":false,\"req\":\"1.0\"},{\"name\":\"serde_json\",\"req\":\"1.0\"},{\"default_features\":true,\"features\":[],\"name\":\"sha2\",\"optional\":true,\"req\":\"0.11\"},{\"default_features\":true,\"features\":[],\"name\":\"sse-stream\",\"optional\":true,\"req\":\"0.2.4\"},{\"name\":\"thiserror\",\"req\":\"2\"},{\"default_features\":true,\"features\":[\"sync\",\"macros\",\"rt\",\"time\"],\"name\":\"tokio\",\"optional\":false,\"req\":\"1\"},{\"default_features\":true,\"features\":[],\"name\":\"tokio-stream\",\"optional\":true,\"req\":\"0.1\"},{\"default_features\":true,\"features\":[],\"name\":\"tokio-util\",\"optional\":false,\"req\":\"0.7\"},{\"default_features\":true,\"features\":[],\"name\":\"tower-service\",\"optional\":true,\"req\":\"0.3\"},{\"default_features\":true,\"features\":[],\"name\":\"tracing\",\"optional\":false,\"req\":\"0.1\"},{\"default_features\":true,\"features\":[],\"name\":\"url\",\"optional\":true,\"req\":\"2.4\"},{\"default_features\":true,\"features\":[\"v4\"],\"name\":\"uuid\",\"optional\":true,\"req\":\"1\"},{\"default_features\":true,\"features\":[],\"name\":\"which\",\"optional\":true,\"req\":\"8\"},{\"default_features\":true,\"features\":[],\"name\":\"zeroize\",\"optional\":true,\"req\":\"1\"},{\"default_features\":false,\"features\":[\"serde\",\"clock\",\"std\",\"oldtime\"],\"name\":\"chrono\",\"optional\":false,\"req\":\"0.4.38\",\"target\":\"cfg(all(target_family = \\\"wasm\\\", target_os = \\\"unknown\\\"))\"},{\"default_features\":false,\"features\":[\"serde\",\"now\"],\"name\":\"chrono\",\"optional\":false,\"req\":\"0.4.38\",\"target\":\"cfg(not(all(target_family = \\\"wasm\\\", target_os = \\\"unknown\\\")))\"}],\"features\":{\"__reqwest\":[\"dep:reqwest\"],\"auth\":[\"dep:async-trait\",\"dep:oauth2\",\"__reqwest\",\"dep:url\"],\"auth-client-credentials-jwt\":[\"auth\",\"dep:jsonwebtoken\",\"uuid\"],\"auth-enterprise-managed\":[\"auth\",\"base64\"],\"client\":[\"dep:tokio-stream\"],\"client-side-sse\":[\"dep:sse-stream\",\"dep:http\",\"dep:bytes\",\"base64\"],\"default\":[\"base64\",\"macros\",\"server\"],\"elicitation\":[\"dep:url\"],\"local\":[\"rmcp-macros?/local\"],\"macros\":[\"dep:rmcp-macros\",\"dep:pastey\"],\"request-state\":[\"dep:hmac\",\"dep:sha2\",\"dep:zeroize\",\"base64\"],\"reqwest\":[\"__reqwest\",\"reqwest?/rustls\"],\"reqwest-native-tls\":[\"__reqwest\",\"reqwest?/native-tls\"],\"reqwest-tls-no-provider\":[\"__reqwest\",\"reqwest?/rustls-no-provider\"],\"schemars\":[\"dep:schemars\"],\"server\":[\"transport-async-rw\",\"schemars\",\"dep:pastey\",\"uuid\"],\"server-side-http\":[\"uuid\",\"dep:rand\",\"dep:tokio-stream\",\"dep:http\",\"dep:http-body\",\"dep:http-body-util\",\"dep:bytes\",\"dep:sse-stream\",\"tower\",\"base64\"],\"tower\":[\"dep:tower-service\"],\"transport-async-rw\":[\"tokio/io-util\",\"tokio-util/codec\"],\"transport-child-process\":[\"transport-async-rw\",\"tokio/process\",\"dep:process-wrap\"],\"transport-io\":[\"transport-async-rw\",\"tokio/io-std\"],\"transport-streamable-http-client\":[\"client-side-sse\",\"transport-worker\"],\"transport-streamable-http-client-reqwest\":[\"transport-streamable-http-client\",\"__reqwest\"],\"transport-streamable-http-client-unix-socket\":[\"transport-streamable-http-client\",\"dep:hyper\",\"dep:hyper-util\",\"dep:http-body-util\",\"dep:http\",\"dep:bytes\",\"tokio/net\"],\"transport-streamable-http-server\":[\"transport-streamable-http-server-session\",\"server-side-http\",\"transport-worker\"],\"transport-streamable-http-server-session\":[\"transport-async-rw\",\"dep:async-trait\",\"dep:tokio-stream\"],\"transport-worker\":[\"dep:tokio-stream\"],\"which-command\":[\"transport-child-process\",\"dep:which\"]},\"strip_prefix\":\"./crates/rmcp\"}", - "git+https://github.com/modelcontextprotocol/rust-sdk?rev=3e636cab26c013eca5131103c03d20237f12c4df#3e636cab26c013eca5131103c03d20237f12c4df_rmcp-macros": "{\"dependencies\":[{\"default_features\":true,\"features\":[],\"name\":\"darling\",\"optional\":false,\"req\":\"0.24\"},{\"name\":\"proc-macro2\",\"req\":\"1\"},{\"name\":\"quote\",\"req\":\"1\"},{\"name\":\"serde_json\",\"req\":\"1.0\"},{\"default_features\":true,\"features\":[\"full\"],\"name\":\"syn\",\"optional\":false,\"req\":\"3\"}],\"features\":{\"local\":[]},\"strip_prefix\":\"./crates/rmcp-macros\"}", + "git+https://github.com/modelcontextprotocol/rust-sdk?rev=4048ac40e958fb1a7d614617a2ae8d26666ee2fa#4048ac40e958fb1a7d614617a2ae8d26666ee2fa_rmcp": "{\"dependencies\":[{\"default_features\":true,\"features\":[],\"name\":\"async-trait\",\"optional\":true,\"req\":\"0.1.89\"},{\"default_features\":true,\"features\":[],\"name\":\"base64\",\"optional\":true,\"req\":\"0.23\"},{\"default_features\":true,\"features\":[],\"name\":\"bytes\",\"optional\":true,\"req\":\"1\"},{\"name\":\"futures\",\"req\":\"0.3\"},{\"default_features\":true,\"features\":[],\"name\":\"hmac\",\"optional\":true,\"req\":\"0.13\"},{\"default_features\":true,\"features\":[],\"name\":\"http\",\"optional\":true,\"req\":\"1\"},{\"default_features\":true,\"features\":[],\"name\":\"http-body\",\"optional\":true,\"req\":\"1\"},{\"default_features\":true,\"features\":[],\"name\":\"http-body-util\",\"optional\":true,\"req\":\"0.1\"},{\"default_features\":true,\"features\":[\"client\",\"http1\"],\"name\":\"hyper\",\"optional\":true,\"req\":\"1\"},{\"default_features\":true,\"features\":[\"tokio\"],\"name\":\"hyper-util\",\"optional\":true,\"req\":\"0.1\"},{\"default_features\":true,\"features\":[\"serde\"],\"name\":\"indexmap\",\"optional\":false,\"req\":\"2\"},{\"default_features\":true,\"features\":[\"aws_lc_rs\"],\"name\":\"jsonwebtoken\",\"optional\":true,\"req\":\"11\"},{\"default_features\":false,\"features\":[],\"name\":\"oauth2\",\"optional\":true,\"req\":\"5.0\"},{\"default_features\":true,\"features\":[],\"name\":\"pastey\",\"optional\":true,\"req\":\"0.2.0\"},{\"name\":\"pin-project-lite\",\"req\":\"0.2\"},{\"default_features\":true,\"features\":[\"tokio1\"],\"name\":\"process-wrap\",\"optional\":true,\"req\":\"10.0\"},{\"default_features\":true,\"features\":[],\"name\":\"rand\",\"optional\":true,\"req\":\"0.10\"},{\"default_features\":false,\"features\":[\"json\",\"stream\"],\"name\":\"reqwest\",\"optional\":true,\"req\":\"0.13.2\"},{\"default_features\":true,\"features\":[],\"name\":\"rmcp-macros\",\"optional\":true,\"req\":\"3.5.1\"},{\"default_features\":true,\"features\":[\"chrono04\"],\"name\":\"schemars\",\"optional\":true,\"req\":\"1.0\"},{\"default_features\":true,\"features\":[\"derive\",\"rc\"],\"name\":\"serde\",\"optional\":false,\"req\":\"1.0\"},{\"name\":\"serde_json\",\"req\":\"1.0\"},{\"default_features\":true,\"features\":[],\"name\":\"sha2\",\"optional\":true,\"req\":\"0.11\"},{\"default_features\":true,\"features\":[],\"name\":\"sse-stream\",\"optional\":true,\"req\":\"0.2.4\"},{\"name\":\"thiserror\",\"req\":\"2\"},{\"default_features\":true,\"features\":[\"sync\",\"macros\",\"rt\",\"time\"],\"name\":\"tokio\",\"optional\":false,\"req\":\"1\"},{\"default_features\":true,\"features\":[],\"name\":\"tokio-stream\",\"optional\":true,\"req\":\"0.1\"},{\"default_features\":true,\"features\":[],\"name\":\"tokio-util\",\"optional\":false,\"req\":\"0.7\"},{\"default_features\":true,\"features\":[],\"name\":\"tower-service\",\"optional\":true,\"req\":\"0.3\"},{\"default_features\":true,\"features\":[],\"name\":\"tracing\",\"optional\":false,\"req\":\"0.1\"},{\"default_features\":true,\"features\":[],\"name\":\"url\",\"optional\":true,\"req\":\"2.4\"},{\"default_features\":true,\"features\":[\"v4\"],\"name\":\"uuid\",\"optional\":true,\"req\":\"1\"},{\"default_features\":true,\"features\":[],\"name\":\"which\",\"optional\":true,\"req\":\"8\"},{\"default_features\":true,\"features\":[],\"name\":\"zeroize\",\"optional\":true,\"req\":\"1\"},{\"default_features\":false,\"features\":[\"serde\",\"clock\",\"std\",\"oldtime\"],\"name\":\"chrono\",\"optional\":false,\"req\":\"0.4.38\",\"target\":\"cfg(all(target_family = \\\"wasm\\\", target_os = \\\"unknown\\\"))\"},{\"default_features\":false,\"features\":[\"serde\",\"now\"],\"name\":\"chrono\",\"optional\":false,\"req\":\"0.4.38\",\"target\":\"cfg(not(all(target_family = \\\"wasm\\\", target_os = \\\"unknown\\\")))\"}],\"features\":{\"__reqwest\":[\"dep:reqwest\"],\"auth\":[\"dep:async-trait\",\"dep:oauth2\",\"__reqwest\",\"dep:url\"],\"auth-client-credentials-jwt\":[\"auth\",\"dep:jsonwebtoken\",\"uuid\"],\"auth-enterprise-managed\":[\"auth\",\"base64\"],\"client\":[\"dep:tokio-stream\"],\"client-side-sse\":[\"dep:sse-stream\",\"dep:http\",\"dep:bytes\",\"base64\"],\"default\":[\"base64\",\"macros\",\"server\"],\"elicitation\":[\"dep:url\"],\"local\":[\"rmcp-macros?/local\"],\"macros\":[\"dep:rmcp-macros\",\"dep:pastey\"],\"request-state\":[\"dep:hmac\",\"dep:sha2\",\"dep:zeroize\",\"base64\"],\"reqwest\":[\"__reqwest\",\"reqwest?/rustls\"],\"reqwest-native-tls\":[\"__reqwest\",\"reqwest?/native-tls\"],\"reqwest-tls-no-provider\":[\"__reqwest\",\"reqwest?/rustls-no-provider\"],\"schemars\":[\"dep:schemars\"],\"server\":[\"transport-async-rw\",\"schemars\",\"dep:pastey\",\"uuid\"],\"server-side-http\":[\"uuid\",\"dep:rand\",\"dep:tokio-stream\",\"dep:http\",\"dep:http-body\",\"dep:http-body-util\",\"dep:bytes\",\"dep:sse-stream\",\"tower\",\"base64\"],\"tower\":[\"dep:tower-service\"],\"transport-async-rw\":[\"tokio/io-util\",\"tokio-util/codec\"],\"transport-child-process\":[\"transport-async-rw\",\"tokio/process\",\"dep:process-wrap\"],\"transport-io\":[\"transport-async-rw\",\"tokio/io-std\"],\"transport-streamable-http-client\":[\"client-side-sse\",\"transport-worker\"],\"transport-streamable-http-client-reqwest\":[\"transport-streamable-http-client\",\"__reqwest\"],\"transport-streamable-http-client-unix-socket\":[\"transport-streamable-http-client\",\"dep:hyper\",\"dep:hyper-util\",\"dep:http-body-util\",\"dep:http\",\"dep:bytes\",\"tokio/net\"],\"transport-streamable-http-server\":[\"transport-streamable-http-server-session\",\"server-side-http\",\"transport-worker\"],\"transport-streamable-http-server-session\":[\"transport-async-rw\",\"dep:async-trait\",\"dep:tokio-stream\"],\"transport-worker\":[\"dep:tokio-stream\"],\"which-command\":[\"transport-child-process\",\"dep:which\"]},\"strip_prefix\":\"./crates/rmcp\"}", + "git+https://github.com/modelcontextprotocol/rust-sdk?rev=4048ac40e958fb1a7d614617a2ae8d26666ee2fa#4048ac40e958fb1a7d614617a2ae8d26666ee2fa_rmcp-macros": "{\"dependencies\":[{\"default_features\":true,\"features\":[],\"name\":\"darling\",\"optional\":false,\"req\":\"0.24\"},{\"name\":\"proc-macro2\",\"req\":\"1\"},{\"name\":\"quote\",\"req\":\"1\"},{\"name\":\"serde_json\",\"req\":\"1.0\"},{\"default_features\":true,\"features\":[\"full\"],\"name\":\"syn\",\"optional\":false,\"req\":\"3\"}],\"features\":{\"local\":[]},\"strip_prefix\":\"./crates/rmcp-macros\"}", "git+https://github.com/openai-oss-forks/crossterm?rev=ed1cdab335221515706178d68495bba2aed1924f#ed1cdab335221515706178d68495bba2aed1924f_crossterm": "{\"dependencies\":[{\"default_features\":true,\"features\":[],\"name\":\"base64\",\"optional\":true,\"req\":\"0.23\"},{\"default_features\":true,\"features\":[],\"name\":\"bitflags\",\"optional\":false,\"req\":\"2.9\"},{\"default_features\":true,\"features\":[\"is_variant\"],\"name\":\"derive_more\",\"optional\":true,\"req\":\"2.0.0\"},{\"default_features\":true,\"features\":[],\"name\":\"document-features\",\"optional\":true,\"req\":\"0.2.11\"},{\"default_features\":false,\"features\":[],\"name\":\"futures-core\",\"optional\":true,\"req\":\"0.3.31\"},{\"name\":\"parking_lot\",\"req\":\"0.12.1\"},{\"default_features\":true,\"features\":[\"derive\"],\"name\":\"serde\",\"optional\":true,\"req\":\"1.0.103\"},{\"default_features\":true,\"features\":[],\"name\":\"filedescriptor\",\"optional\":true,\"req\":\"0.8\",\"target\":\"cfg(unix)\"},{\"default_features\":false,\"features\":[],\"name\":\"libc\",\"optional\":true,\"req\":\"0.2.168\",\"target\":\"cfg(unix)\"},{\"default_features\":true,\"features\":[\"os-poll\"],\"name\":\"mio\",\"optional\":true,\"req\":\"1.0.1\",\"target\":\"cfg(unix)\"},{\"default_features\":false,\"features\":[\"std\",\"stdio\",\"termios\"],\"name\":\"rustix\",\"optional\":false,\"req\":\"1\",\"target\":\"cfg(unix)\"},{\"default_features\":true,\"features\":[],\"name\":\"signal-hook\",\"optional\":true,\"req\":\"0.3.17\",\"target\":\"cfg(unix)\"},{\"default_features\":true,\"features\":[\"support-v1_0\"],\"name\":\"signal-hook-mio\",\"optional\":true,\"req\":\"0.2.4\",\"target\":\"cfg(unix)\"},{\"default_features\":true,\"features\":[],\"name\":\"crossterm_winapi\",\"optional\":true,\"req\":\"0.9.1\",\"target\":\"cfg(windows)\"},{\"default_features\":true,\"features\":[\"winuser\",\"winerror\"],\"name\":\"winapi\",\"optional\":true,\"req\":\"0.3.9\",\"target\":\"cfg(windows)\"}],\"features\":{\"bracketed-paste\":[],\"default\":[\"bracketed-paste\",\"derive-more\",\"events\",\"windows\"],\"derive-more\":[\"dep:derive_more\"],\"document-features\":[\"dep:document-features\"],\"event-stream\":[\"dep:futures-core\",\"events\"],\"events\":[\"dep:mio\",\"dep:signal-hook\",\"dep:signal-hook-mio\"],\"osc52\":[\"dep:base64\"],\"serde\":[\"dep:serde\",\"bitflags/serde\"],\"use-dev-tty\":[\"filedescriptor\",\"rustix/process\"],\"windows\":[\"dep:winapi\",\"dep:crossterm_winapi\"]},\"strip_prefix\":\"\"}", "git+https://github.com/openai-oss-forks/tokio-tungstenite?rev=0e5b2d73aa18dd9f0a50ee9ff199d5aef7594186#0e5b2d73aa18dd9f0a50ee9ff199d5aef7594186_tokio-tungstenite": "{\"dependencies\":[{\"default_features\":false,\"features\":[\"sink\",\"std\"],\"name\":\"futures-util\",\"optional\":false,\"req\":\"0.3.28\"},{\"name\":\"log\",\"req\":\"0.4.17\"},{\"default_features\":true,\"features\":[],\"name\":\"native-tls-crate\",\"optional\":true,\"package\":\"native-tls\",\"req\":\"0.2.11\"},{\"default_features\":false,\"features\":[],\"name\":\"rustls\",\"optional\":true,\"req\":\"0.23.0\"},{\"default_features\":true,\"features\":[],\"name\":\"rustls-native-certs\",\"optional\":true,\"req\":\"0.8.0\"},{\"default_features\":true,\"features\":[],\"name\":\"rustls-pki-types\",\"optional\":true,\"req\":\"1.0\"},{\"default_features\":false,\"features\":[\"io-util\"],\"name\":\"tokio\",\"optional\":false,\"req\":\"1.0.0\"},{\"default_features\":true,\"features\":[],\"name\":\"tokio-native-tls\",\"optional\":true,\"req\":\"0.3.1\"},{\"default_features\":false,\"features\":[],\"name\":\"tokio-rustls\",\"optional\":true,\"req\":\"0.26.0\"},{\"default_features\":false,\"features\":[],\"name\":\"tungstenite\",\"optional\":false},{\"default_features\":true,\"features\":[],\"name\":\"webpki-roots\",\"optional\":true,\"req\":\"0.26.0\"}],\"features\":{\"__rustls-tls\":[\"rustls\",\"rustls-pki-types\",\"tokio-rustls\",\"stream\",\"tungstenite/__rustls-tls\",\"handshake\"],\"connect\":[\"stream\",\"tokio/net\",\"tokio/time\",\"handshake\"],\"default\":[\"connect\",\"handshake\"],\"handshake\":[\"tungstenite/handshake\"],\"native-tls\":[\"native-tls-crate\",\"tokio-native-tls\",\"stream\",\"tungstenite/native-tls\",\"handshake\"],\"native-tls-vendored\":[\"native-tls\",\"native-tls-crate/vendored\",\"tungstenite/native-tls-vendored\"],\"proxy\":[\"tungstenite/proxy\",\"tokio/net\",\"handshake\"],\"rustls-tls-native-roots\":[\"__rustls-tls\",\"rustls-native-certs\"],\"rustls-tls-webpki-roots\":[\"__rustls-tls\",\"webpki-roots\"],\"stream\":[],\"url\":[\"tungstenite/url\"]},\"strip_prefix\":\"\"}", "git+https://github.com/openai-oss-forks/tungstenite-rs?rev=4fffad30fe373adbdcffab9545e9e9bf4f2fc19f#4fffad30fe373adbdcffab9545e9e9bf4f2fc19f_tungstenite": "{\"dependencies\":[{\"name\":\"bytes\",\"req\":\"1.9.0\"},{\"default_features\":true,\"features\":[],\"name\":\"data-encoding\",\"optional\":true,\"req\":\"2\"},{\"default_features\":false,\"features\":[\"zlib-rs\"],\"name\":\"flate2\",\"optional\":true,\"req\":\"1.1.0\"},{\"default_features\":true,\"features\":[],\"name\":\"headers\",\"optional\":true,\"req\":\"0.4.0\"},{\"default_features\":true,\"features\":[],\"name\":\"http\",\"optional\":true,\"req\":\"1.0\"},{\"default_features\":true,\"features\":[],\"name\":\"httparse\",\"optional\":true,\"req\":\"1.3.4\"},{\"name\":\"log\",\"req\":\"0.4.8\"},{\"default_features\":true,\"features\":[],\"name\":\"native-tls-crate\",\"optional\":true,\"package\":\"native-tls\",\"req\":\"0.2.3\"},{\"name\":\"rand\",\"req\":\"0.9.0\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"rustls\",\"optional\":true,\"req\":\"0.23.0\"},{\"default_features\":true,\"features\":[],\"name\":\"rustls-native-certs\",\"optional\":true,\"req\":\"0.8.0\"},{\"default_features\":true,\"features\":[],\"name\":\"rustls-pki-types\",\"optional\":true,\"req\":\"1.0\"},{\"default_features\":true,\"features\":[],\"name\":\"sha1\",\"optional\":true,\"req\":\"0.10\"},{\"name\":\"thiserror\",\"req\":\"2.0.7\"},{\"default_features\":true,\"features\":[],\"name\":\"url\",\"optional\":true,\"req\":\"2.1.0\"},{\"name\":\"utf-8\",\"req\":\"0.7.5\"},{\"default_features\":true,\"features\":[],\"name\":\"webpki-roots\",\"optional\":true,\"req\":\"0.26\"}],\"features\":{\"__rustls-tls\":[\"rustls\",\"rustls-pki-types\"],\"default\":[\"handshake\"],\"deflate\":[\"headers\",\"flate2\"],\"handshake\":[\"data-encoding\",\"headers\",\"httparse\",\"sha1\"],\"headers\":[\"http\",\"dep:headers\"],\"native-tls\":[\"native-tls-crate\"],\"native-tls-vendored\":[\"native-tls\",\"native-tls-crate/vendored\"],\"proxy\":[\"handshake\"],\"rustls-tls-native-roots\":[\"__rustls-tls\",\"rustls-native-certs\"],\"rustls-tls-webpki-roots\":[\"__rustls-tls\",\"webpki-roots\"],\"url\":[\"dep:url\"]},\"strip_prefix\":\"\"}", diff --git a/codex-rs/Cargo.lock b/codex-rs/Cargo.lock index 10c3e869ae..945d4c0b42 100644 --- a/codex-rs/Cargo.lock +++ b/codex-rs/Cargo.lock @@ -13098,8 +13098,8 @@ dependencies = [ [[package]] name = "rmcp" -version = "3.3.0" -source = "git+https://github.com/modelcontextprotocol/rust-sdk?rev=3e636cab26c013eca5131103c03d20237f12c4df#3e636cab26c013eca5131103c03d20237f12c4df" +version = "3.5.1" +source = "git+https://github.com/modelcontextprotocol/rust-sdk?rev=4048ac40e958fb1a7d614617a2ae8d26666ee2fa#4048ac40e958fb1a7d614617a2ae8d26666ee2fa" dependencies = [ "async-trait", "base64 0.23.0", @@ -13133,8 +13133,8 @@ dependencies = [ [[package]] name = "rmcp-macros" -version = "3.3.0" -source = "git+https://github.com/modelcontextprotocol/rust-sdk?rev=3e636cab26c013eca5131103c03d20237f12c4df#3e636cab26c013eca5131103c03d20237f12c4df" +version = "3.5.1" +source = "git+https://github.com/modelcontextprotocol/rust-sdk?rev=4048ac40e958fb1a7d614617a2ae8d26666ee2fa#4048ac40e958fb1a7d614617a2ae8d26666ee2fa" dependencies = [ "darling 0.24.0", "proc-macro2", diff --git a/codex-rs/Cargo.toml b/codex-rs/Cargo.toml index 1dcbaf88c0..8fffe579a7 100644 --- a/codex-rs/Cargo.toml +++ b/codex-rs/Cargo.toml @@ -447,8 +447,7 @@ regex-automata = { version = "0.4.13", default-features = false, features = ["st regex-lite = "0.1.8" regex-syntax = "0.8.8" reqwest = { version = "0.12", features = ["cookies"] } -# rmcp-v3.3.0 includes enterprise authorization helpers (rust-sdk#1234). -rmcp = { git = "https://github.com/modelcontextprotocol/rust-sdk", rev = "3e636cab26c013eca5131103c03d20237f12c4df", version = "=3.3.0", default-features = false } +rmcp = { git = "https://github.com/modelcontextprotocol/rust-sdk", rev = "4048ac40e958fb1a7d614617a2ae8d26666ee2fa", version = "=3.5.1", default-features = false } runfiles = { git = "https://github.com/dzbarsky/rules_rust", rev = "b56cbaa8465e74127f1ea216f813cd377295ad81" } rustix = { version = "1.1.4", features = ["net"] } rustls = { version = "0.23.45", default-features = false, features = [ diff --git a/codex-rs/app-server/tests/suite/v2/app_installed.rs b/codex-rs/app-server/tests/suite/v2/app_installed.rs index 90c32b81d2..2f6ba0450f 100644 --- a/codex-rs/app-server/tests/suite/v2/app_installed.rs +++ b/codex-rs/app-server/tests/suite/v2/app_installed.rs @@ -36,7 +36,7 @@ use rmcp::model::CallToolResult; use rmcp::model::ContentBlock; use rmcp::model::ListToolsResult; use rmcp::model::ServerCapabilities; -use rmcp::model::ServerInfo; +use rmcp::model::ServerConfig; use rmcp::model::Tool; use rmcp::transport::StreamableHttpServerConfig; use rmcp::transport::StreamableHttpService; @@ -461,8 +461,8 @@ struct InstalledAppsMcpServer { } impl ServerHandler for InstalledAppsMcpServer { - fn get_info(&self) -> ServerInfo { - ServerInfo::new(ServerCapabilities::builder().enable_tools().build()) + fn get_info(&self) -> ServerConfig { + ServerConfig::new(ServerCapabilities::builder().enable_tools().build()) } async fn call_tool( diff --git a/codex-rs/app-server/tests/suite/v2/app_list.rs b/codex-rs/app-server/tests/suite/v2/app_list.rs index 84cdd36c04..5805f02ae7 100644 --- a/codex-rs/app-server/tests/suite/v2/app_list.rs +++ b/codex-rs/app-server/tests/suite/v2/app_list.rs @@ -43,7 +43,7 @@ use rmcp::model::JsonObject; use rmcp::model::ListToolsResult; use rmcp::model::MetaObject; use rmcp::model::ServerCapabilities; -use rmcp::model::ServerInfo; +use rmcp::model::ServerConfig; use rmcp::model::Tool; use rmcp::model::ToolAnnotations; use rmcp::transport::StreamableHttpServerConfig; @@ -1497,8 +1497,8 @@ impl AppsServerControl { } impl ServerHandler for AppListMcpServer { - fn get_info(&self) -> ServerInfo { - ServerInfo::new(ServerCapabilities::builder().enable_tools().build()) + fn get_info(&self) -> ServerConfig { + ServerConfig::new(ServerCapabilities::builder().enable_tools().build()) } fn list_tools( diff --git a/codex-rs/app-server/tests/suite/v2/executor_mcp.rs b/codex-rs/app-server/tests/suite/v2/executor_mcp.rs index ce5e1e314b..f2c5da814e 100644 --- a/codex-rs/app-server/tests/suite/v2/executor_mcp.rs +++ b/codex-rs/app-server/tests/suite/v2/executor_mcp.rs @@ -37,7 +37,7 @@ use rmcp::model::CallToolResult; use rmcp::model::JsonObject; use rmcp::model::ListToolsResult; use rmcp::model::ServerCapabilities; -use rmcp::model::ServerInfo; +use rmcp::model::ServerConfig; use rmcp::model::Tool; use rmcp::model::ToolAnnotations; use rmcp::service::RequestContext; @@ -1507,8 +1507,8 @@ startup_timeout_sec = 10 struct ExecutorHttpMcpServer; impl ServerHandler for ExecutorHttpMcpServer { - fn get_info(&self) -> ServerInfo { - ServerInfo::new(ServerCapabilities::builder().enable_tools().build()) + fn get_info(&self) -> ServerConfig { + ServerConfig::new(ServerCapabilities::builder().enable_tools().build()) } async fn list_tools( diff --git a/codex-rs/app-server/tests/suite/v2/mcp_resource.rs b/codex-rs/app-server/tests/suite/v2/mcp_resource.rs index b25a2098fd..cb5f8760dc 100644 --- a/codex-rs/app-server/tests/suite/v2/mcp_resource.rs +++ b/codex-rs/app-server/tests/suite/v2/mcp_resource.rs @@ -105,7 +105,7 @@ use rmcp::model::ReadResourceResult; use rmcp::model::Resource; use rmcp::model::ResourceContents; use rmcp::model::ServerCapabilities; -use rmcp::model::ServerInfo; +use rmcp::model::ServerConfig; use rmcp::service::RequestContext; use rmcp::service::RoleServer; use rmcp::transport::StreamableHttpServerConfig; @@ -1376,8 +1376,8 @@ struct ResourceAppsMcpServer { } impl ServerHandler for ResourceAppsMcpServer { - fn get_info(&self) -> ServerInfo { - let mut info = ServerInfo::new(ServerCapabilities::builder().enable_resources().build()) + fn get_info(&self) -> ServerConfig { + let mut info = ServerConfig::new(ServerCapabilities::builder().enable_resources().build()) .with_protocol_version(ProtocolVersion::V_2025_06_18); if self.calls.tools_enabled.load(Ordering::Relaxed) { info.capabilities.tools = Some(Default::default()); diff --git a/codex-rs/app-server/tests/suite/v2/mcp_server_elicitation.rs b/codex-rs/app-server/tests/suite/v2/mcp_server_elicitation.rs index def85ba0a4..cda1f49377 100644 --- a/codex-rs/app-server/tests/suite/v2/mcp_server_elicitation.rs +++ b/codex-rs/app-server/tests/suite/v2/mcp_server_elicitation.rs @@ -64,7 +64,7 @@ use rmcp::model::MetaObject; use rmcp::model::PrimitiveSchemaDefinition; use rmcp::model::RequestMetaObject; use rmcp::model::ServerCapabilities; -use rmcp::model::ServerInfo; +use rmcp::model::ServerConfig; use rmcp::model::ServerRequest as McpServerRequest; use rmcp::model::Tool; use rmcp::model::ToolAnnotations; @@ -1048,8 +1048,8 @@ impl ServerHandler for ElicitationAppsMcpServer { Ok(self.get_info()) } - fn get_info(&self) -> ServerInfo { - ServerInfo::new(ServerCapabilities::builder().enable_tools().build()) + fn get_info(&self) -> ServerConfig { + ServerConfig::new(ServerCapabilities::builder().enable_tools().build()) .with_protocol_version(rmcp::model::ProtocolVersion::V_2025_06_18) } diff --git a/codex-rs/app-server/tests/suite/v2/mcp_server_status.rs b/codex-rs/app-server/tests/suite/v2/mcp_server_status.rs index 09daa053f3..c7dc29539e 100644 --- a/codex-rs/app-server/tests/suite/v2/mcp_server_status.rs +++ b/codex-rs/app-server/tests/suite/v2/mcp_server_status.rs @@ -47,7 +47,7 @@ use rmcp::model::ListResourcesResult; use rmcp::model::ListToolsResult; use rmcp::model::PaginatedRequestParams; use rmcp::model::ServerCapabilities; -use rmcp::model::ServerInfo; +use rmcp::model::ServerConfig; use rmcp::model::Tool; use rmcp::model::ToolAnnotations; use rmcp::service::RequestContext; @@ -1229,7 +1229,7 @@ struct McpStatusServer { } impl ServerHandler for McpStatusServer { - fn get_info(&self) -> ServerInfo { + fn get_info(&self) -> ServerConfig { let mut capabilities = ServerCapabilities::builder().enable_tools().build(); capabilities.extensions = Some(BTreeMap::from([( "openai/settings".to_string(), @@ -1238,7 +1238,7 @@ impl ServerHandler for McpStatusServer { ) .expect("settings capability is a JSON object"), )])); - ServerInfo::new(capabilities).with_server_info( + ServerConfig::new(capabilities).with_server_info( Implementation::new("lookup-server", "1.0.0").with_title("Lookup Server"), ) } @@ -1274,8 +1274,8 @@ struct SlowInventoryServer { } impl ServerHandler for SlowInventoryServer { - fn get_info(&self) -> ServerInfo { - ServerInfo::new( + fn get_info(&self) -> ServerConfig { + ServerConfig::new( ServerCapabilities::builder() .enable_tools() .enable_resources() diff --git a/codex-rs/app-server/tests/suite/v2/mcp_tool.rs b/codex-rs/app-server/tests/suite/v2/mcp_tool.rs index f346144a71..f95a709820 100644 --- a/codex-rs/app-server/tests/suite/v2/mcp_tool.rs +++ b/codex-rs/app-server/tests/suite/v2/mcp_tool.rs @@ -66,7 +66,7 @@ use rmcp::model::PrimitiveSchemaDefinition; use rmcp::model::ProtocolVersion; use rmcp::model::RequestMetaObject; use rmcp::model::ServerCapabilities; -use rmcp::model::ServerInfo; +use rmcp::model::ServerConfig; use rmcp::model::Tool; use rmcp::model::ToolAnnotations; use rmcp::service::RequestContext; @@ -1285,8 +1285,8 @@ impl ServerHandler for ToolAppsMcpServer { Ok(self.get_info()) } - fn get_info(&self) -> ServerInfo { - ServerInfo::new(ServerCapabilities::builder().enable_tools().build()) + fn get_info(&self) -> ServerConfig { + ServerConfig::new(ServerCapabilities::builder().enable_tools().build()) } async fn list_tools( diff --git a/codex-rs/app-server/tests/suite/v2/plugin_install.rs b/codex-rs/app-server/tests/suite/v2/plugin_install.rs index 3d48cb3f4c..6c30dc727d 100644 --- a/codex-rs/app-server/tests/suite/v2/plugin_install.rs +++ b/codex-rs/app-server/tests/suite/v2/plugin_install.rs @@ -53,7 +53,7 @@ use rmcp::model::JsonObject; use rmcp::model::ListToolsResult; use rmcp::model::MetaObject; use rmcp::model::ServerCapabilities; -use rmcp::model::ServerInfo; +use rmcp::model::ServerConfig; use rmcp::model::Tool; use rmcp::model::ToolAnnotations; use rmcp::transport::StreamableHttpServerConfig; @@ -2632,8 +2632,8 @@ struct PluginInstallMcpServer { } impl ServerHandler for PluginInstallMcpServer { - fn get_info(&self) -> ServerInfo { - ServerInfo::new(ServerCapabilities::builder().enable_tools().build()) + fn get_info(&self) -> ServerConfig { + ServerConfig::new(ServerCapabilities::builder().enable_tools().build()) } fn list_tools( diff --git a/codex-rs/app-server/tests/suite/v2/user_verification_mcp.rs b/codex-rs/app-server/tests/suite/v2/user_verification_mcp.rs index 3206dc2b5a..3b6aec4c2d 100644 --- a/codex-rs/app-server/tests/suite/v2/user_verification_mcp.rs +++ b/codex-rs/app-server/tests/suite/v2/user_verification_mcp.rs @@ -33,7 +33,7 @@ use rmcp::model::CallToolResult; use rmcp::model::ContentBlock; use rmcp::model::CustomRequest; use rmcp::model::ServerCapabilities; -use rmcp::model::ServerInfo; +use rmcp::model::ServerConfig; use rmcp::model::ServerRequest; use rmcp::service::RequestContext; use rmcp::service::RoleServer; @@ -73,8 +73,8 @@ impl McpServerContributor for HostedVerificationServer { } impl ServerHandler for VerificationServer { - fn get_info(&self) -> ServerInfo { - ServerInfo::new(ServerCapabilities::builder().enable_tools().build()) + fn get_info(&self) -> ServerConfig { + ServerConfig::new(ServerCapabilities::builder().enable_tools().build()) } async fn call_tool( diff --git a/codex-rs/cli/tests/exec_server.rs b/codex-rs/cli/tests/exec_server.rs index 8f4f8b742e..36b122a414 100644 --- a/codex-rs/cli/tests/exec_server.rs +++ b/codex-rs/cli/tests/exec_server.rs @@ -281,7 +281,7 @@ metrics_exporter = {{ otlp-http = {{ endpoint = "{collector_url}/v1/metrics", pr "2", ]) .stdin(Stdio::piped()) - .stderr(Stdio::piped()) + .stderr(Stdio::inherit()) .kill_on_drop(true); let mut child = command.spawn()?; @@ -290,7 +290,9 @@ metrics_exporter = {{ otlp-http = {{ endpoint = "{collector_url}/v1/metrics", pr .take() .ok_or_else(|| anyhow::anyhow!("remote exec-server stdin was not piped"))?; - let environment_websocket = accept_parent_lifetime_websocket(&listener, TEST_TIMEOUT).await?; + let environment_websocket = accept_parent_lifetime_websocket(&listener, TEST_TIMEOUT) + .await + .context("remote executor failed to attach")?; // Remote startup must capture the version before registration, not on the first initialize. std::fs::write(&manifest, r#"{"version":"9.9.9"}"#)?; let executor_public_key = registered_parent_lifetime_executor_public_key(®istry).await?; @@ -311,7 +313,9 @@ metrics_exporter = {{ otlp-http = {{ endpoint = "{collector_url}/v1/metrics", pr }; let client_task = tokio::spawn(async move { ExecServerClient::connect_noise_rendezvous(harness_args).await }); - let harness_websocket = accept_parent_lifetime_websocket(&listener, TEST_TIMEOUT).await?; + let harness_websocket = accept_parent_lifetime_websocket(&listener, TEST_TIMEOUT) + .await + .context("harness failed to attach")?; let relay_task = tokio::spawn(proxy_parent_lifetime_relay( environment_websocket, harness_websocket, @@ -342,9 +346,12 @@ metrics_exporter = {{ otlp-http = {{ endpoint = "{collector_url}/v1/metrics", pr #[cfg(windows)] let argv = vec![ - "cmd.exe", - "/C", - "if defined CODEX_EXEC_SERVER_EXIT_ON_STDIN_CLOSE (exit /b 1) else ping -n 61 127.0.0.1", + "powershell.exe", + "-NoLogo", + "-NoProfile", + "-NonInteractive", + "-Command", + "if (Test-Path Env:CODEX_EXEC_SERVER_EXIT_ON_STDIN_CLOSE) { exit 1 }; Start-Sleep -Seconds 60", ]; #[cfg(not(windows))] let argv = vec![ @@ -385,15 +392,10 @@ metrics_exporter = {{ otlp-http = {{ endpoint = "{collector_url}/v1/metrics", pr drop(stdin); - let output = tokio::time::timeout(TEST_TIMEOUT, child.wait_with_output()) + let status = tokio::time::timeout(TEST_TIMEOUT, child.wait()) .await .map_err(|_| anyhow::anyhow!("remote exec-server did not exit after stdin closed"))??; - anyhow::ensure!( - output.status.success(), - "remote exec-server exited with {}; stderr: {}", - output.status, - String::from_utf8_lossy(&output.stderr) - ); + anyhow::ensure!(status.success(), "remote exec-server exited with {status}"); relay_task.abort(); let _ = relay_task.await; diff --git a/codex-rs/cli/tests/sandbox_network_proxy.rs b/codex-rs/cli/tests/sandbox_network_proxy.rs index 21747c3919..a21b5b4ee8 100644 --- a/codex-rs/cli/tests/sandbox_network_proxy.rs +++ b/codex-rs/cli/tests/sandbox_network_proxy.rs @@ -1,11 +1,14 @@ #![cfg(target_os = "linux")] use std::net::TcpListener; -use std::time::Duration; -use std::time::Instant; use anyhow::Result; use tempfile::TempDir; +use wiremock::Mock; +use wiremock::MockServer; +use wiremock::ResponseTemplate; +use wiremock::matchers::method; +use wiremock::matchers::path; const BWRAP_UNAVAILABLE_ERR: &str = "bubblewrap is unavailable"; @@ -71,50 +74,17 @@ mode = "full" Ok(()) } -#[test] -fn sandbox_with_network_proxy_allows_explicit_loopback_access() -> Result<()> { +#[tokio::test] +async fn sandbox_with_network_proxy_allows_explicit_loopback_access() -> Result<()> { let codex_home = TempDir::new()?; let listener = TcpListener::bind("127.0.0.2:0")?; let port = listener.local_addr()?.port(); - listener.set_nonblocking(true)?; - let server = std::thread::spawn(move || -> std::io::Result<()> { - let deadline = Instant::now() + Duration::from_secs(5); - loop { - match listener.accept() { - Ok((mut stream, _)) => { - // Closing with unread request bytes can reset the connection and yield a 502. - stream.set_nonblocking(false)?; - stream.set_read_timeout(Some(Duration::from_secs(5)))?; - let mut request = Vec::new(); - let mut buffer = [0_u8; 1024]; - while !request.windows(4).any(|window| window == b"\r\n\r\n") { - let bytes_read = std::io::Read::read(&mut stream, &mut buffer)?; - if bytes_read == 0 || request.len() + bytes_read > 64 * 1024 { - return Err(std::io::Error::other( - "incomplete or oversized loopback request headers", - )); - } - request.extend_from_slice(&buffer[..bytes_read]); - } - std::io::Write::write_all( - &mut stream, - b"HTTP/1.1 204 No Content\r\nContent-Length: 0\r\n\r\n", - )?; - return Ok(()); - } - Err(err) if err.kind() == std::io::ErrorKind::WouldBlock => { - if Instant::now() >= deadline { - return Err(std::io::Error::new( - std::io::ErrorKind::TimedOut, - "timed out waiting for allowlisted loopback request", - )); - } - std::thread::sleep(Duration::from_millis(10)); - } - Err(err) => return Err(err), - } - } - }); + let server = MockServer::builder().listener(listener).start().await; + Mock::given(method("GET")) + .and(path("/")) + .respond_with(ResponseTemplate::new(204)) + .mount(&server) + .await; std::fs::write( codex_home.path().join("config.toml"), r#" @@ -138,7 +108,7 @@ allow_local_binding = false )?; let url = format!("http://127.0.0.2:{port}/"); - let output = std::process::Command::new(codex_utils_cargo_bin::cargo_bin("codex")?) + let output = tokio::process::Command::new(codex_utils_cargo_bin::cargo_bin("codex")?) .env("CODEX_HOME", codex_home.path()) .args([ "sandbox", @@ -155,7 +125,8 @@ allow_local_binding = false "4", url.as_str(), ]) - .output()?; + .output() + .await?; let stderr = String::from_utf8_lossy(&output.stderr); if stderr.contains(BWRAP_UNAVAILABLE_ERR) { @@ -170,7 +141,5 @@ allow_local_binding = false String::from_utf8_lossy(&output.stdout), stderr, ); - server.join().expect("loopback server panicked")?; - Ok(()) } diff --git a/codex-rs/codex-mcp/src/connection_manager_tests.rs b/codex-rs/codex-mcp/src/connection_manager_tests.rs index f40a689beb..d7a841bf4c 100644 --- a/codex-rs/codex-mcp/src/connection_manager_tests.rs +++ b/codex-rs/codex-mcp/src/connection_manager_tests.rs @@ -80,7 +80,7 @@ use rmcp::model::NumberOrString; use rmcp::model::PaginatedRequestParams; use rmcp::model::ProtocolVersion; use rmcp::model::ServerCapabilities; -use rmcp::model::ServerInfo; +use rmcp::model::ServerConfig; use rmcp::model::Tool; use rmcp::service::RequestContext; use std::collections::HashMap; @@ -290,8 +290,8 @@ struct RefreshTestTransportFactory { } impl ServerHandler for RefreshTestTransportFactory { - fn get_info(&self) -> ServerInfo { - ServerInfo::new(ServerCapabilities::builder().enable_tools().build()) + fn get_info(&self) -> ServerConfig { + ServerConfig::new(ServerCapabilities::builder().enable_tools().build()) } async fn list_tools( @@ -338,8 +338,8 @@ struct MutableToolsServer { } impl ServerHandler for MutableToolsServer { - fn get_info(&self) -> ServerInfo { - ServerInfo::new(ServerCapabilities::builder().enable_tools().build()) + fn get_info(&self) -> ServerConfig { + ServerConfig::new(ServerCapabilities::builder().enable_tools().build()) } async fn list_tools( diff --git a/codex-rs/memories/write/src/startup_tests.rs b/codex-rs/memories/write/src/startup_tests.rs index 7d974674c9..1f2cb94f6b 100644 --- a/codex-rs/memories/write/src/startup_tests.rs +++ b/codex-rs/memories/write/src/startup_tests.rs @@ -143,7 +143,7 @@ async fn memories_startup_fails_consolidation_when_worker_creates_extension_syml ev_assistant_message("msg-phase2-complete", "phase2 complete"), ev_completed("resp-phase2-complete"), ]), - sse_failed("resp-phase2-failed", "server_error", "worker failed"), + sse_failed("resp-phase2-failed", "invalid_prompt", "worker failed"), ]; for response in responses { diff --git a/codex-rs/rmcp-client/src/bin/rmcp_test_server.rs b/codex-rs/rmcp-client/src/bin/rmcp_test_server.rs index a2ab794f5a..aba1422d90 100644 --- a/codex-rs/rmcp-client/src/bin/rmcp_test_server.rs +++ b/codex-rs/rmcp-client/src/bin/rmcp_test_server.rs @@ -11,7 +11,7 @@ use rmcp::model::JsonObject; use rmcp::model::ListToolsResult; use rmcp::model::PaginatedRequestParams; use rmcp::model::ServerCapabilities; -use rmcp::model::ServerInfo; +use rmcp::model::ServerConfig; use rmcp::model::Tool; use serde::Deserialize; use serde_json::json; @@ -78,8 +78,8 @@ struct EchoArgs { } impl ServerHandler for TestToolServer { - fn get_info(&self) -> ServerInfo { - ServerInfo::new( + fn get_info(&self) -> ServerConfig { + ServerConfig::new( ServerCapabilities::builder() .enable_tools() .enable_tool_list_changed() diff --git a/codex-rs/rmcp-client/src/bin/test_stdio_server.rs b/codex-rs/rmcp-client/src/bin/test_stdio_server.rs index 8bf0845d1a..b3d2161efc 100644 --- a/codex-rs/rmcp-client/src/bin/test_stdio_server.rs +++ b/codex-rs/rmcp-client/src/bin/test_stdio_server.rs @@ -28,7 +28,7 @@ use rmcp::model::Resource; use rmcp::model::ResourceContents; use rmcp::model::ResourceTemplate; use rmcp::model::ServerCapabilities; -use rmcp::model::ServerInfo; +use rmcp::model::ServerConfig; use rmcp::model::Tool; use rmcp::model::ToolAnnotations; use serde::Deserialize; @@ -527,7 +527,7 @@ impl ServerHandler for TestToolServer { Ok(self.get_info()) } - fn get_info(&self) -> ServerInfo { + fn get_info(&self) -> ServerConfig { let mut capabilities = ServerCapabilities::builder() .enable_tools() .enable_tool_list_changed() @@ -538,7 +538,7 @@ impl ServerHandler for TestToolServer { JsonObject::new(), )])); - let server_info = ServerInfo::new(capabilities); + let server_info = ServerConfig::new(capabilities); let server_info = match dynamic_server_process_label() { Some(process_label) => server_info .with_server_info( diff --git a/codex-rs/rmcp-client/src/bin/test_streamable_http_server.rs b/codex-rs/rmcp-client/src/bin/test_streamable_http_server.rs index 9702794bd6..31af242ed1 100644 --- a/codex-rs/rmcp-client/src/bin/test_streamable_http_server.rs +++ b/codex-rs/rmcp-client/src/bin/test_streamable_http_server.rs @@ -40,7 +40,7 @@ use rmcp::model::Resource; use rmcp::model::ResourceContents; use rmcp::model::ResourceTemplate; use rmcp::model::ServerCapabilities; -use rmcp::model::ServerInfo; +use rmcp::model::ServerConfig; use rmcp::model::Tool; use rmcp::model::ToolAnnotations; use rmcp::transport::StreamableHttpServerConfig; @@ -251,8 +251,8 @@ async fn main() -> Result<(), Box> { } impl ServerHandler for TestToolServer { - fn get_info(&self) -> ServerInfo { - ServerInfo::new( + fn get_info(&self) -> ServerConfig { + ServerConfig::new( ServerCapabilities::builder() .enable_tools() .enable_tool_list_changed() diff --git a/codex-rs/rmcp-client/src/elicitation_client_service.rs b/codex-rs/rmcp-client/src/elicitation_client_service.rs index ce15297007..ef2fbacd92 100644 --- a/codex-rs/rmcp-client/src/elicitation_client_service.rs +++ b/codex-rs/rmcp-client/src/elicitation_client_service.rs @@ -7,7 +7,7 @@ use std::sync::PoisonError; use codex_protocol::mcp::OPENAI_ELICITATION_EXTENSION_ID; use rmcp::RoleClient; -use rmcp::model::ClientInfo; +use rmcp::model::ClientConfig; use rmcp::model::ClientResult; use rmcp::model::CustomRequest; use rmcp::model::CustomResult; @@ -95,7 +95,7 @@ impl Drop for PendingVerification { impl ElicitationClientService { pub(crate) fn new( - client_info: ClientInfo, + client_info: ClientConfig, send_elicitation: SendElicitation, pause_state: ElicitationPauseState, ) -> Self { @@ -336,7 +336,7 @@ impl Service for ElicitationClientService { .await } - fn get_info(&self) -> ClientInfo { + fn get_info(&self) -> ClientConfig { >::get_info(&self.handler) } } diff --git a/codex-rs/rmcp-client/src/logging_client_handler.rs b/codex-rs/rmcp-client/src/logging_client_handler.rs index 98887d7490..f123021d21 100644 --- a/codex-rs/rmcp-client/src/logging_client_handler.rs +++ b/codex-rs/rmcp-client/src/logging_client_handler.rs @@ -3,7 +3,7 @@ use std::sync::Arc; use rmcp::ClientHandler; use rmcp::RoleClient; use rmcp::model::CancelledNotificationParam; -use rmcp::model::ClientInfo; +use rmcp::model::ClientConfig; use rmcp::model::ElicitRequestParams; use rmcp::model::ElicitResult; #[allow(deprecated)] @@ -24,12 +24,12 @@ use crate::rmcp_client::SendElicitation; #[derive(Clone)] pub(crate) struct LoggingClientHandler { - client_info: ClientInfo, + client_info: ClientConfig, send_elicitation: Arc, } impl LoggingClientHandler { - pub(crate) fn new(client_info: ClientInfo, send_elicitation: SendElicitation) -> Self { + pub(crate) fn new(client_info: ClientConfig, send_elicitation: SendElicitation) -> Self { Self { client_info, send_elicitation: Arc::new(send_elicitation), @@ -91,7 +91,7 @@ impl ClientHandler for LoggingClientHandler { info!("MCP server prompt list changed"); } - fn get_info(&self) -> ClientInfo { + fn get_info(&self) -> ClientConfig { self.client_info.clone() } diff --git a/codex-rs/rmcp-client/src/tool_input_tests.rs b/codex-rs/rmcp-client/src/tool_input_tests.rs index ebbdc962f6..306de871e0 100644 --- a/codex-rs/rmcp-client/src/tool_input_tests.rs +++ b/codex-rs/rmcp-client/src/tool_input_tests.rs @@ -6,7 +6,7 @@ use crate::tool_input::call_tool; use codex_protocol::mcp::OPENAI_ELICITATION_EXTENSION_ID; use rmcp::RoleServer; use rmcp::model::CallToolRequestParams; -use rmcp::model::ClientInfo; +use rmcp::model::ClientConfig; use rmcp::model::ClientJsonRpcMessage; use rmcp::model::CustomResult; use rmcp::model::ServerJsonRpcMessage; @@ -28,7 +28,7 @@ async fn connection_closure_releases_pending_inputs_and_timeout_pause() -> anyho let pause_state = ElicitationPauseState::new(); let mut paused = pause_state.subscribe(); let (route_tx, mut route_rx) = mpsc::unbounded_channel(); - let mut info = ClientInfo::default(); + let mut info = ClientConfig::default(); info.capabilities.extensions = Some( [( OPENAI_ELICITATION_EXTENSION_ID.into(), diff --git a/codex-rs/rmcp-client/src/user_verification_cancellation_tests.rs b/codex-rs/rmcp-client/src/user_verification_cancellation_tests.rs index 6d66c0416d..7e330f36d7 100644 --- a/codex-rs/rmcp-client/src/user_verification_cancellation_tests.rs +++ b/codex-rs/rmcp-client/src/user_verification_cancellation_tests.rs @@ -13,7 +13,7 @@ use rmcp::ServerHandler; use rmcp::ServiceExt; use rmcp::model::CancelledNotification; use rmcp::model::CancelledNotificationParam; -use rmcp::model::ClientInfo; +use rmcp::model::ClientConfig; use rmcp::model::ClientJsonRpcMessage; use rmcp::model::CustomRequest; use rmcp::model::ElicitationAction; @@ -23,6 +23,7 @@ use rmcp::model::ServerJsonRpcMessage; use rmcp::model::ServerNotification; use rmcp::model::ServerRequest; use rmcp::service::PeerRequestOptions; +use rmcp::service::QuitReason; use rmcp::service::RunningService; use rmcp::service::ServerInitializeError; use rmcp::service::serve_directly; @@ -82,7 +83,7 @@ async fn recovered_connections_accept_elicitations_with_previously_cancelled_ids .await?; client .initialize( - ClientInfo::default().with_protocol_version(ProtocolVersion::V_2025_06_18), + ClientConfig::default().with_protocol_version(ProtocolVersion::V_2025_06_18), Some(Duration::from_secs(/*secs*/ 5)), Box::new(|_, _| { Box::pin(async { @@ -171,7 +172,7 @@ async fn ordinary_elicitations_release_pending_responses_on_cancellation() -> an let mut paused = pause_state.subscribe(); let (route_tx, mut route_rx) = mpsc::unbounded_channel(); let service = ElicitationClientService::new( - ClientInfo::default(), + ClientConfig::default(), Box::new(move |_, _| { let (response_tx, response_rx) = oneshot::channel(); route_tx.send(response_tx).expect("observe elicitation"); @@ -211,14 +212,6 @@ async fn ordinary_elicitations_release_pending_responses_on_cancellation() -> an paused.wait_for(|paused| !*paused), ) .await??; - let response = timeout(Duration::from_secs(/*secs*/ 5), server.receive()) - .await? - .expect("cancelled elicitation returned a response"); - assert_eq!( - serde_json::to_value(response)?, - json!({"jsonrpc": "2.0", "id": 1, "result": {"action": "cancel"}}), - ); - server .send(ServerJsonRpcMessage::request(request, RequestId::Number(2))) .await?; @@ -239,12 +232,88 @@ async fn ordinary_elicitations_release_pending_responses_on_cancellation() -> an Ok(()) } +#[tokio::test(start_paused = true)] +async fn transport_eof_releases_pending_elicitations_without_dropping_client() -> anyhow::Result<()> +{ + for (method, params) in [ + ( + "elicitation/create", + json!({ + "mode": "form", + "message": "Confirm", + "requestedSchema": {"type": "object", "properties": {}}, + }), + ), + ( + "openai/elicitation/create", + json!({ + "mode": "openai/userVerification", + "title": "Approve", + "description": "", + "challenge": "AQID", + }), + ), + ] { + let pause_state = ElicitationPauseState::new(); + let mut paused = pause_state.subscribe(); + let (route_tx, mut route_rx) = mpsc::unbounded_channel(); + let mut client_info = ClientConfig::default(); + client_info.capabilities.extensions = Some( + [( + OPENAI_ELICITATION_EXTENSION_ID.to_string(), + serde_json::Map::from_iter([("userVerification".to_string(), json!({}))]), + )] + .into_iter() + .collect(), + ); + let service = ElicitationClientService::new( + client_info, + Box::new(move |_, _| { + let (response_tx, response_rx) = oneshot::channel(); + route_tx + .send(response_tx) + .expect("observe pending elicitation"); + Box::pin(async move { Ok(response_rx.await?) }) + }), + pause_state, + ); + let (client_transport, server_transport) = tokio::io::duplex(/*max_buf_size*/ 4096); + let client = serve_directly(service, client_transport, /*peer_info*/ None); + let mut server = IntoTransport::::into_transport(server_transport); + // A server can request approval without an outgoing tools/call to detect EOF. + server + .send(serde_json::from_value::(json!({ + "jsonrpc": "2.0", "id": 1, "method": method, "params": params, + }))?) + .await?; + let mut response_tx = timeout(Duration::from_secs(/*secs*/ 5), route_rx.recv()) + .await? + .expect("elicitation reached the UI"); + assert!(*paused.borrow()); + + drop(server); + + // RMCP allows five seconds to drain responses after EOF. Retain the client + // throughout cleanup: dropping it would cancel the handler and hide the bug. + timeout(Duration::from_secs(/*secs*/ 7), response_tx.closed()).await?; + timeout( + Duration::from_secs(/*secs*/ 1), + paused.wait_for(|paused| !*paused), + ) + .await??; + // Only consume the retained client after observing handler cleanup. + let reason = timeout(Duration::from_secs(/*secs*/ 1), client.waiting()).await??; + assert!(matches!(reason, QuitReason::Closed)); + } + Ok(()) +} + #[tokio::test] async fn user_verification_service_cancellation_drops_pending_response() -> anyhow::Result<()> { let pause_state = ElicitationPauseState::new(); let mut paused = pause_state.subscribe(); let (route_tx, mut route_rx) = mpsc::unbounded_channel(); - let mut client_info = ClientInfo::default(); + let mut client_info = ClientConfig::default(); client_info.capabilities.extensions = Some( [( OPENAI_ELICITATION_EXTENSION_ID.to_string(), @@ -303,7 +372,7 @@ async fn cancelling_one_verification_leaves_the_mcp_connection_and_other_request let pause_state = ElicitationPauseState::new(); let mut paused = pause_state.subscribe(); let (route_tx, mut route_rx) = mpsc::unbounded_channel(); - let mut client_info = ClientInfo::default(); + let mut client_info = ClientConfig::default(); client_info.capabilities.extensions = Some( [( OPENAI_ELICITATION_EXTENSION_ID.to_string(), @@ -362,17 +431,6 @@ async fn cancelling_one_verification_leaves_the_mcp_connection_and_other_request timeout(Duration::from_secs(/*secs*/ 5), first.closed()).await?; assert!(!second.is_closed()); assert!(*paused.borrow()); - let cancel = timeout(Duration::from_secs(/*secs*/ 5), server.receive()) - .await? - .expect("cancelled verification still sends a response"); - let ClientJsonRpcMessage::Response(cancel) = cancel else { - anyhow::bail!("expected a cancellation response"); - }; - assert_eq!(cancel.id, first_id); - assert_eq!( - serde_json::to_value(cancel.result)?, - json!({"action": "cancel", "_meta": {"openai/userVerificationReason": "interrupted"}}) - ); for request_id in [first_id, RequestId::Number(999)] { server diff --git a/codex-rs/rmcp-client/src/user_verification_dispatch_tests.rs b/codex-rs/rmcp-client/src/user_verification_dispatch_tests.rs index dda5a58e18..51e534aeed 100644 --- a/codex-rs/rmcp-client/src/user_verification_dispatch_tests.rs +++ b/codex-rs/rmcp-client/src/user_verification_dispatch_tests.rs @@ -11,7 +11,7 @@ use std::time::Duration; use tokio::time::timeout; fn service() -> ElicitationClientService { - let mut info = ClientInfo::default(); + let mut info = ClientConfig::default(); info.capabilities.extensions = Some( [( OPENAI_ELICITATION_EXTENSION_ID.into(), diff --git a/codex-rs/tui/src/app/tests/session_lifecycle_requests.rs b/codex-rs/tui/src/app/tests/session_lifecycle_requests.rs index 3e7975f219..5b82dc6d3a 100644 --- a/codex-rs/tui/src/app/tests/session_lifecycle_requests.rs +++ b/codex-rs/tui/src/app/tests/session_lifecycle_requests.rs @@ -39,11 +39,26 @@ use tokio::net::TcpListener; use tokio::sync::oneshot; use tokio::task::JoinHandle; use tokio_tungstenite::accept_async; +use tokio_tungstenite::tungstenite::Error as WebSocketError; use tokio_tungstenite::tungstenite::Message; +use tokio_tungstenite::tungstenite::error::ProtocolError; pub(super) type RecordedRequests = Arc>>; pub(super) type RecordingAppServer = (AppServerSession, RecordedRequests, JoinHandle>); +fn is_recording_client_disconnect(error: &WebSocketError) -> bool { + match error { + WebSocketError::Protocol(ProtocolError::ResetWithoutClosingHandshake) => true, + WebSocketError::Io(error) => matches!( + error.kind(), + std::io::ErrorKind::BrokenPipe + | std::io::ErrorKind::ConnectionReset + | std::io::ErrorKind::ConnectionAborted + ), + _ => false, + } +} + #[tokio::test] async fn daybreak_command_persists_and_confirms_each_selection() -> Result<()> { use codex_protocol::openai_models::ModelAccessPrograms; @@ -495,20 +510,19 @@ pub(super) async fn start_recording_app_server_with_realtime_speech( if let codex_app_server_client::InProcessServerEvent::ServerNotification(notification) = event && matches!(*notification, ServerNotification::ThreadSettingsUpdated(_)) { - websocket.send(Message::Text(serde_json::to_string(¬ification)?.into())).await?; + let result = websocket.send(Message::Text(serde_json::to_string(¬ification)?.into())).await; + if result.as_ref().is_err_and(is_recording_client_disconnect) { + break; + } + result?; } continue; } }; let Some(frame) = frame else { break }; - // The client can close with an unread settings notification during shutdown. - match &frame { - Err(tokio_tungstenite::tungstenite::Error::Protocol( - tokio_tungstenite::tungstenite::error::ProtocolError::ResetWithoutClosingHandshake, - )) => break, - Err(tokio_tungstenite::tungstenite::Error::Io(error)) - if matches!(error.kind(), std::io::ErrorKind::BrokenPipe | std::io::ErrorKind::ConnectionReset | std::io::ErrorKind::ConnectionAborted) => break, - _ => {} + // The client can close with requests or notifications pending during shutdown. + if frame.as_ref().is_err_and(is_recording_client_disconnect) { + break; } let Message::Text(text) = frame? else { continue; @@ -516,7 +530,7 @@ pub(super) async fn start_recording_app_server_with_realtime_speech( let message = serde_json::from_str::(&text)?; match message { JSONRPCMessage::Request(request) if request.method == "initialize" => { - websocket + let result = websocket .send(Message::Text( serde_json::to_string(&JSONRPCMessage::Response(JSONRPCResponse { id: request.id, @@ -527,7 +541,11 @@ pub(super) async fn start_recording_app_server_with_realtime_speech( }))? .into(), )) - .await?; + .await; + if result.as_ref().is_err_and(is_recording_client_disconnect) { + break; + } + result?; } JSONRPCMessage::Request(request) => { request_sink @@ -803,9 +821,13 @@ pub(super) async fn start_recording_app_server_with_realtime_speech( } } }; - websocket + let result = websocket .send(Message::Text(serde_json::to_string(&response)?.into())) - .await?; + .await; + if result.as_ref().is_err_and(is_recording_client_disconnect) { + break; + } + result?; } JSONRPCMessage::Notification(notification) if notification.method == "initialized" => {} diff --git a/codex-rs/tui/src/dynamic_tools_mcp.rs b/codex-rs/tui/src/dynamic_tools_mcp.rs index 67fed87b4c..71914649a4 100644 --- a/codex-rs/tui/src/dynamic_tools_mcp.rs +++ b/codex-rs/tui/src/dynamic_tools_mcp.rs @@ -31,7 +31,7 @@ use rmcp::model::JsonObject; use rmcp::model::ListToolsResult; use rmcp::model::PaginatedRequestParams; use rmcp::model::ServerCapabilities; -use rmcp::model::ServerInfo; +use rmcp::model::ServerConfig; use rmcp::model::Tool; use rmcp::model::ToolAnnotations; use rmcp::service::RequestContext; @@ -246,8 +246,8 @@ struct DynamicToolMcpHandler { } impl ServerHandler for DynamicToolMcpHandler { - fn get_info(&self) -> ServerInfo { - ServerInfo::new(ServerCapabilities::builder().enable_tools().build()) + fn get_info(&self) -> ServerConfig { + ServerConfig::new(ServerCapabilities::builder().enable_tools().build()) } async fn list_tools(