From 709283b432fb5231c9bca720c239b25046968aa2 Mon Sep 17 00:00:00 2001 From: Celia Chen Date: Tue, 28 Jul 2026 19:04:05 +0000 Subject: [PATCH] Use configured HTTP clients for all MCP OAuth requests (#35814) ## What changed - Require callers to provide an HTTP client for MCP OAuth discovery and login, removing the separate direct `reqwest` path. - Use the shared `http` and `url` types throughout the MCP client and drop its direct `reqwest` dependency. - Preserve configured MCP headers when routing OAuth discovery through the provided client. ## Testing - Add coverage that routed OAuth discovery forwards configured headers. GitOrigin-RevId: 1345f56bd794626533133447597a5908e57b9b48 --- MODULE.bazel.lock | 7 - codex-rs/Cargo.lock | 100 +--------- codex-rs/app-server/src/request_processors.rs | 4 +- .../src/request_processors/mcp_processor.rs | 4 +- .../src/request_processors/plugins.rs | 4 +- codex-rs/cli/src/mcp_cmd.rs | 8 +- codex-rs/codex-mcp/src/lib.rs | 2 - codex-rs/codex-mcp/src/mcp/auth.rs | 40 +--- codex-rs/codex-mcp/src/mcp/mod.rs | 2 - codex-rs/core/src/mcp_skill_dependencies.rs | 4 +- codex-rs/deny.toml | 1 - codex-rs/rmcp-client/Cargo.toml | 10 +- codex-rs/rmcp-client/src/auth_status.rs | 171 +++++++++--------- .../rmcp-client/src/http_client_adapter.rs | 33 ++-- .../http_client_adapter/www_authenticate.rs | 2 +- codex-rs/rmcp-client/src/lib.rs | 4 - .../src/oauth/tests/persistor_tests.rs | 14 +- codex-rs/rmcp-client/src/oauth_http_client.rs | 53 +----- .../rmcp-client/src/perform_oauth_login.rs | 44 +---- codex-rs/rmcp-client/src/rmcp_client.rs | 4 +- .../rmcp-client/src/streamable_http_retry.rs | 2 +- codex-rs/rmcp-client/src/utils.rs | 8 +- .../tests/mcp_2026_oauth_discovery.rs | 13 +- .../tests/streamable_http_oauth_startup.rs | 3 + .../tests/streamable_http_test_support.rs | 24 ++- 25 files changed, 181 insertions(+), 380 deletions(-) diff --git a/MODULE.bazel.lock b/MODULE.bazel.lock index dc70e7dd7e..5921b7a187 100644 --- a/MODULE.bazel.lock +++ b/MODULE.bazel.lock @@ -1145,12 +1145,8 @@ "jiff-tzdb_0.1.6": "{\"dependencies\":[],\"features\":{}}", "jiff_0.2.23": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"anyhow\",\"req\":\"^1.0.81\"},{\"features\":[\"serde\"],\"kind\":\"dev\",\"name\":\"chrono\",\"req\":\"^0.4.38\"},{\"kind\":\"dev\",\"name\":\"chrono-tz\",\"req\":\"^0.10.0\"},{\"kind\":\"dev\",\"name\":\"hifitime\",\"req\":\"^3.9.0\",\"target\":\"cfg(not(target_family = \\\"wasm\\\"))\"},{\"kind\":\"dev\",\"name\":\"humantime\",\"req\":\"^2.1.0\"},{\"kind\":\"dev\",\"name\":\"insta\",\"req\":\"^1.39.0\"},{\"name\":\"jiff-static\",\"req\":\"=0.2.23\",\"target\":\"cfg(any())\"},{\"name\":\"jiff-static\",\"optional\":true,\"req\":\"^0.2\"},{\"name\":\"jiff-tzdb\",\"optional\":true,\"req\":\"^0.1.6\"},{\"name\":\"jiff-tzdb-platform\",\"optional\":true,\"req\":\"^0.1.3\",\"target\":\"cfg(any(windows, target_family = \\\"wasm\\\"))\"},{\"name\":\"js-sys\",\"optional\":true,\"req\":\"^0.3.50\",\"target\":\"cfg(all(any(target_arch = \\\"wasm32\\\", target_arch = \\\"wasm64\\\"), target_os = \\\"unknown\\\"))\"},{\"default_features\":false,\"name\":\"log\",\"optional\":true,\"req\":\"^0.4.21\"},{\"kind\":\"dev\",\"name\":\"log\",\"req\":\"^0.4.21\"},{\"default_features\":false,\"name\":\"portable-atomic\",\"req\":\"^1.10.0\",\"target\":\"cfg(not(target_has_atomic = \\\"ptr\\\"))\"},{\"default_features\":false,\"name\":\"portable-atomic-util\",\"req\":\"^0.2.4\",\"target\":\"cfg(not(target_has_atomic = \\\"ptr\\\"))\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"quickcheck\",\"req\":\"^1.0.3\"},{\"features\":[\"derive\"],\"kind\":\"dev\",\"name\":\"serde\",\"req\":\"^1.0.203\"},{\"default_features\":false,\"name\":\"serde_core\",\"optional\":true,\"req\":\"^1.0.221\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0.117\"},{\"kind\":\"dev\",\"name\":\"serde_yaml\",\"req\":\"^0.9.34\"},{\"kind\":\"dev\",\"name\":\"tabwriter\",\"req\":\"^1.4.0\"},{\"features\":[\"local-offset\",\"macros\",\"parsing\"],\"kind\":\"dev\",\"name\":\"time\",\"req\":\"^0.3.36\"},{\"kind\":\"dev\",\"name\":\"time-tz\",\"req\":\"^2.0.0\"},{\"kind\":\"dev\",\"name\":\"tzfile\",\"req\":\"^0.1.3\"},{\"kind\":\"dev\",\"name\":\"walkdir\",\"req\":\"^2.5.0\"},{\"name\":\"wasm-bindgen\",\"optional\":true,\"req\":\"^0.2.70\",\"target\":\"cfg(all(any(target_arch = \\\"wasm32\\\", target_arch = \\\"wasm64\\\"), target_os = \\\"unknown\\\"))\"},{\"default_features\":false,\"features\":[\"Win32_Foundation\",\"Win32_System_Time\"],\"name\":\"windows-sys\",\"optional\":true,\"req\":\">=0.52.0, <=0.61\",\"target\":\"cfg(windows)\"}],\"features\":{\"alloc\":[\"serde_core?/alloc\",\"portable-atomic-util/alloc\"],\"default\":[\"std\",\"tz-system\",\"tz-fat\",\"tzdb-bundle-platform\",\"tzdb-zoneinfo\",\"tzdb-concatenated\",\"perf-inline\"],\"js\":[\"dep:wasm-bindgen\",\"dep:js-sys\"],\"logging\":[\"dep:log\"],\"perf-inline\":[],\"serde\":[\"dep:serde_core\"],\"static\":[\"static-tz\",\"jiff-static?/tzdb\"],\"static-tz\":[\"dep:jiff-static\"],\"std\":[\"alloc\",\"log?/std\",\"serde_core?/std\"],\"tz-fat\":[\"jiff-static?/tz-fat\"],\"tz-system\":[\"std\",\"dep:windows-sys\"],\"tzdb-bundle-always\":[\"dep:jiff-tzdb\",\"alloc\"],\"tzdb-bundle-platform\":[\"dep:jiff-tzdb-platform\",\"alloc\"],\"tzdb-concatenated\":[\"std\"],\"tzdb-zoneinfo\":[\"std\"]}}", "jiff_0.2.24": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"anyhow\",\"req\":\"^1.0.81\"},{\"features\":[\"serde\"],\"kind\":\"dev\",\"name\":\"chrono\",\"req\":\"^0.4.38\"},{\"kind\":\"dev\",\"name\":\"chrono-tz\",\"req\":\"^0.10.0\"},{\"kind\":\"dev\",\"name\":\"hifitime\",\"req\":\"^3.9.0\",\"target\":\"cfg(not(target_family = \\\"wasm\\\"))\"},{\"kind\":\"dev\",\"name\":\"humantime\",\"req\":\"^2.1.0\"},{\"kind\":\"dev\",\"name\":\"insta\",\"req\":\"^1.39.0\"},{\"name\":\"jiff-static\",\"req\":\"=0.2.24\",\"target\":\"cfg(any())\"},{\"name\":\"jiff-static\",\"optional\":true,\"req\":\"^0.2\"},{\"name\":\"jiff-tzdb\",\"optional\":true,\"req\":\"^0.1.6\"},{\"name\":\"jiff-tzdb-platform\",\"optional\":true,\"req\":\"^0.1.3\",\"target\":\"cfg(any(windows, target_family = \\\"wasm\\\"))\"},{\"name\":\"js-sys\",\"optional\":true,\"req\":\"^0.3.50\",\"target\":\"cfg(all(any(target_arch = \\\"wasm32\\\", target_arch = \\\"wasm64\\\"), target_os = \\\"unknown\\\"))\"},{\"default_features\":false,\"name\":\"log\",\"optional\":true,\"req\":\"^0.4.21\"},{\"kind\":\"dev\",\"name\":\"log\",\"req\":\"^0.4.21\"},{\"default_features\":false,\"name\":\"portable-atomic\",\"req\":\"^1.10.0\",\"target\":\"cfg(not(target_has_atomic = \\\"ptr\\\"))\"},{\"default_features\":false,\"name\":\"portable-atomic-util\",\"req\":\"^0.2.4\",\"target\":\"cfg(not(target_has_atomic = \\\"ptr\\\"))\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"quickcheck\",\"req\":\"^1.0.3\"},{\"features\":[\"derive\"],\"kind\":\"dev\",\"name\":\"serde\",\"req\":\"^1.0.203\"},{\"default_features\":false,\"name\":\"serde_core\",\"optional\":true,\"req\":\"^1.0.221\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0.117\"},{\"kind\":\"dev\",\"name\":\"serde_yaml\",\"req\":\"^0.9.34\"},{\"kind\":\"dev\",\"name\":\"tabwriter\",\"req\":\"^1.4.0\"},{\"features\":[\"local-offset\",\"macros\",\"parsing\"],\"kind\":\"dev\",\"name\":\"time\",\"req\":\"^0.3.36\"},{\"kind\":\"dev\",\"name\":\"time-tz\",\"req\":\"^2.0.0\"},{\"kind\":\"dev\",\"name\":\"tzfile\",\"req\":\"^0.1.3\"},{\"kind\":\"dev\",\"name\":\"walkdir\",\"req\":\"^2.5.0\"},{\"name\":\"wasm-bindgen\",\"optional\":true,\"req\":\"^0.2.70\",\"target\":\"cfg(all(any(target_arch = \\\"wasm32\\\", target_arch = \\\"wasm64\\\"), target_os = \\\"unknown\\\"))\"},{\"default_features\":false,\"features\":[\"Win32_Foundation\",\"Win32_System_Time\"],\"name\":\"windows-sys\",\"optional\":true,\"req\":\">=0.52.0, <=0.61\",\"target\":\"cfg(windows)\"}],\"features\":{\"alloc\":[\"serde_core?/alloc\",\"portable-atomic-util/alloc\"],\"default\":[\"std\",\"tz-system\",\"tz-fat\",\"tzdb-bundle-platform\",\"tzdb-zoneinfo\",\"tzdb-concatenated\",\"perf-inline\"],\"js\":[\"dep:wasm-bindgen\",\"dep:js-sys\"],\"logging\":[\"dep:log\"],\"perf-inline\":[],\"serde\":[\"dep:serde_core\"],\"static\":[\"static-tz\",\"jiff-static?/tzdb\"],\"static-tz\":[\"dep:jiff-static\"],\"std\":[\"alloc\",\"log?/std\",\"serde_core?/std\"],\"tz-fat\":[\"jiff-static?/tz-fat\"],\"tz-system\":[\"std\",\"dep:windows-sys\"],\"tzdb-bundle-always\":[\"dep:jiff-tzdb\",\"alloc\"],\"tzdb-bundle-platform\":[\"dep:jiff-tzdb-platform\",\"alloc\"],\"tzdb-concatenated\":[\"std\"],\"tzdb-zoneinfo\":[\"std\"]}}", - "jni-macros_0.22.4": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"javac\",\"req\":\"^0.1.0\"},{\"name\":\"proc-macro2\",\"req\":\"^1.0\"},{\"name\":\"quote\",\"req\":\"^1.0\"},{\"kind\":\"build\",\"name\":\"rustc_version\",\"req\":\"^0.4\"},{\"kind\":\"dev\",\"name\":\"rusty-fork\",\"req\":\"^0.3.0\"},{\"name\":\"simd_cesu8\",\"req\":\"^1.0.1\"},{\"features\":[\"full\"],\"name\":\"syn\",\"req\":\"^2.0\"},{\"kind\":\"dev\",\"name\":\"thiserror\",\"req\":\"^2\"},{\"kind\":\"dev\",\"name\":\"trybuild\",\"req\":\"^1\"}],\"features\":{}}", - "jni-sys-macros_0.4.1": "{\"dependencies\":[{\"name\":\"quote\",\"req\":\"^1\"},{\"features\":[\"full\"],\"name\":\"syn\",\"req\":\"^2\"}],\"features\":{}}", "jni-sys_0.3.0": "{\"dependencies\":[],\"features\":{}}", - "jni-sys_0.4.1": "{\"dependencies\":[{\"name\":\"jni-sys-macros\",\"req\":\"^0.4.1\"},{\"kind\":\"dev\",\"name\":\"trybuild\",\"req\":\"^1\"}],\"features\":{}}", "jni_0.21.1": "{\"dependencies\":[{\"name\":\"cesu8\",\"req\":\"^1.1.0\"},{\"name\":\"cfg-if\",\"req\":\"^1.0.0\"},{\"name\":\"combine\",\"req\":\"^4.1.0\"},{\"name\":\"java-locator\",\"optional\":true,\"req\":\"^0.1\"},{\"name\":\"jni-sys\",\"req\":\"^0.3.0\"},{\"name\":\"libloading\",\"optional\":true,\"req\":\"^0.7\"},{\"name\":\"log\",\"req\":\"^0.4.4\"},{\"name\":\"thiserror\",\"req\":\"^1.0.20\"},{\"kind\":\"dev\",\"name\":\"assert_matches\",\"req\":\"^1.5.0\"},{\"kind\":\"dev\",\"name\":\"lazy_static\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"rusty-fork\",\"req\":\"^0.3.0\"},{\"kind\":\"build\",\"name\":\"walkdir\",\"req\":\"^2\"},{\"features\":[\"Win32_Globalization\"],\"name\":\"windows-sys\",\"req\":\"^0.45.0\",\"target\":\"cfg(windows)\"},{\"kind\":\"dev\",\"name\":\"bytemuck\",\"req\":\"^1.13.0\",\"target\":\"cfg(windows)\"}],\"features\":{\"default\":[],\"invocation\":[\"java-locator\",\"libloading\"]}}", - "jni_0.22.4": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"assert_matches\",\"req\":\"^1.5.0\"},{\"kind\":\"dev\",\"name\":\"bytemuck\",\"req\":\"^1.13.0\",\"target\":\"cfg(windows)\"},{\"name\":\"cfg-if\",\"req\":\"^1.0.0\"},{\"name\":\"combine\",\"req\":\"^4.1.0\"},{\"features\":[\"html_reports\"],\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.7\"},{\"name\":\"java-locator\",\"optional\":true,\"req\":\"^0.1.3\",\"target\":\"cfg(not(target_os = \\\"android\\\"))\"},{\"kind\":\"dev\",\"name\":\"javac\",\"req\":\"^0.1.0\"},{\"name\":\"jni-macros\",\"req\":\"=0.22.4\"},{\"name\":\"jni-sys\",\"req\":\"^0.4.1\"},{\"kind\":\"dev\",\"name\":\"lazy_static\",\"req\":\"^1\"},{\"name\":\"libloading\",\"optional\":true,\"req\":\"^0.8\",\"target\":\"cfg(not(target_os = \\\"android\\\"))\"},{\"name\":\"log\",\"req\":\"^0.4.4\"},{\"kind\":\"dev\",\"name\":\"rusty-fork\",\"req\":\"^0.3.0\"},{\"name\":\"simd_cesu8\",\"req\":\"^1.1.1\"},{\"kind\":\"dev\",\"name\":\"static_assertions\",\"req\":\"^1\"},{\"name\":\"thiserror\",\"req\":\"^2\"},{\"kind\":\"dev\",\"name\":\"trybuild\",\"req\":\"^1\"},{\"kind\":\"build\",\"name\":\"walkdir\",\"req\":\"^2\"},{\"name\":\"windows-link\",\"req\":\"^0.2\",\"target\":\"cfg(windows)\"},{\"features\":[\"Win32_System_Threading\",\"Win32_Foundation\"],\"kind\":\"dev\",\"name\":\"windows-sys\",\"req\":\"^0.61\",\"target\":\"cfg(windows)\"}],\"features\":{\"_cfg_test\":[],\"default\":[],\"invocation\":[\"dep:java-locator\",\"dep:libloading\"]}}", "jobserver_0.1.34": "{\"dependencies\":[{\"features\":[\"std\"],\"name\":\"getrandom\",\"req\":\"^0.3.2\",\"target\":\"cfg(windows)\"},{\"name\":\"libc\",\"req\":\"^0.2.171\",\"target\":\"cfg(unix)\"},{\"features\":[\"fs\"],\"kind\":\"dev\",\"name\":\"nix\",\"req\":\"^0.28.0\",\"target\":\"cfg(unix)\"},{\"kind\":\"dev\",\"name\":\"tempfile\",\"req\":\"^3.10.1\"}],\"features\":{}}", "js-sys_0.3.82": "{\"dependencies\":[{\"default_features\":false,\"name\":\"once_cell\",\"req\":\"^1.12\"},{\"default_features\":false,\"name\":\"wasm-bindgen\",\"req\":\"=0.2.105\"}],\"features\":{\"default\":[\"std\"],\"std\":[\"wasm-bindgen/std\"]}}", "js-sys_0.3.85": "{\"dependencies\":[{\"default_features\":false,\"name\":\"once_cell\",\"req\":\"^1.12\"},{\"default_features\":false,\"name\":\"wasm-bindgen\",\"req\":\"=0.2.108\"}],\"features\":{\"default\":[\"std\"],\"std\":[\"wasm-bindgen/std\"]}}", @@ -1443,8 +1439,6 @@ "rustls-native-certs_0.8.3": "{\"dependencies\":[{\"name\":\"openssl-probe\",\"req\":\"^0.2\",\"target\":\"cfg(all(unix, not(target_os = \\\"macos\\\")))\"},{\"features\":[\"std\"],\"name\":\"pki-types\",\"package\":\"rustls-pki-types\",\"req\":\"^1.10\"},{\"kind\":\"dev\",\"name\":\"ring\",\"req\":\"^0.17\"},{\"kind\":\"dev\",\"name\":\"rustls\",\"req\":\"^0.23\"},{\"kind\":\"dev\",\"name\":\"rustls-webpki\",\"req\":\"^0.103\"},{\"name\":\"schannel\",\"req\":\"^0.1\",\"target\":\"cfg(windows)\"},{\"name\":\"security-framework\",\"req\":\"^3\",\"target\":\"cfg(target_os = \\\"macos\\\")\"},{\"kind\":\"dev\",\"name\":\"serial_test\",\"req\":\"^3\"},{\"kind\":\"dev\",\"name\":\"tempfile\",\"req\":\"^3.5\"},{\"kind\":\"dev\",\"name\":\"untrusted\",\"req\":\"^0.9\"},{\"kind\":\"dev\",\"name\":\"webpki-roots\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"x509-parser\",\"req\":\"^0.18\"}],\"features\":{}}", "rustls-pki-types_1.14.0": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"crabgrind\",\"req\":\"=0.1.9\",\"target\":\"cfg(all(target_os = \\\"linux\\\", target_arch = \\\"x86_64\\\"))\"},{\"name\":\"web-time\",\"optional\":true,\"req\":\"^1\",\"target\":\"cfg(all(target_family = \\\"wasm\\\", target_os = \\\"unknown\\\"))\"},{\"name\":\"zeroize\",\"optional\":true,\"req\":\"^1\"}],\"features\":{\"alloc\":[\"dep:zeroize\"],\"default\":[\"alloc\"],\"std\":[\"alloc\"],\"web\":[\"web-time\"]}}", "rustls-pki-types_1.14.1": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"crabgrind\",\"req\":\"=0.1.9\",\"target\":\"cfg(all(target_os = \\\"linux\\\", target_arch = \\\"x86_64\\\"))\"},{\"name\":\"web-time\",\"optional\":true,\"req\":\"^1\",\"target\":\"cfg(all(target_family = \\\"wasm\\\", target_os = \\\"unknown\\\"))\"},{\"name\":\"zeroize\",\"optional\":true,\"req\":\"^1\"}],\"features\":{\"alloc\":[\"dep:zeroize\"],\"default\":[\"alloc\"],\"std\":[\"alloc\"],\"web\":[\"web-time\"]}}", - "rustls-platform-verifier-android_0.1.1": "{\"dependencies\":[],\"features\":{}}", - "rustls-platform-verifier_0.7.0": "{\"dependencies\":[{\"name\":\"android_logger\",\"optional\":true,\"req\":\"^0.15\",\"target\":\"cfg(target_os = \\\"android\\\")\"},{\"name\":\"base64\",\"optional\":true,\"req\":\"^0.22\"},{\"name\":\"core-foundation\",\"req\":\"^0.10\",\"target\":\"cfg(any(target_vendor = \\\"apple\\\"))\"},{\"name\":\"core-foundation-sys\",\"req\":\"^0.8\",\"target\":\"cfg(any(target_vendor = \\\"apple\\\"))\"},{\"default_features\":false,\"name\":\"jni\",\"req\":\"^0.22\",\"target\":\"cfg(target_os = \\\"android\\\")\"},{\"default_features\":false,\"name\":\"jni\",\"optional\":true,\"req\":\"^0.22.4\"},{\"name\":\"log\",\"req\":\"^0.4\"},{\"name\":\"once_cell\",\"req\":\"^1.9\",\"target\":\"cfg(target_os = \\\"android\\\")\"},{\"name\":\"once_cell\",\"optional\":true,\"req\":\"^1.9\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"rustls\",\"req\":\"^0.23.27\"},{\"default_features\":false,\"features\":[\"ring\"],\"kind\":\"dev\",\"name\":\"rustls\",\"req\":\"^0.23\"},{\"name\":\"rustls-native-certs\",\"req\":\"^0.8\",\"target\":\"cfg(all(unix, not(target_os = \\\"android\\\"), not(target_vendor = \\\"apple\\\"), not(target_arch = \\\"wasm32\\\")))\"},{\"name\":\"rustls-platform-verifier-android\",\"req\":\"^0.1.0\",\"target\":\"cfg(target_os = \\\"android\\\")\"},{\"name\":\"security-framework\",\"req\":\"^3.5.0\",\"target\":\"cfg(any(target_vendor = \\\"apple\\\"))\"},{\"name\":\"security-framework-sys\",\"req\":\"^2.15\",\"target\":\"cfg(any(target_vendor = \\\"apple\\\"))\"},{\"default_features\":false,\"name\":\"webpki\",\"package\":\"rustls-webpki\",\"req\":\"^0.103\",\"target\":\"cfg(all(unix, not(target_os = \\\"android\\\"), not(target_vendor = \\\"apple\\\"), not(target_arch = \\\"wasm32\\\")))\"},{\"default_features\":false,\"name\":\"webpki\",\"package\":\"rustls-webpki\",\"req\":\"^0.103\",\"target\":\"cfg(target_arch = \\\"wasm32\\\")\"},{\"default_features\":false,\"name\":\"webpki\",\"package\":\"rustls-webpki\",\"req\":\"^0.103\",\"target\":\"cfg(target_os = \\\"android\\\")\"},{\"name\":\"webpki-root-certs\",\"req\":\"^1\",\"target\":\"cfg(target_arch = \\\"wasm32\\\")\"},{\"kind\":\"dev\",\"name\":\"webpki-root-certs\",\"req\":\"^1\"},{\"default_features\":false,\"features\":[\"Win32_Foundation\",\"Win32_Security_Cryptography\"],\"name\":\"windows-sys\",\"req\":\">=0.52.0, <0.62.0\",\"target\":\"cfg(windows)\"}],\"features\":{\"cert-logging\":[\"base64\"],\"dbg\":[],\"docsrs\":[\"jni\",\"once_cell\"],\"ffi-testing\":[\"android_logger\",\"rustls/ring\"]}}", "rustls-webpki_0.103.13": "{\"dependencies\":[{\"default_features\":false,\"name\":\"aws-lc-rs\",\"optional\":true,\"req\":\"^1.14\"},{\"kind\":\"dev\",\"name\":\"base64\",\"req\":\"^0.22\"},{\"kind\":\"dev\",\"name\":\"bencher\",\"req\":\"^0.1.5\"},{\"kind\":\"dev\",\"name\":\"bzip2\",\"req\":\"^0.6\"},{\"kind\":\"dev\",\"name\":\"once_cell\",\"req\":\"^1.17.2\"},{\"default_features\":false,\"name\":\"pki-types\",\"package\":\"rustls-pki-types\",\"req\":\"^1.12\"},{\"default_features\":false,\"features\":[\"aws_lc_rs\"],\"kind\":\"dev\",\"name\":\"rcgen\",\"req\":\"^0.14.2\"},{\"default_features\":false,\"name\":\"ring\",\"optional\":true,\"req\":\"^0.17\"},{\"features\":[\"derive\"],\"kind\":\"dev\",\"name\":\"serde\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0\"},{\"name\":\"untrusted\",\"req\":\"^0.9\"},{\"kind\":\"dev\",\"name\":\"x509-parser\",\"req\":\"^0.18.1\"}],\"features\":{\"alloc\":[\"ring?/alloc\",\"pki-types/alloc\"],\"aws-lc-rs\":[\"dep:aws-lc-rs\",\"aws-lc-rs/aws-lc-sys\",\"aws-lc-rs/prebuilt-nasm\"],\"aws-lc-rs-fips\":[\"dep:aws-lc-rs\",\"aws-lc-rs/fips\"],\"aws-lc-rs-unstable\":[\"aws-lc-rs\",\"aws-lc-rs/unstable\"],\"default\":[\"std\"],\"ring\":[\"dep:ring\"],\"std\":[\"alloc\",\"pki-types/std\"]}}", "rustls_0.23.36": "{\"dependencies\":[{\"default_features\":false,\"name\":\"aws-lc-rs\",\"optional\":true,\"req\":\"^1.14\"},{\"kind\":\"dev\",\"name\":\"base64\",\"req\":\"^0.22\"},{\"kind\":\"dev\",\"name\":\"bencher\",\"req\":\"^0.1.5\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"brotli\",\"optional\":true,\"req\":\"^8\"},{\"name\":\"brotli-decompressor\",\"optional\":true,\"req\":\"^5.0.0\"},{\"kind\":\"dev\",\"name\":\"env_logger\",\"req\":\"^0.11\"},{\"default_features\":false,\"features\":[\"default-hasher\",\"inline-more\"],\"name\":\"hashbrown\",\"optional\":true,\"req\":\"^0.15\"},{\"kind\":\"dev\",\"name\":\"hex\",\"req\":\"^0.4\"},{\"name\":\"log\",\"optional\":true,\"req\":\"^0.4.8\"},{\"kind\":\"dev\",\"name\":\"log\",\"req\":\"^0.4.8\"},{\"kind\":\"dev\",\"name\":\"macro_rules_attribute\",\"req\":\"^0.2\"},{\"kind\":\"dev\",\"name\":\"num-bigint\",\"req\":\"^0.4.4\"},{\"default_features\":false,\"features\":[\"alloc\",\"race\"],\"name\":\"once_cell\",\"req\":\"^1.16\"},{\"features\":[\"alloc\"],\"name\":\"pki-types\",\"package\":\"rustls-pki-types\",\"req\":\"^1.12\"},{\"default_features\":false,\"features\":[\"pem\",\"aws_lc_rs\"],\"kind\":\"dev\",\"name\":\"rcgen\",\"req\":\"^0.14\"},{\"name\":\"ring\",\"optional\":true,\"req\":\"^0.17\"},{\"kind\":\"build\",\"name\":\"rustversion\",\"optional\":true,\"req\":\"^1.0.6\"},{\"features\":[\"derive\"],\"kind\":\"dev\",\"name\":\"serde\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1\"},{\"default_features\":false,\"name\":\"subtle\",\"req\":\"^2.5.0\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"time\",\"req\":\"^0.3.6\"},{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"webpki\",\"package\":\"rustls-webpki\",\"req\":\"^0.103.5\"},{\"kind\":\"dev\",\"name\":\"webpki-roots\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"x509-parser\",\"req\":\"^0.17\"},{\"name\":\"zeroize\",\"req\":\"^1.8\"},{\"name\":\"zlib-rs\",\"optional\":true,\"req\":\"^0.5\"}],\"features\":{\"aws-lc-rs\":[\"aws_lc_rs\"],\"aws_lc_rs\":[\"dep:aws-lc-rs\",\"webpki/aws-lc-rs\",\"aws-lc-rs/aws-lc-sys\",\"aws-lc-rs/prebuilt-nasm\"],\"brotli\":[\"dep:brotli\",\"dep:brotli-decompressor\",\"std\"],\"custom-provider\":[],\"default\":[\"aws_lc_rs\",\"logging\",\"prefer-post-quantum\",\"std\",\"tls12\"],\"fips\":[\"aws_lc_rs\",\"aws-lc-rs?/fips\",\"webpki/aws-lc-rs-fips\"],\"logging\":[\"log\"],\"prefer-post-quantum\":[\"aws_lc_rs\"],\"read_buf\":[\"rustversion\",\"std\"],\"ring\":[\"dep:ring\",\"webpki/ring\"],\"std\":[\"webpki/std\",\"pki-types/std\",\"once_cell/std\"],\"tls12\":[],\"zlib\":[\"dep:zlib-rs\"]}}", "rustls_0.23.40": "{\"dependencies\":[{\"default_features\":false,\"name\":\"aws-lc-rs\",\"optional\":true,\"req\":\"^1.14\"},{\"kind\":\"dev\",\"name\":\"base64\",\"req\":\"^0.22\"},{\"kind\":\"dev\",\"name\":\"bencher\",\"req\":\"^0.1.5\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"brotli\",\"optional\":true,\"req\":\"^8\"},{\"name\":\"brotli-decompressor\",\"optional\":true,\"req\":\"^5.0.0\"},{\"kind\":\"dev\",\"name\":\"env_logger\",\"req\":\"^0.11\"},{\"default_features\":false,\"features\":[\"default-hasher\",\"inline-more\"],\"name\":\"hashbrown\",\"optional\":true,\"req\":\"^0.15\"},{\"kind\":\"dev\",\"name\":\"hex\",\"req\":\"^0.4\"},{\"name\":\"log\",\"optional\":true,\"req\":\"^0.4.8\"},{\"kind\":\"dev\",\"name\":\"log\",\"req\":\"^0.4.8\"},{\"kind\":\"dev\",\"name\":\"macro_rules_attribute\",\"req\":\"^0.2\"},{\"kind\":\"dev\",\"name\":\"num-bigint\",\"req\":\"^0.4.4\"},{\"default_features\":false,\"features\":[\"alloc\",\"race\"],\"name\":\"once_cell\",\"req\":\"^1.16\"},{\"features\":[\"alloc\"],\"name\":\"pki-types\",\"package\":\"rustls-pki-types\",\"req\":\"^1.12\"},{\"default_features\":false,\"features\":[\"pem\",\"aws_lc_rs\"],\"kind\":\"dev\",\"name\":\"rcgen\",\"req\":\"^0.14\"},{\"name\":\"ring\",\"optional\":true,\"req\":\"^0.17\"},{\"kind\":\"build\",\"name\":\"rustversion\",\"optional\":true,\"req\":\"^1.0.6\"},{\"features\":[\"derive\"],\"kind\":\"dev\",\"name\":\"serde\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1\"},{\"default_features\":false,\"name\":\"subtle\",\"req\":\"^2.5.0\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"time\",\"req\":\"^0.3.6\"},{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"webpki\",\"package\":\"rustls-webpki\",\"req\":\"^0.103.5\"},{\"kind\":\"dev\",\"name\":\"webpki-roots\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"x509-parser\",\"req\":\"^0.17\"},{\"name\":\"zeroize\",\"req\":\"^1.8\"},{\"name\":\"zlib-rs\",\"optional\":true,\"req\":\"^0.6\"}],\"features\":{\"aws-lc-rs\":[\"aws_lc_rs\"],\"aws_lc_rs\":[\"dep:aws-lc-rs\",\"webpki/aws-lc-rs\",\"aws-lc-rs/aws-lc-sys\",\"aws-lc-rs/prebuilt-nasm\"],\"brotli\":[\"dep:brotli\",\"dep:brotli-decompressor\",\"std\"],\"custom-provider\":[],\"default\":[\"aws_lc_rs\",\"logging\",\"prefer-post-quantum\",\"std\",\"tls12\"],\"fips\":[\"aws_lc_rs\",\"aws-lc-rs?/fips\",\"webpki/aws-lc-rs-fips\"],\"logging\":[\"log\"],\"prefer-post-quantum\":[\"aws_lc_rs\"],\"read_buf\":[\"rustversion\",\"std\"],\"ring\":[\"dep:ring\",\"webpki/ring\"],\"std\":[\"webpki/std\",\"pki-types/std\",\"once_cell/std\"],\"tls12\":[],\"zlib\":[\"dep:zlib-rs\"]}}", @@ -1527,7 +1521,6 @@ "signature_2.2.0": "{\"dependencies\":[{\"name\":\"derive\",\"optional\":true,\"package\":\"signature_derive\",\"req\":\"^2\"},{\"default_features\":false,\"name\":\"digest\",\"optional\":true,\"req\":\"^0.10.6\"},{\"kind\":\"dev\",\"name\":\"hex-literal\",\"req\":\"^0.4\"},{\"default_features\":false,\"name\":\"rand_core\",\"optional\":true,\"req\":\"^0.6.4\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"sha2\",\"req\":\"^0.10\"}],\"features\":{\"alloc\":[],\"std\":[\"alloc\",\"rand_core?/std\"]}}", "simd-adler32_0.3.8": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"adler\",\"req\":\"^1.0.2\"},{\"kind\":\"dev\",\"name\":\"adler32\",\"req\":\"^1.2.0\"},{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.3\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.8\"}],\"features\":{\"const-generics\":[],\"default\":[\"std\",\"const-generics\"],\"nightly\":[],\"std\":[]}}", "simd-adler32_0.3.9": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"adler\",\"req\":\"^1.0.2\"},{\"kind\":\"dev\",\"name\":\"adler32\",\"req\":\"^1.2.0\"},{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.3\"},{\"features\":[\"small_rng\"],\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.8\"}],\"features\":{\"const-generics\":[],\"default\":[\"std\",\"const-generics\"],\"nightly\":[],\"std\":[]}}", - "simd_cesu8_1.1.1": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"cesu8\",\"req\":\"^1.1.0\"},{\"features\":[\"html_reports\"],\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.5.1\"},{\"kind\":\"build\",\"name\":\"rustc_version\",\"req\":\"^0.4.0\"},{\"default_features\":false,\"name\":\"simdutf8\",\"req\":\"^0.1.4\"}],\"features\":{\"bench\":[],\"default\":[\"std\"],\"nightly\":[],\"std\":[\"simdutf8/std\"]}}", "simdutf8_0.1.5": "{\"dependencies\":[],\"features\":{\"aarch64_neon\":[],\"aarch64_neon_prefetch\":[],\"default\":[\"std\"],\"hints\":[],\"public_imp\":[],\"std\":[]}}", "similar_2.7.0": "{\"dependencies\":[{\"default_features\":false,\"name\":\"bstr\",\"optional\":true,\"req\":\"^1.5.0\"},{\"kind\":\"dev\",\"name\":\"console\",\"req\":\"^0.15.0\"},{\"kind\":\"dev\",\"name\":\"insta\",\"req\":\"^1.10.0\"},{\"features\":[\"derive\"],\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0.130\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0.68\"},{\"name\":\"unicode-segmentation\",\"optional\":true,\"req\":\"^1.7.1\"},{\"name\":\"web-time\",\"optional\":true,\"req\":\"^1.1\"}],\"features\":{\"bytes\":[\"bstr\",\"text\"],\"default\":[\"text\"],\"inline\":[\"text\"],\"text\":[],\"unicode\":[\"text\",\"unicode-segmentation\",\"bstr?/unicode\",\"bstr?/std\"],\"wasm32_web_time\":[\"web-time\"]}}", "simple_asn1_0.6.4": "{\"dependencies\":[{\"default_features\":false,\"name\":\"num-bigint\",\"req\":\"^0.4\"},{\"default_features\":false,\"name\":\"num-traits\",\"req\":\"^0.2\"},{\"kind\":\"dev\",\"name\":\"quickcheck\",\"req\":\"^1.0.3\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.8.4\"},{\"default_features\":false,\"name\":\"thiserror\",\"req\":\"^2\"},{\"default_features\":false,\"features\":[\"formatting\",\"macros\",\"parsing\"],\"name\":\"time\",\"req\":\"^0.3.47\"},{\"default_features\":false,\"features\":[\"formatting\",\"macros\",\"parsing\",\"quickcheck\"],\"kind\":\"dev\",\"name\":\"time\",\"req\":\"^0.3\"}],\"features\":{}}", diff --git a/codex-rs/Cargo.lock b/codex-rs/Cargo.lock index 6ca0e133bd..6810d2c3da 100644 --- a/codex-rs/Cargo.lock +++ b/codex-rs/Cargo.lock @@ -3858,11 +3858,11 @@ dependencies = [ "codex-utils-path-uri", "codex-utils-pty", "futures", + "http 1.4.0", "keyring", "memchr", "oauth2", "pretty_assertions", - "reqwest 0.13.4", "rmcp", "serde", "serde_json", @@ -3874,6 +3874,7 @@ dependencies = [ "tiny_http", "tokio", "tracing", + "url", "urlencoding", "webbrowser", "which 8.0.0", @@ -8487,68 +8488,19 @@ dependencies = [ "cesu8", "cfg-if", "combine", - "jni-sys 0.3.0", + "jni-sys", "log", "thiserror 1.0.69", "walkdir", "windows-sys 0.45.0", ] -[[package]] -name = "jni" -version = "0.22.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498" -dependencies = [ - "cfg-if", - "combine", - "jni-macros", - "jni-sys 0.4.1", - "log", - "simd_cesu8", - "thiserror 2.0.18", - "walkdir", - "windows-link", -] - -[[package]] -name = "jni-macros" -version = "0.22.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3" -dependencies = [ - "proc-macro2", - "quote", - "rustc_version", - "simd_cesu8", - "syn 2.0.117", -] - [[package]] name = "jni-sys" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" -[[package]] -name = "jni-sys" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2" -dependencies = [ - "jni-sys-macros", -] - -[[package]] -name = "jni-sys-macros" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" -dependencies = [ - "quote", - "syn 2.0.117", -] - [[package]] name = "jobserver" version = "0.1.34" @@ -10488,7 +10440,6 @@ version = "0.11.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "434b42fec591c96ef50e21e886936e66d3cc3f737104fdb9b737c40ffb94c098" dependencies = [ - "aws-lc-rs", "bytes", "getrandom 0.3.4", "lru-slab", @@ -11177,21 +11128,15 @@ dependencies = [ "http-body 1.0.1", "http-body-util", "hyper", - "hyper-rustls", "hyper-util", "js-sys", "log", "percent-encoding", "pin-project-lite", - "quinn", - "rustls", - "rustls-pki-types", - "rustls-platform-verifier", "serde", "serde_json", "sync_wrapper", "tokio", - "tokio-rustls", "tokio-util", "tower", "tower-http", @@ -11440,33 +11385,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "rustls-platform-verifier" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26d1e2536ce4f35f4846aa13bff16bd0ff40157cdb14cc056c7b14ba41233ba0" -dependencies = [ - "core-foundation 0.10.1", - "core-foundation-sys", - "jni 0.22.4", - "log", - "once_cell", - "rustls", - "rustls-native-certs", - "rustls-platform-verifier-android", - "rustls-webpki", - "security-framework 3.5.1", - "security-framework-sys", - "webpki-root-certs", - "windows-sys 0.61.2", -] - -[[package]] -name = "rustls-platform-verifier-android" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" - [[package]] name = "rustls-webpki" version = "0.103.13" @@ -12267,16 +12185,6 @@ version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" -[[package]] -name = "simd_cesu8" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94f90157bb87cddf702797c5dadfa0be7d266cdf49e22da2fcaa32eff75b2c33" -dependencies = [ - "rustc_version", - "simdutf8", -] - [[package]] name = "simdutf8" version = "0.1.5" @@ -14527,7 +14435,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "00f1243ef785213e3a32fa0396093424a3a6ea566f9948497e5a2309261a4c97" dependencies = [ "core-foundation 0.10.1", - "jni 0.21.1", + "jni", "log", "ndk-context", "objc2", diff --git a/codex-rs/app-server/src/request_processors.rs b/codex-rs/app-server/src/request_processors.rs index 62ec16992e..9afaf18ede 100644 --- a/codex-rs/app-server/src/request_processors.rs +++ b/codex-rs/app-server/src/request_processors.rs @@ -409,7 +409,7 @@ use codex_mcp::McpRuntimeContext; use codex_mcp::McpServerStatusSnapshot; use codex_mcp::McpSnapshotDetail; use codex_mcp::collect_mcp_server_status_snapshot_with_detail; -use codex_mcp::discover_supported_scopes_with_http_client; +use codex_mcp::discover_supported_scopes; use codex_mcp::read_mcp_resource as read_mcp_resource_without_thread; use codex_mcp::resolve_oauth_scopes; use codex_memories_write::clear_memory_roots_contents; @@ -457,7 +457,7 @@ use codex_protocol::protocol::W3cTraceContext; use codex_protocol::protocol::strip_user_message_prefix; use codex_protocol::user_input::MAX_USER_INPUT_TEXT_CHARS; use codex_protocol::user_input::UserInput as CoreInputItem; -use codex_rmcp_client::perform_oauth_login_return_url_with_http_client; +use codex_rmcp_client::perform_oauth_login_return_url; use codex_rollout::is_persisted_rollout_item; use codex_rollout::state_db::StateDbHandle; use codex_rollout::state_db::reconcile_rollout; diff --git a/codex-rs/app-server/src/request_processors/mcp_processor.rs b/codex-rs/app-server/src/request_processors/mcp_processor.rs index a81b2758fc..0751f26e16 100644 --- a/codex-rs/app-server/src/request_processors/mcp_processor.rs +++ b/codex-rs/app-server/src/request_processors/mcp_processor.rs @@ -172,7 +172,7 @@ impl McpRequestProcessor { })?; let discovered_scopes = if scopes.is_none() && server.scopes.is_none() { - discover_supported_scopes_with_http_client( + discover_supported_scopes( &server.transport, Arc::clone(&http_client), codex_rmcp_client::OAuthDiscoveryTimeout::Requested, @@ -184,7 +184,7 @@ impl McpRequestProcessor { let resolved_scopes = resolve_oauth_scopes(scopes, server.scopes.clone(), discovered_scopes); - let handle = perform_oauth_login_return_url_with_http_client( + let handle = perform_oauth_login_return_url( &name, &url, mcp_config.mcp_oauth_credentials_store_mode, diff --git a/codex-rs/app-server/src/request_processors/plugins.rs b/codex-rs/app-server/src/request_processors/plugins.rs index a77c64d339..1c47cba748 100644 --- a/codex-rs/app-server/src/request_processors/plugins.rs +++ b/codex-rs/app-server/src/request_processors/plugins.rs @@ -26,7 +26,7 @@ use codex_core_plugins::remote::validate_remote_plugin_id; use codex_core_plugins::remote_bundle::RemotePluginBundleInstallError; use codex_mcp::McpOAuthLoginSupport; use codex_mcp::McpRuntimeContext; -use codex_mcp::oauth_login_support_with_http_client; +use codex_mcp::oauth_login_support; use codex_mcp::should_retry_without_scopes; use codex_plugin::PluginId; use codex_plugin::PluginTelemetryMetadata; @@ -1888,7 +1888,7 @@ impl PluginRequestProcessor { continue; } }; - let login_support = oauth_login_support_with_http_client( + let login_support = oauth_login_support( &server.transport, Arc::clone(&http_client), OAuthDiscoveryTimeout::LOCAL, diff --git a/codex-rs/cli/src/mcp_cmd.rs b/codex-rs/cli/src/mcp_cmd.rs index 7857edf670..7571b116db 100644 --- a/codex-rs/cli/src/mcp_cmd.rs +++ b/codex-rs/cli/src/mcp_cmd.rs @@ -26,8 +26,8 @@ use codex_mcp::McpOAuthLoginSupport; use codex_mcp::McpRuntimeContext; use codex_mcp::ResolvedMcpOAuthScopes; use codex_mcp::compute_auth_statuses; -use codex_mcp::discover_supported_scopes_with_http_client; -use codex_mcp::oauth_login_support_with_http_client; +use codex_mcp::discover_supported_scopes; +use codex_mcp::oauth_login_support; use codex_mcp::resolve_oauth_scopes; use codex_mcp::should_retry_without_scopes; use codex_protocol::protocol::McpAuthStatus; @@ -391,7 +391,7 @@ async fn run_add(config_overrides: &CliConfigOverrides, add_args: AddArgs) -> Re // OAuth discovery uses the local route-aware HTTP client. let http_client: Arc = Arc::new(RouteAwareHttpClient::new(config.http_client_factory())); - let login_support = oauth_login_support_with_http_client( + let login_support = oauth_login_support( &transport, Arc::clone(&http_client), OAuthDiscoveryTimeout::LOCAL, @@ -502,7 +502,7 @@ async fn run_login(config_overrides: &CliConfigOverrides, login_args: LoginArgs) Arc::new(RouteAwareHttpClient::new(config.http_client_factory())); let explicit_scopes = (!scopes.is_empty()).then_some(scopes); let discovered_scopes = if explicit_scopes.is_none() && server.scopes.is_none() { - discover_supported_scopes_with_http_client( + discover_supported_scopes( &server.transport, Arc::clone(&http_client), OAuthDiscoveryTimeout::LOCAL, diff --git a/codex-rs/codex-mcp/src/lib.rs b/codex-rs/codex-mcp/src/lib.rs index 5138018873..3a7265a347 100644 --- a/codex-rs/codex-mcp/src/lib.rs +++ b/codex-rs/codex-mcp/src/lib.rs @@ -72,9 +72,7 @@ pub use mcp::McpOAuthScopesSource; pub use mcp::ResolvedMcpOAuthScopes; pub use mcp::compute_auth_statuses; pub use mcp::discover_supported_scopes; -pub use mcp::discover_supported_scopes_with_http_client; pub use mcp::oauth_login_support; -pub use mcp::oauth_login_support_with_http_client; pub use mcp::resolve_oauth_scopes; pub use mcp::should_retry_without_scopes; diff --git a/codex-rs/codex-mcp/src/mcp/auth.rs b/codex-rs/codex-mcp/src/mcp/auth.rs index f79064a14a..1a9863190a 100644 --- a/codex-rs/codex-mcp/src/mcp/auth.rs +++ b/codex-rs/codex-mcp/src/mcp/auth.rs @@ -12,9 +12,8 @@ use codex_login::CodexAuth; use codex_rmcp_client::McpAuthState; use codex_rmcp_client::OAuthDiscoveryTimeout; use codex_rmcp_client::OAuthProviderError; -use codex_rmcp_client::determine_streamable_http_auth_status_with_http_client; +use codex_rmcp_client::determine_streamable_http_auth_status; use codex_rmcp_client::discover_streamable_http_oauth; -use codex_rmcp_client::discover_streamable_http_oauth_with_http_client; use futures::FutureExt; use futures::future::join_all; use tracing::warn; @@ -57,27 +56,7 @@ pub struct McpAuthStatusEntry { pub auth_state: McpAuthState, } -pub async fn oauth_login_support(transport: &McpServerTransportConfig) -> McpOAuthLoginSupport { - let Some(mut config) = oauth_login_candidate(transport) else { - return McpOAuthLoginSupport::Unsupported; - }; - match discover_streamable_http_oauth( - &config.url, - config.http_headers.clone(), - config.env_http_headers.clone(), - ) - .await - { - Ok(Some(discovery)) => { - config.discovered_scopes = discovery.scopes_supported; - McpOAuthLoginSupport::Supported(config) - } - Ok(None) => McpOAuthLoginSupport::Unsupported, - Err(err) => McpOAuthLoginSupport::Unknown(err), - } -} - -pub async fn oauth_login_support_with_http_client( +pub async fn oauth_login_support( transport: &McpServerTransportConfig, http_client: Arc, discovery_timeout: OAuthDiscoveryTimeout, @@ -85,7 +64,7 @@ pub async fn oauth_login_support_with_http_client( let Some(mut config) = oauth_login_candidate(transport) else { return McpOAuthLoginSupport::Unsupported; }; - match discover_streamable_http_oauth_with_http_client( + match discover_streamable_http_oauth( &config.url, config.http_headers.clone(), config.env_http_headers.clone(), @@ -126,19 +105,10 @@ fn oauth_login_candidate(transport: &McpServerTransportConfig) -> Option Option> { - match oauth_login_support(transport).await { - McpOAuthLoginSupport::Supported(config) => config.discovered_scopes, - McpOAuthLoginSupport::Unsupported | McpOAuthLoginSupport::Unknown(_) => None, - } -} - -pub async fn discover_supported_scopes_with_http_client( - transport: &McpServerTransportConfig, http_client: Arc, discovery_timeout: OAuthDiscoveryTimeout, ) -> Option> { - match oauth_login_support_with_http_client(transport, http_client, discovery_timeout).await { + match oauth_login_support(transport, http_client, discovery_timeout).await { McpOAuthLoginSupport::Supported(config) => config.discovered_scopes, McpOAuthLoginSupport::Unsupported | McpOAuthLoginSupport::Unknown(_) => None, } @@ -266,7 +236,7 @@ async fn compute_auth_status( } else { OAuthDiscoveryTimeout::Requested }; - determine_streamable_http_auth_status_with_http_client( + determine_streamable_http_auth_status( server_name, url, bearer_token_env_var.as_deref(), diff --git a/codex-rs/codex-mcp/src/mcp/mod.rs b/codex-rs/codex-mcp/src/mcp/mod.rs index ad269671a1..b8422e1e74 100644 --- a/codex-rs/codex-mcp/src/mcp/mod.rs +++ b/codex-rs/codex-mcp/src/mcp/mod.rs @@ -5,9 +5,7 @@ pub use auth::McpOAuthScopesSource; pub use auth::ResolvedMcpOAuthScopes; pub use auth::compute_auth_statuses; pub use auth::discover_supported_scopes; -pub use auth::discover_supported_scopes_with_http_client; pub use auth::oauth_login_support; -pub use auth::oauth_login_support_with_http_client; pub use auth::resolve_oauth_scopes; pub use auth::should_retry_without_scopes; diff --git a/codex-rs/core/src/mcp_skill_dependencies.rs b/codex-rs/core/src/mcp_skill_dependencies.rs index 2f3bfbc909..dc6d41c351 100644 --- a/codex-rs/core/src/mcp_skill_dependencies.rs +++ b/codex-rs/core/src/mcp_skill_dependencies.rs @@ -25,7 +25,7 @@ use codex_mcp::ElicitationReviewerHandle; use codex_mcp::McpOAuthLoginSupport; use codex_mcp::McpPermissionPromptAutoApproveContext; use codex_mcp::mcp_permission_prompt_is_auto_approved; -use codex_mcp::oauth_login_support_with_http_client; +use codex_mcp::oauth_login_support; use codex_mcp::resolve_oauth_scopes; use codex_mcp::should_retry_without_scopes; @@ -148,7 +148,7 @@ pub(crate) async fn maybe_install_mcp_dependencies( } else { OAuthDiscoveryTimeout::Requested }; - let login_support = oauth_login_support_with_http_client( + let login_support = oauth_login_support( &server_config.transport, Arc::clone(&http_client), discovery_timeout, diff --git a/codex-rs/deny.toml b/codex-rs/deny.toml index 8f235a864d..39899a35bd 100644 --- a/codex-rs/deny.toml +++ b/codex-rs/deny.toml @@ -245,7 +245,6 @@ deny = [ "codex-otel", "codex-protocol", "codex-responses-api-proxy", - "codex-rmcp-client", "codex-tui", # Third-party crates that own their reqwest integration. These are not part of the # first-party migration count above. diff --git a/codex-rs/rmcp-client/Cargo.toml b/codex-rs/rmcp-client/Cargo.toml index 7dd513b64d..43d735ef1b 100644 --- a/codex-rs/rmcp-client/Cargo.toml +++ b/codex-rs/rmcp-client/Cargo.toml @@ -27,14 +27,10 @@ codex-utils-pty = { workspace = true } codex-utils-home-dir = { workspace = true } bytes = { workspace = true } futures = { workspace = true, default-features = false, features = ["std"] } +http = { workspace = true } keyring = { workspace = true, features = ["crypto-rust"] } memchr = { workspace = true } oauth2 = "5" -reqwest = { version = "0.13", default-features = false, features = [ - "json", - "stream", - "rustls", -] } rmcp = { workspace = true, default-features = false, features = [ "auth", "base64", @@ -44,7 +40,7 @@ rmcp = { workspace = true, default-features = false, features = [ "server", "transport-async-rw", "transport-child-process", - "transport-streamable-http-client-reqwest", + "transport-streamable-http-client", "transport-streamable-http-server", ] } serde = { workspace = true, features = ["derive"] } @@ -63,11 +59,13 @@ tokio = { workspace = true, features = [ "time", ] } tracing = { workspace = true, features = ["log"] } +url = { workspace = true } urlencoding = { workspace = true } webbrowser = { workspace = true } which = { workspace = true } [dev-dependencies] +codex-http-client = { workspace = true } codex-utils-cargo-bin = { workspace = true } pretty_assertions = { workspace = true } serial_test = { workspace = true } diff --git a/codex-rs/rmcp-client/src/auth_status.rs b/codex-rs/rmcp-client/src/auth_status.rs index 8b0d48e065..7ccc9e0442 100644 --- a/codex-rs/rmcp-client/src/auth_status.rs +++ b/codex-rs/rmcp-client/src/auth_status.rs @@ -4,14 +4,10 @@ use std::time::Duration; use anyhow::Result; use codex_exec_server::HttpClient; -use codex_exec_server::RouteAwareHttpClient; -use codex_http_client::HttpClientFactory; -use codex_http_client::OutboundProxyPolicy; use codex_protocol::protocol::McpAuthStatus; use futures::FutureExt; -use reqwest::Client; -use reqwest::header::AUTHORIZATION; -use reqwest::header::HeaderMap; +use http::HeaderMap; +use http::header::AUTHORIZATION; use rmcp::transport::AuthorizationManager; use rmcp::transport::auth::AuthError; use tracing::debug; @@ -74,40 +70,10 @@ enum AuthStatusCheck { Discover(HeaderMap), } -/// Determine the authentication status for a streamable HTTP MCP server. -pub async fn determine_streamable_http_auth_status( - server_name: &str, - url: &str, - bearer_token_env_var: Option<&str>, - http_headers: Option>, - env_http_headers: Option>, - store_mode: OAuthCredentialsStoreMode, - keyring_backend_kind: AuthKeyringBackendKind, -) -> Result { - let default_headers = match auth_status_before_discovery( - server_name, - url, - bearer_token_env_var, - http_headers, - env_http_headers, - store_mode, - keyring_backend_kind, - )? { - AuthStatusCheck::Complete(status) => return Ok(status), - AuthStatusCheck::Discover(default_headers) => default_headers, - }; - - determine_auth_status_from_discovery( - server_name, - url, - discover_streamable_http_oauth_with_headers(url, &default_headers).await, - ) -} - /// Determine authentication status while routing OAuth discovery through the /// provided HTTP client. #[allow(clippy::too_many_arguments)] -pub async fn determine_streamable_http_auth_status_with_http_client( +pub async fn determine_streamable_http_auth_status( server_name: &str, url: &str, bearer_token_env_var: Option<&str>, @@ -219,28 +185,10 @@ fn determine_auth_status_from_discovery( } } -/// Attempt to determine whether a streamable HTTP MCP server advertises OAuth login. -pub async fn supports_oauth_login(url: &str) -> Result { - Ok(discover_streamable_http_oauth( - url, /*http_headers*/ None, /*env_http_headers*/ None, - ) - .await? - .is_some()) -} - pub async fn discover_streamable_http_oauth( url: &str, http_headers: Option>, env_http_headers: Option>, -) -> Result> { - let default_headers = build_default_headers(http_headers, env_http_headers)?; - discover_streamable_http_oauth_with_headers(url, &default_headers).await -} - -pub async fn discover_streamable_http_oauth_with_http_client( - url: &str, - http_headers: Option>, - env_http_headers: Option>, http_client: Arc, discovery_timeout: OAuthDiscoveryTimeout, ) -> Result> { @@ -254,32 +202,6 @@ pub async fn discover_streamable_http_oauth_with_http_client( .await } -async fn discover_streamable_http_oauth_with_headers( - url: &str, - default_headers: &HeaderMap, -) -> Result> { - // Use no_proxy to avoid a bug in the system-configuration crate that - // can result in a panic. See #8912. - let client = Client::builder() - .timeout(DISCOVERY_TIMEOUT) - .no_proxy() - .redirect(reqwest::redirect::Policy::none()) - .build()?; - let oauth_http_client = Arc::new( - OAuthHttpClientAdapter::new( - Arc::new(RouteAwareHttpClient::new(HttpClientFactory::new( - OutboundProxyPolicy::ReqwestDefault, - ))), - default_headers.clone(), - ) - .with_local_http_client(client), - ); - let mut authorization_manager = - AuthorizationManager::new_with_oauth_http_client(url, oauth_http_client).await?; - authorization_manager.set_allow_missing_issuer(true); - discover_streamable_http_oauth_with_manager(&authorization_manager).await -} - async fn discover_streamable_http_oauth_with_headers_and_http_client( url: &str, default_headers: HeaderMap, @@ -347,6 +269,9 @@ mod tests { use codex_exec_server::HttpRequestParams; use codex_exec_server::HttpRequestResponse; use codex_exec_server::HttpResponseBodyStream; + use codex_exec_server::RouteAwareHttpClient; + use codex_http_client::HttpClientFactory; + use codex_http_client::OutboundProxyPolicy; use futures::future::BoxFuture; use pretty_assertions::assert_eq; use serial_test::serial; @@ -366,6 +291,12 @@ mod tests { handle: JoinHandle<()>, } + fn test_http_client() -> Arc { + Arc::new(RouteAwareHttpClient::new(HttpClientFactory::new( + OutboundProxyPolicy::ReqwestDefault, + ))) + } + impl Drop for TestServer { fn drop(&mut self) { self.handle.abort(); @@ -374,6 +305,7 @@ mod tests { #[derive(Default)] struct RecordingHttpClient { + headers: Mutex>>, timeout_ms: Mutex>>, } @@ -394,6 +326,16 @@ mod tests { params: HttpRequestParams, ) -> BoxFuture<'_, Result<(HttpRequestResponse, HttpResponseBodyStream), ExecServerError>> { + *self + .headers + .lock() + .expect("header recorder lock should not be poisoned") = Some( + params + .headers + .iter() + .map(|header| (header.name.clone(), header.value.clone())) + .collect(), + ); *self .timeout_ms .lock() @@ -482,6 +424,8 @@ mod tests { /*env_http_headers*/ None, OAuthCredentialsStoreMode::Keyring, AuthKeyringBackendKind::default(), + test_http_client(), + OAuthDiscoveryTimeout::Requested, ) .await .expect("status should compute"); @@ -504,6 +448,8 @@ mod tests { )])), OAuthCredentialsStoreMode::Keyring, AuthKeyringBackendKind::default(), + test_http_client(), + OAuthDiscoveryTimeout::Requested, ) .await .expect("status should compute"); @@ -553,7 +499,11 @@ mod tests { }); let url = format!("http://{address}/mcp"); let discovery = discover_streamable_http_oauth( - &url, /*http_headers*/ None, /*env_http_headers*/ None, + &url, + /*http_headers*/ None, + /*env_http_headers*/ None, + test_http_client(), + OAuthDiscoveryTimeout::LOCAL, ) .await; assert_eq!( @@ -594,6 +544,8 @@ mod tests { "sensitive-key".to_string(), )])), /*env_http_headers*/ None, + test_http_client(), + OAuthDiscoveryTimeout::LOCAL, ) .await .expect("discovery should complete without following the redirect"); @@ -616,6 +568,8 @@ mod tests { &server.url, /*http_headers*/ None, /*env_http_headers*/ None, + test_http_client(), + OAuthDiscoveryTimeout::LOCAL, ) .await .expect("discovery should succeed") @@ -631,7 +585,7 @@ mod tests { async fn routed_oauth_discovery_caps_local_discovery_timeout() { let http_client = Arc::new(RecordingHttpClient::default()); - let discovery = discover_streamable_http_oauth_with_http_client( + let discovery = discover_streamable_http_oauth( "http://example.com/mcp", /*http_headers*/ None, /*env_http_headers*/ None, @@ -657,7 +611,7 @@ mod tests { async fn routed_oauth_discovery_preserves_requested_timeout() { let http_client = Arc::new(RecordingHttpClient::default()); - let discovery = discover_streamable_http_oauth_with_http_client( + let discovery = discover_streamable_http_oauth( "http://example.com/mcp", /*http_headers*/ None, /*env_http_headers*/ None, @@ -676,6 +630,38 @@ mod tests { ); } + #[tokio::test] + async fn routed_oauth_discovery_preserves_configured_headers() { + let http_client = Arc::new(RecordingHttpClient::default()); + + let discovery = discover_streamable_http_oauth( + "http://example.com/mcp", + Some(HashMap::from([( + "X-Mcp-Discovery".to_string(), + "configured-value".to_string(), + )])), + /*env_http_headers*/ None, + http_client.clone(), + OAuthDiscoveryTimeout::LOCAL, + ) + .await; + + assert!(matches!(discovery, Ok(None))); + let headers = http_client + .headers + .lock() + .expect("header recorder lock should not be poisoned") + .clone() + .expect("discovery should issue an HTTP request"); + assert_eq!( + headers + .iter() + .find(|(name, _)| name.eq_ignore_ascii_case("x-mcp-discovery")) + .map(|(_, value)| value.as_str()), + Some("configured-value") + ); + } + #[tokio::test] async fn discover_streamable_http_oauth_follows_protected_resource_metadata() { let authorization_server = spawn_oauth_discovery_server(serde_json::json!({ @@ -724,6 +710,8 @@ mod tests { &resource_server.url, /*http_headers*/ None, /*env_http_headers*/ None, + test_http_client(), + OAuthDiscoveryTimeout::LOCAL, ) .await .expect("discovery should succeed") @@ -748,6 +736,8 @@ mod tests { &server.url, /*http_headers*/ None, /*env_http_headers*/ None, + test_http_client(), + OAuthDiscoveryTimeout::LOCAL, ) .await .expect("discovery should succeed") @@ -764,9 +754,16 @@ mod tests { })) .await; - let supported = supports_oauth_login(&server.url) - .await - .expect("support check should succeed"); + let supported = discover_streamable_http_oauth( + &server.url, + /*http_headers*/ None, + /*env_http_headers*/ None, + test_http_client(), + OAuthDiscoveryTimeout::LOCAL, + ) + .await + .expect("support check should succeed") + .is_some(); assert!(supported); } diff --git a/codex-rs/rmcp-client/src/http_client_adapter.rs b/codex-rs/rmcp-client/src/http_client_adapter.rs index db0c579a9b..22998c10c8 100644 --- a/codex-rs/rmcp-client/src/http_client_adapter.rs +++ b/codex-rs/rmcp-client/src/http_client_adapter.rs @@ -22,12 +22,14 @@ use codex_exec_server::HttpResponseBodyStream; use futures::StreamExt; use futures::stream; use futures::stream::BoxStream; -use reqwest::StatusCode; -use reqwest::header::ACCEPT; -use reqwest::header::AUTHORIZATION; -use reqwest::header::CONTENT_TYPE; -use reqwest::header::HeaderMap; -use reqwest::header::HeaderName; +use http::HeaderMap; +use http::HeaderName; +use http::HeaderValue; +use http::StatusCode; +use http::header::ACCEPT; +use http::header::AUTHORIZATION; +use http::header::CONTENT_TYPE; +use http::header::WWW_AUTHENTICATE; use rmcp::model::ClientJsonRpcMessage; use rmcp::model::ClientNotification; use rmcp::model::ConstString; @@ -102,7 +104,7 @@ impl StreamableHttpClient for StreamableHttpClientAdapter { message: ClientJsonRpcMessage, session_id: Option>, auth_token: Option, - custom_headers: HashMap, + custom_headers: HashMap, ) -> std::result::Result> { let (mcp_method, mcp_request_id) = client_jsonrpc_message_fields(&message); let has_session_id = session_id.is_some(); @@ -187,8 +189,7 @@ impl StreamableHttpClient for StreamableHttpClientAdapter { )); } if response.status == StatusCode::UNAUTHORIZED.as_u16() - && let Some(header) = - response_header(&response.headers, reqwest::header::WWW_AUTHENTICATE) + && let Some(header) = response_header(&response.headers, WWW_AUTHENTICATE) { return Err(StreamableHttpError::AuthRequired(AuthRequiredError::new( header, @@ -324,7 +325,7 @@ impl StreamableHttpClient for StreamableHttpClientAdapter { uri: Arc, session: Arc, auth_token: Option, - custom_headers: HashMap, + custom_headers: HashMap, ) -> std::result::Result<(), StreamableHttpError> { let mut headers = self.default_headers.clone(); headers.extend(custom_headers); @@ -378,7 +379,7 @@ impl StreamableHttpClient for StreamableHttpClientAdapter { session_id: Option>, last_event_id: Option, auth_token: Option, - custom_headers: HashMap, + custom_headers: HashMap, ) -> std::result::Result< BoxStream<'static, std::result::Result>, StreamableHttpError, @@ -535,19 +536,19 @@ fn log_post_message_http_error( has_session_id: bool, has_authorization_header: bool, ) { - let parsed_url = reqwest::Url::parse(uri).ok(); + let parsed_url = url::Url::parse(uri).ok(); tracing::warn!( endpoint_scheme = parsed_url .as_ref() - .map(reqwest::Url::scheme) + .map(url::Url::scheme) .unwrap_or(""), endpoint_host = parsed_url .as_ref() - .and_then(reqwest::Url::host_str) + .and_then(url::Url::host_str) .unwrap_or(""), endpoint_path = parsed_url .as_ref() - .map(reqwest::Url::path) + .map(url::Url::path) .unwrap_or(""), endpoint_has_query = parsed_url.as_ref().is_some_and(|url| url.query().is_some()), mcp_method = mcp_method.unwrap_or(""), @@ -567,7 +568,7 @@ fn insert_header( where Error: std::error::Error + Send + Sync + 'static, { - let value = reqwest::header::HeaderValue::from_str(&value) + let value = HeaderValue::from_str(&value) .map_err(|error| StreamableHttpError::Client(map_error(error.to_string())))?; headers.insert(name, value); Ok(()) diff --git a/codex-rs/rmcp-client/src/http_client_adapter/www_authenticate.rs b/codex-rs/rmcp-client/src/http_client_adapter/www_authenticate.rs index 3c122c3bb7..374d11d067 100644 --- a/codex-rs/rmcp-client/src/http_client_adapter/www_authenticate.rs +++ b/codex-rs/rmcp-client/src/http_client_adapter/www_authenticate.rs @@ -1,5 +1,5 @@ use codex_exec_server::HttpHeader; -use reqwest::header::WWW_AUTHENTICATE; +use http::header::WWW_AUTHENTICATE; #[derive(Debug, PartialEq, Eq)] pub(super) struct InsufficientScopeChallenge { diff --git a/codex-rs/rmcp-client/src/lib.rs b/codex-rs/rmcp-client/src/lib.rs index 5febefe281..0ed0f5d054 100644 --- a/codex-rs/rmcp-client/src/lib.rs +++ b/codex-rs/rmcp-client/src/lib.rs @@ -22,10 +22,7 @@ pub use auth_status::OAuthDiscoveryTimeout; pub use auth_status::StreamableHttpOAuthDiscovery; pub use auth_status::determine_streamable_http_auth_status; pub use auth_status::determine_streamable_http_auth_status_from_credentials; -pub use auth_status::determine_streamable_http_auth_status_with_http_client; pub use auth_status::discover_streamable_http_oauth; -pub use auth_status::discover_streamable_http_oauth_with_http_client; -pub use auth_status::supports_oauth_login; pub use codex_protocol::protocol::McpAuthStatus; pub use in_process_transport::InProcessTransportFactory; pub use oauth::StoredOAuthTokens; @@ -37,7 +34,6 @@ pub use perform_oauth_login::OAuthProviderError; pub use perform_oauth_login::OauthLoginHandle; pub use perform_oauth_login::perform_oauth_login; pub use perform_oauth_login::perform_oauth_login_return_url; -pub use perform_oauth_login::perform_oauth_login_return_url_with_http_client; pub use perform_oauth_login::perform_oauth_login_silent; pub use protocol_mode::McpProtocolMode; pub use rmcp::model::ElicitationAction; diff --git a/codex-rs/rmcp-client/src/oauth/tests/persistor_tests.rs b/codex-rs/rmcp-client/src/oauth/tests/persistor_tests.rs index 7f8a62a5ec..a0bfb25d64 100644 --- a/codex-rs/rmcp-client/src/oauth/tests/persistor_tests.rs +++ b/codex-rs/rmcp-client/src/oauth/tests/persistor_tests.rs @@ -6,6 +6,10 @@ use anyhow::Context; use anyhow::Result; use codex_config::types::AuthKeyringBackendKind; use codex_config::types::OAuthCredentialsStoreMode; +use codex_exec_server::RouteAwareHttpClient; +use codex_http_client::HttpClientFactory; +use codex_http_client::OutboundProxyPolicy; +use http::HeaderMap; use keyring::Error as KeyringError; use oauth2::AccessToken; use oauth2::TokenResponse; @@ -41,6 +45,7 @@ use crate::oauth::load_oauth_tokens_from_file; use crate::oauth::refresh_lock::RefreshCredentialLock; use crate::oauth::save_oauth_tokens_to_file; use crate::oauth::stored_oauth_credentials; +use crate::oauth_http_client::OAuthHttpClientAdapter; use crate::startup_error::is_authentication_required_error; const REFRESH_LOCK_CONTENTION_EVENT_TARGET: &str = @@ -397,7 +402,14 @@ async fn test_context() -> Result<(TempCodexHome, MockServer, StoredOAuthTokens) async fn authorization_manager_for( tokens: &StoredOAuthTokens, ) -> Result>> { - let mut state = OAuthState::new(tokens.url.clone(), Some(reqwest::Client::new())).await?; + let oauth_http_client = Arc::new(OAuthHttpClientAdapter::new( + Arc::new(RouteAwareHttpClient::new(HttpClientFactory::new( + OutboundProxyPolicy::ReqwestDefault, + ))), + HeaderMap::new(), + )); + let mut state = + OAuthState::new_with_oauth_http_client(tokens.url.clone(), oauth_http_client).await?; state .set_credentials(&tokens.client_id, tokens.token_response.0.clone()) .await?; diff --git a/codex-rs/rmcp-client/src/oauth_http_client.rs b/codex-rs/rmcp-client/src/oauth_http_client.rs index 3cc9ed49a5..457fea5582 100644 --- a/codex-rs/rmcp-client/src/oauth_http_client.rs +++ b/codex-rs/rmcp-client/src/oauth_http_client.rs @@ -7,11 +7,9 @@ use codex_exec_server::HttpClient; use codex_exec_server::HttpHeader; use codex_exec_server::HttpRedirectPolicy; use codex_exec_server::HttpRequestParams; -use futures::StreamExt; +use http::HeaderMap; use oauth2::HttpRequest; use oauth2::HttpResponse; -use reqwest::Client; -use reqwest::header::HeaderMap; use rmcp::transport::auth::OAuthHttpClient; use rmcp::transport::auth::OAuthHttpClientError; use rmcp::transport::auth::OAuthHttpClientFuture; @@ -28,7 +26,6 @@ pub(crate) struct OAuthHttpClientAdapter { http_client: Arc, default_headers: HeaderMap, timeout: OAuthDiscoveryTimeout, - local_http_client: Option, } impl OAuthHttpClientAdapter { @@ -37,7 +34,6 @@ impl OAuthHttpClientAdapter { http_client, default_headers, timeout: OAuthDiscoveryTimeout::Requested, - local_http_client: None, } } @@ -50,16 +46,10 @@ impl OAuthHttpClientAdapter { http_client, default_headers, timeout: OAuthDiscoveryTimeout::Capped(max_timeout), - local_http_client: None, } } - pub(crate) fn with_local_http_client(mut self, client: Client) -> Self { - self.local_http_client = Some(client); - self - } - - async fn execute_transport_request( + async fn execute_request( &self, request: HttpRequest, redirect_policy: OAuthHttpRedirectPolicy, @@ -74,44 +64,13 @@ impl OAuthHttpClientAdapter { )); } }; - let (mut parts, body) = request.into_parts(); + let (parts, body) = request.into_parts(); let mut headers = self.default_headers.clone(); for name in parts.headers.keys() { headers.remove(name); } headers.extend(parts.headers); - if let Some(client) = &self.local_http_client { - parts.headers = headers; - let request = HttpRequest::from_parts(parts, body); - let request = reqwest::Request::try_from(request) - .map_err(|error| OAuthHttpClientError::new(error.to_string()))?; - let response = client - .execute(request) - .await - .map_err(|error| OAuthHttpClientError::new(error.to_string()))?; - let mut builder = oauth2::http::Response::builder() - .status(response.status()) - .version(response.version()); - for (name, value) in response.headers() { - builder = builder.header(name, value); - } - let mut body = Vec::new(); - let mut body_stream = response.bytes_stream(); - while let Some(chunk) = body_stream.next().await { - let chunk = chunk.map_err(|error| OAuthHttpClientError::new(error.to_string()))?; - if chunk.len() > MAX_OAUTH_HTTP_RESPONSE_BODY_BYTES - body.len() { - return Err(OAuthHttpClientError::new(format!( - "OAuth HTTP response body exceeds {MAX_OAUTH_HTTP_RESPONSE_BODY_BYTES} bytes" - ))); - } - body.extend_from_slice(&chunk); - } - return builder - .body(body) - .map_err(|error| OAuthHttpClientError::new(error.to_string())); - } - let headers = headers .iter() .map(|(name, value)| { @@ -175,10 +134,6 @@ impl OAuthHttpClientAdapter { impl OAuthHttpClient for OAuthHttpClientAdapter { fn execute(&self, request: OAuthHttpRequest) -> OAuthHttpClientFuture<'_> { - Box::pin(self.execute_transport_request( - request.request, - request.redirect_policy, - request.timeout, - )) + Box::pin(self.execute_request(request.request, request.redirect_policy, request.timeout)) } } diff --git a/codex-rs/rmcp-client/src/perform_oauth_login.rs b/codex-rs/rmcp-client/src/perform_oauth_login.rs index d61ad311c2..023d0cf9e9 100644 --- a/codex-rs/rmcp-client/src/perform_oauth_login.rs +++ b/codex-rs/rmcp-client/src/perform_oauth_login.rs @@ -10,10 +10,6 @@ use anyhow::bail; use base64::Engine; use base64::engine::general_purpose::URL_SAFE_NO_PAD; use codex_exec_server::HttpClient; -use codex_exec_server::RouteAwareHttpClient; -use codex_http_client::HttpClientFactory; -use codex_http_client::OutboundProxyPolicy; -use reqwest::Url; use rmcp::transport::AuthorizationManager; use rmcp::transport::AuthorizationRequest; use rmcp::transport::AuthorizationSession; @@ -26,6 +22,7 @@ use tiny_http::Response; use tiny_http::Server; use tokio::sync::oneshot; use tokio::time::timeout; +use url::Url; use urlencoding::decode; use crate::StoredOAuthTokens; @@ -203,41 +200,6 @@ pub async fn perform_oauth_login_return_url( timeout_secs: Option, callback_port: Option, callback_url: Option<&str>, -) -> Result { - perform_oauth_login_return_url_with_http_client( - server_name, - server_url, - store_mode, - keyring_backend_kind, - http_headers, - env_http_headers, - scopes, - oauth_client_id, - oauth_resource, - timeout_secs, - callback_port, - callback_url, - Arc::new(RouteAwareHttpClient::new(HttpClientFactory::new( - OutboundProxyPolicy::ReqwestDefault, - ))), - ) - .await -} - -#[allow(clippy::too_many_arguments)] -pub async fn perform_oauth_login_return_url_with_http_client( - server_name: &str, - server_url: &str, - store_mode: OAuthCredentialsStoreMode, - keyring_backend_kind: AuthKeyringBackendKind, - http_headers: Option>, - env_http_headers: Option>, - scopes: &[String], - oauth_client_id: Option<&str>, - oauth_resource: Option<&str>, - timeout_secs: Option, - callback_port: Option, - callback_url: Option<&str>, http_client: Arc, ) -> Result { let http_context = OAuthHttpContext { @@ -757,11 +719,11 @@ mod tests { use codex_http_client::HttpClientFactory; use codex_http_client::OutboundProxyPolicy; use futures::future::BoxFuture; + use http::HeaderMap; use pretty_assertions::assert_eq; - use reqwest::Url; - use reqwest::header::HeaderMap; use serde_json::json; use tokio::net::TcpListener; + use url::Url; use super::CallbackOutcome; use super::OAuthHttpClientAdapter; diff --git a/codex-rs/rmcp-client/src/rmcp_client.rs b/codex-rs/rmcp-client/src/rmcp_client.rs index a1e14845f2..22f876e6a2 100644 --- a/codex-rs/rmcp-client/src/rmcp_client.rs +++ b/codex-rs/rmcp-client/src/rmcp_client.rs @@ -19,9 +19,9 @@ use codex_exec_server::HttpClient; use codex_keyring_store::DefaultKeyringStore; use futures::FutureExt; use futures::future::BoxFuture; +use http::HeaderMap; +use http::header::AUTHORIZATION; use oauth2::TokenResponse; -use reqwest::header::AUTHORIZATION; -use reqwest::header::HeaderMap; use rmcp::model::CallToolRequestParams; use rmcp::model::CallToolResult; use rmcp::model::ClientNotification; diff --git a/codex-rs/rmcp-client/src/streamable_http_retry.rs b/codex-rs/rmcp-client/src/streamable_http_retry.rs index aff433d3d4..33f39c9a0e 100644 --- a/codex-rs/rmcp-client/src/streamable_http_retry.rs +++ b/codex-rs/rmcp-client/src/streamable_http_retry.rs @@ -5,7 +5,7 @@ use std::time::Instant; use anyhow::Result; use anyhow::anyhow; use codex_exec_server::ExecServerError; -use reqwest::StatusCode; +use http::StatusCode; use rmcp::service::RoleClient; use rmcp::service::RunningService; use rmcp::transport::streamable_http_client::StreamableHttpError; diff --git a/codex-rs/rmcp-client/src/utils.rs b/codex-rs/rmcp-client/src/utils.rs index 1ba7e45d8c..b3b97ca24c 100644 --- a/codex-rs/rmcp-client/src/utils.rs +++ b/codex-rs/rmcp-client/src/utils.rs @@ -1,10 +1,10 @@ use anyhow::Result; use anyhow::anyhow; use codex_config::types::McpServerEnvVar; -use reqwest::header::HeaderMap; -use reqwest::header::HeaderName; -use reqwest::header::HeaderValue; -use reqwest::header::USER_AGENT; +use http::HeaderMap; +use http::HeaderName; +use http::HeaderValue; +use http::header::USER_AGENT; use std::collections::HashMap; use std::env; use std::ffi::OsString; diff --git a/codex-rs/rmcp-client/tests/mcp_2026_oauth_discovery.rs b/codex-rs/rmcp-client/tests/mcp_2026_oauth_discovery.rs index acb58b6876..8d861b5730 100644 --- a/codex-rs/rmcp-client/tests/mcp_2026_oauth_discovery.rs +++ b/codex-rs/rmcp-client/tests/mcp_2026_oauth_discovery.rs @@ -7,7 +7,6 @@ use codex_http_client::OutboundProxyPolicy; use codex_rmcp_client::OAuthDiscoveryTimeout; use codex_rmcp_client::StreamableHttpOAuthDiscovery; use codex_rmcp_client::discover_streamable_http_oauth; -use codex_rmcp_client::discover_streamable_http_oauth_with_http_client; use pretty_assertions::assert_eq; use rmcp::transport::auth::AuthError; use serde_json::json; @@ -105,9 +104,13 @@ async fn discover_legacy_oauth_without_starting_an_mcp_session( &resource_url, resource_headers(), /*env_http_headers*/ None, + Arc::new(RouteAwareHttpClient::new(HttpClientFactory::new( + OutboundProxyPolicy::ReqwestDefault, + ))), + OAuthDiscoveryTimeout::LOCAL, ) .await; - let routed_discovery = discover_streamable_http_oauth_with_http_client( + let routed_discovery = discover_streamable_http_oauth( &resource_url, resource_headers(), /*env_http_headers*/ None, @@ -186,7 +189,7 @@ async fn oauth_discovery_does_not_invent_support_for_an_unauthenticated_legacy_s let resource_server = MockServer::start().await; let server_url = format!("{}/mcp", resource_server.uri()); - let executor_discovery = discover_streamable_http_oauth_with_http_client( + let executor_discovery = discover_streamable_http_oauth( &server_url, /*http_headers*/ None, /*env_http_headers*/ None, @@ -200,6 +203,10 @@ async fn oauth_discovery_does_not_invent_support_for_an_unauthenticated_legacy_s &server_url, /*http_headers*/ None, /*env_http_headers*/ None, + Arc::new(RouteAwareHttpClient::new(HttpClientFactory::new( + OutboundProxyPolicy::ReqwestDefault, + ))), + OAuthDiscoveryTimeout::LOCAL, ) .await?; diff --git a/codex-rs/rmcp-client/tests/streamable_http_oauth_startup.rs b/codex-rs/rmcp-client/tests/streamable_http_oauth_startup.rs index c9320b408e..df99ca9bbb 100644 --- a/codex-rs/rmcp-client/tests/streamable_http_oauth_startup.rs +++ b/codex-rs/rmcp-client/tests/streamable_http_oauth_startup.rs @@ -9,6 +9,7 @@ use codex_config::types::OAuthCredentialsStoreMode; use codex_exec_server::Environment; use codex_rmcp_client::McpAuthState; use codex_rmcp_client::McpLoginRequirement; +use codex_rmcp_client::OAuthDiscoveryTimeout; use codex_rmcp_client::RmcpClient; use codex_rmcp_client::StoredOAuthTokens; use codex_rmcp_client::WrappedOAuthTokenResponse; @@ -294,6 +295,8 @@ async fn auth_status(server_url: &str) -> anyhow::Result { /*env_http_headers*/ None, OAuthCredentialsStoreMode::File, AuthKeyringBackendKind::default(), + Environment::default_for_tests().get_http_client(), + OAuthDiscoveryTimeout::LOCAL, ) .await } diff --git a/codex-rs/rmcp-client/tests/streamable_http_test_support.rs b/codex-rs/rmcp-client/tests/streamable_http_test_support.rs index c00b2ba2b1..43bd3141bf 100644 --- a/codex-rs/rmcp-client/tests/streamable_http_test_support.rs +++ b/codex-rs/rmcp-client/tests/streamable_http_test_support.rs @@ -177,13 +177,17 @@ pub(crate) async fn call_echo_tool( .await } +fn control_client() -> anyhow::Result { + Ok(codex_http_client::HttpClientBuilder::new().build_direct()?) +} + pub(crate) async fn arm_session_post_failure( base_url: &str, status: u16, remaining: usize, www_authenticate_headers: &[&str], ) -> anyhow::Result<()> { - let response = reqwest::Client::new() + let response = control_client()? .post(format!("{base_url}{SESSION_POST_FAILURE_CONTROL_PATH}")) .json(&json!({ "status": status, @@ -193,7 +197,7 @@ pub(crate) async fn arm_session_post_failure( .send() .await?; - assert_eq!(response.status(), reqwest::StatusCode::NO_CONTENT); + assert_eq!(response.status(), http::StatusCode::NO_CONTENT); Ok(()) } @@ -202,7 +206,7 @@ pub(crate) async fn arm_session_post_json_rpc_failure( status: u16, remaining: usize, ) -> anyhow::Result<()> { - let response = reqwest::Client::new() + let response = control_client()? .post(format!("{base_url}{SESSION_POST_FAILURE_CONTROL_PATH}")) .json(&json!({ "status": status, @@ -220,7 +224,7 @@ pub(crate) async fn arm_session_post_json_rpc_failure( .send() .await?; - assert_eq!(response.status(), reqwest::StatusCode::NO_CONTENT); + assert_eq!(response.status(), http::StatusCode::NO_CONTENT); Ok(()) } @@ -229,7 +233,7 @@ pub(crate) async fn arm_initialized_notification_post_json_rpc_failure( status: u16, remaining: usize, ) -> anyhow::Result<()> { - let response = reqwest::Client::new() + let response = control_client()? .post(format!( "{base_url}{INITIALIZED_NOTIFICATION_POST_FAILURE_CONTROL_PATH}" )) @@ -249,7 +253,7 @@ pub(crate) async fn arm_initialized_notification_post_json_rpc_failure( .send() .await?; - assert_eq!(response.status(), reqwest::StatusCode::NO_CONTENT); + assert_eq!(response.status(), http::StatusCode::NO_CONTENT); Ok(()) } @@ -258,7 +262,7 @@ pub(crate) async fn arm_initialize_post_failure( status: u16, remaining: usize, ) -> anyhow::Result<()> { - let response = reqwest::Client::new() + let response = control_client()? .post(format!("{base_url}{INITIALIZE_POST_FAILURE_CONTROL_PATH}")) .json(&json!({ "status": status, @@ -267,7 +271,7 @@ pub(crate) async fn arm_initialize_post_failure( .send() .await?; - assert_eq!(response.status(), reqwest::StatusCode::NO_CONTENT); + assert_eq!(response.status(), http::StatusCode::NO_CONTENT); Ok(()) } @@ -276,7 +280,7 @@ pub(crate) async fn arm_initialize_post_json_rpc_failure( status: u16, remaining: usize, ) -> anyhow::Result<()> { - let response = reqwest::Client::new() + let response = control_client()? .post(format!("{base_url}{INITIALIZE_POST_FAILURE_CONTROL_PATH}")) .json(&json!({ "status": status, @@ -294,7 +298,7 @@ pub(crate) async fn arm_initialize_post_json_rpc_failure( .send() .await?; - assert_eq!(response.status(), reqwest::StatusCode::NO_CONTENT); + assert_eq!(response.status(), http::StatusCode::NO_CONTENT); Ok(()) }