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
This commit is contained in:
Celia Chen
2026-07-28 19:11:36 +00:00
committed by copyberry
parent 9ea975a2dc
commit 709283b432
25 changed files with 181 additions and 380 deletions
-7
View File
@@ -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\":{}}",
+4 -96
View File
@@ -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",
@@ -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;
@@ -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,
@@ -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,
+4 -4
View File
@@ -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<dyn HttpClient> =
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,
-2
View File
@@ -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;
+5 -35
View File
@@ -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<dyn HttpClient>,
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<McpOAut
pub async fn discover_supported_scopes(
transport: &McpServerTransportConfig,
) -> Option<Vec<String>> {
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<dyn HttpClient>,
discovery_timeout: OAuthDiscoveryTimeout,
) -> Option<Vec<String>> {
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(),
-2
View File
@@ -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;
+2 -2
View File
@@ -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,
-1
View File
@@ -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.
+4 -6
View File
@@ -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 }
+84 -87
View File
@@ -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<HashMap<String, String>>,
env_http_headers: Option<HashMap<String, String>>,
store_mode: OAuthCredentialsStoreMode,
keyring_backend_kind: AuthKeyringBackendKind,
) -> Result<McpAuthState> {
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<bool> {
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<HashMap<String, String>>,
env_http_headers: Option<HashMap<String, String>>,
) -> Result<Option<StreamableHttpOAuthDiscovery>> {
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<HashMap<String, String>>,
env_http_headers: Option<HashMap<String, String>>,
http_client: Arc<dyn HttpClient>,
discovery_timeout: OAuthDiscoveryTimeout,
) -> Result<Option<StreamableHttpOAuthDiscovery>> {
@@ -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<Option<StreamableHttpOAuthDiscovery>> {
// 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<dyn HttpClient> {
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<Option<Vec<(String, String)>>>,
timeout_ms: Mutex<Option<Option<u64>>>,
}
@@ -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);
}
+17 -16
View File
@@ -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<Arc<str>>,
auth_token: Option<String>,
custom_headers: HashMap<HeaderName, reqwest::header::HeaderValue>,
custom_headers: HashMap<HeaderName, HeaderValue>,
) -> std::result::Result<StreamableHttpPostResponse, StreamableHttpError<Self::Error>> {
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<str>,
session: Arc<str>,
auth_token: Option<String>,
custom_headers: HashMap<HeaderName, reqwest::header::HeaderValue>,
custom_headers: HashMap<HeaderName, HeaderValue>,
) -> std::result::Result<(), StreamableHttpError<Self::Error>> {
let mut headers = self.default_headers.clone();
headers.extend(custom_headers);
@@ -378,7 +379,7 @@ impl StreamableHttpClient for StreamableHttpClientAdapter {
session_id: Option<Arc<str>>,
last_event_id: Option<String>,
auth_token: Option<String>,
custom_headers: HashMap<HeaderName, reqwest::header::HeaderValue>,
custom_headers: HashMap<HeaderName, HeaderValue>,
) -> std::result::Result<
BoxStream<'static, std::result::Result<Sse, sse_stream::Error>>,
StreamableHttpError<Self::Error>,
@@ -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("<invalid>"),
endpoint_host = parsed_url
.as_ref()
.and_then(reqwest::Url::host_str)
.and_then(url::Url::host_str)
.unwrap_or("<invalid>"),
endpoint_path = parsed_url
.as_ref()
.map(reqwest::Url::path)
.map(url::Url::path)
.unwrap_or("<invalid>"),
endpoint_has_query = parsed_url.as_ref().is_some_and(|url| url.query().is_some()),
mcp_method = mcp_method.unwrap_or("<none>"),
@@ -567,7 +568,7 @@ fn insert_header<Error>(
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(())
@@ -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 {
-4
View File
@@ -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;
@@ -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<Arc<TokioMutex<AuthorizationManager>>> {
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?;
+4 -49
View File
@@ -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<dyn HttpClient>,
default_headers: HeaderMap,
timeout: OAuthDiscoveryTimeout,
local_http_client: Option<Client>,
}
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))
}
}
@@ -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<i64>,
callback_port: Option<u16>,
callback_url: Option<&str>,
) -> Result<OauthLoginHandle> {
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<HashMap<String, String>>,
env_http_headers: Option<HashMap<String, String>>,
scopes: &[String],
oauth_client_id: Option<&str>,
oauth_resource: Option<&str>,
timeout_secs: Option<i64>,
callback_port: Option<u16>,
callback_url: Option<&str>,
http_client: Arc<dyn HttpClient>,
) -> Result<OauthLoginHandle> {
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;
+2 -2
View File
@@ -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;
@@ -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;
+4 -4
View File
@@ -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;
@@ -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?;
@@ -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<McpAuthState> {
/*env_http_headers*/ None,
OAuthCredentialsStoreMode::File,
AuthKeyringBackendKind::default(),
Environment::default_for_tests().get_http_client(),
OAuthDiscoveryTimeout::LOCAL,
)
.await
}
@@ -177,13 +177,17 @@ pub(crate) async fn call_echo_tool(
.await
}
fn control_client() -> anyhow::Result<codex_http_client::HttpClient> {
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(())
}