From a43ae86b6c072a962120460e5f4386cbcbc35b27 Mon Sep 17 00:00:00 2001 From: Gabriel Peal Date: Tue, 7 Oct 2025 20:21:37 -0700 Subject: [PATCH] [MCP] Add support for streamable http servers with `codex mcp add` and replace bearer token handling (#4904) 1. You can now add streamable http servers via the CLI 2. As part of this, I'm also changing the existing bearer_token plain text config field with ane env var ``` mcp add github --url https://api.githubcopilot.com/mcp/ --bearer-token-env-var=GITHUB_PAT ``` --- codex-rs/Cargo.lock | 8 +- codex-rs/cli/src/mcp_cmd.rs | 158 ++++++++++++++------ codex-rs/cli/tests/mcp_add_remove.rs | 113 ++++++++++++++ codex-rs/core/src/config.rs | 79 ++++++++-- codex-rs/core/src/config_types.rs | 45 ++++-- codex-rs/core/src/mcp_connection_manager.rs | 40 ++++- codex-rs/core/tests/suite/rmcp_client.rs | 4 +- 7 files changed, 372 insertions(+), 75 deletions(-) diff --git a/codex-rs/Cargo.lock b/codex-rs/Cargo.lock index f23890bf..8c992a6f 100644 --- a/codex-rs/Cargo.lock +++ b/codex-rs/Cargo.lock @@ -4772,9 +4772,9 @@ dependencies = [ [[package]] name = "rmcp" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "583d060e99feb3a3683fb48a1e4bf5f8d4a50951f429726f330ee5ff548837f8" +checksum = "6f35acda8f89fca5fd8c96cae3c6d5b4c38ea0072df4c8030915f3b5ff469c1c" dependencies = [ "base64", "bytes", @@ -4806,9 +4806,9 @@ dependencies = [ [[package]] name = "rmcp-macros" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "421d8b0ba302f479214889486f9550e63feca3af310f1190efcf6e2016802693" +checksum = "c9f1d5220aaa23b79c3d02e18f7a554403b3ccea544bbb6c69d6bcb3e854a274" dependencies = [ "darling 0.21.3", "proc-macro2", diff --git a/codex-rs/cli/src/mcp_cmd.rs b/codex-rs/cli/src/mcp_cmd.rs index c6add86f..0cf3c0e2 100644 --- a/codex-rs/cli/src/mcp_cmd.rs +++ b/codex-rs/cli/src/mcp_cmd.rs @@ -4,6 +4,7 @@ use anyhow::Context; use anyhow::Result; use anyhow::anyhow; use anyhow::bail; +use clap::ArgGroup; use codex_common::CliConfigOverrides; use codex_core::config::Config; use codex_core::config::ConfigOverrides; @@ -77,13 +78,61 @@ pub struct AddArgs { /// Name for the MCP server configuration. pub name: String, - /// Environment variables to set when launching the server. - #[arg(long, value_parser = parse_env_pair, value_name = "KEY=VALUE")] - pub env: Vec<(String, String)>, + #[command(flatten)] + pub transport_args: AddMcpTransportArgs, +} +#[derive(Debug, clap::Args)] +#[command( + group( + ArgGroup::new("transport") + .args(["command", "url"]) + .required(true) + .multiple(false) + ) +)] +pub struct AddMcpTransportArgs { + #[command(flatten)] + pub stdio: Option, + + #[command(flatten)] + pub streamable_http: Option, +} + +#[derive(Debug, clap::Args)] +pub struct AddMcpStdioArgs { /// Command to launch the MCP server. - #[arg(trailing_var_arg = true, num_args = 1..)] + /// Use --url for a streamable HTTP server. + #[arg( + trailing_var_arg = true, + num_args = 0.., + )] pub command: Vec, + + /// Environment variables to set when launching the server. + /// Only valid with stdio servers. + #[arg( + long, + value_parser = parse_env_pair, + value_name = "KEY=VALUE", + )] + pub env: Vec<(String, String)>, +} + +#[derive(Debug, clap::Args)] +pub struct AddMcpStreamableHttpArgs { + /// URL for a streamable HTTP MCP server. + #[arg(long)] + pub url: String, + + /// Optional environment variable to read for a bearer token. + /// Only valid with streamable HTTP servers. + #[arg( + long = "bearer-token-env-var", + value_name = "ENV_VAR", + requires = "url" + )] + pub bearer_token_env_var: Option, } #[derive(Debug, clap::Parser)] @@ -140,37 +189,51 @@ async fn run_add(config_overrides: &CliConfigOverrides, add_args: AddArgs) -> Re // Validate any provided overrides even though they are not currently applied. config_overrides.parse_overrides().map_err(|e| anyhow!(e))?; - let AddArgs { name, env, command } = add_args; + let AddArgs { + name, + transport_args, + } = add_args; validate_server_name(&name)?; - let mut command_parts = command.into_iter(); - let command_bin = command_parts - .next() - .ok_or_else(|| anyhow!("command is required"))?; - let command_args: Vec = command_parts.collect(); - - let env_map = if env.is_empty() { - None - } else { - let mut map = HashMap::new(); - for (key, value) in env { - map.insert(key, value); - } - Some(map) - }; - let codex_home = find_codex_home().context("failed to resolve CODEX_HOME")?; let mut servers = load_global_mcp_servers(&codex_home) .await .with_context(|| format!("failed to load MCP servers from {}", codex_home.display()))?; - let new_entry = McpServerConfig { - transport: McpServerTransportConfig::Stdio { - command: command_bin, - args: command_args, - env: env_map, + let transport = match transport_args { + AddMcpTransportArgs { + stdio: Some(stdio), .. + } => { + let mut command_parts = stdio.command.into_iter(); + let command_bin = command_parts + .next() + .ok_or_else(|| anyhow!("command is required"))?; + let command_args: Vec = command_parts.collect(); + + let env_map = if stdio.env.is_empty() { + None + } else { + Some(stdio.env.into_iter().collect::>()) + }; + McpServerTransportConfig::Stdio { + command: command_bin, + args: command_args, + env: env_map, + } + } + AddMcpTransportArgs { + streamable_http: Some(streamable_http), + .. + } => McpServerTransportConfig::StreamableHttp { + url: streamable_http.url, + bearer_token_env_var: streamable_http.bearer_token_env_var, }, + AddMcpTransportArgs { .. } => bail!("exactly one of --command or --url must be provided"), + }; + + let new_entry = McpServerConfig { + transport, startup_timeout_sec: None, tool_timeout_sec: None, }; @@ -288,11 +351,14 @@ async fn run_list(config_overrides: &CliConfigOverrides, list_args: ListArgs) -> "args": args, "env": env, }), - McpServerTransportConfig::StreamableHttp { url, bearer_token } => { + McpServerTransportConfig::StreamableHttp { + url, + bearer_token_env_var, + } => { serde_json::json!({ "type": "streamable_http", "url": url, - "bearer_token": bearer_token, + "bearer_token_env_var": bearer_token_env_var, }) } }; @@ -345,13 +411,15 @@ async fn run_list(config_overrides: &CliConfigOverrides, list_args: ListArgs) -> }; stdio_rows.push([name.clone(), command.clone(), args_display, env_display]); } - McpServerTransportConfig::StreamableHttp { url, bearer_token } => { - let has_bearer = if bearer_token.is_some() { - "True" - } else { - "False" - }; - http_rows.push([name.clone(), url.clone(), has_bearer.into()]); + McpServerTransportConfig::StreamableHttp { + url, + bearer_token_env_var, + } => { + http_rows.push([ + name.clone(), + url.clone(), + bearer_token_env_var.clone().unwrap_or("-".to_string()), + ]); } } } @@ -396,7 +464,7 @@ async fn run_list(config_overrides: &CliConfigOverrides, list_args: ListArgs) -> } if !http_rows.is_empty() { - let mut widths = ["Name".len(), "Url".len(), "Has Bearer Token".len()]; + let mut widths = ["Name".len(), "Url".len(), "Bearer Token Env Var".len()]; for row in &http_rows { for (i, cell) in row.iter().enumerate() { widths[i] = widths[i].max(cell.len()); @@ -407,7 +475,7 @@ async fn run_list(config_overrides: &CliConfigOverrides, list_args: ListArgs) -> "{: Re "args": args, "env": env, }), - McpServerTransportConfig::StreamableHttp { url, bearer_token } => serde_json::json!({ + McpServerTransportConfig::StreamableHttp { + url, + bearer_token_env_var, + } => serde_json::json!({ "type": "streamable_http", "url": url, - "bearer_token": bearer_token, + "bearer_token_env_var": bearer_token_env_var, }), }; let output = serde_json::to_string_pretty(&serde_json::json!({ @@ -493,11 +564,14 @@ async fn run_get(config_overrides: &CliConfigOverrides, get_args: GetArgs) -> Re }; println!(" env: {env_display}"); } - McpServerTransportConfig::StreamableHttp { url, bearer_token } => { + McpServerTransportConfig::StreamableHttp { + url, + bearer_token_env_var, + } => { println!(" transport: streamable_http"); println!(" url: {url}"); - let bearer = bearer_token.as_deref().unwrap_or("-"); - println!(" bearer_token: {bearer}"); + let env_var = bearer_token_env_var.as_deref().unwrap_or("-"); + println!(" bearer_token_env_var: {env_var}"); } } if let Some(timeout) = server.startup_timeout_sec { diff --git a/codex-rs/cli/tests/mcp_add_remove.rs b/codex-rs/cli/tests/mcp_add_remove.rs index 6530760e..9ccb9f73 100644 --- a/codex-rs/cli/tests/mcp_add_remove.rs +++ b/codex-rs/cli/tests/mcp_add_remove.rs @@ -93,3 +93,116 @@ async fn add_with_env_preserves_key_order_and_values() -> Result<()> { Ok(()) } + +#[tokio::test] +async fn add_streamable_http_without_manual_token() -> Result<()> { + let codex_home = TempDir::new()?; + + let mut add_cmd = codex_command(codex_home.path())?; + add_cmd + .args(["mcp", "add", "github", "--url", "https://example.com/mcp"]) + .assert() + .success(); + + let servers = load_global_mcp_servers(codex_home.path()).await?; + let github = servers.get("github").expect("github server should exist"); + match &github.transport { + McpServerTransportConfig::StreamableHttp { + url, + bearer_token_env_var, + } => { + assert_eq!(url, "https://example.com/mcp"); + assert!(bearer_token_env_var.is_none()); + } + other => panic!("unexpected transport: {other:?}"), + } + + assert!(!codex_home.path().join(".credentials.json").exists()); + assert!(!codex_home.path().join(".env").exists()); + + Ok(()) +} + +#[tokio::test] +async fn add_streamable_http_with_custom_env_var() -> Result<()> { + let codex_home = TempDir::new()?; + + let mut add_cmd = codex_command(codex_home.path())?; + add_cmd + .args([ + "mcp", + "add", + "issues", + "--url", + "https://example.com/issues", + "--bearer-token-env-var", + "GITHUB_TOKEN", + ]) + .assert() + .success(); + + let servers = load_global_mcp_servers(codex_home.path()).await?; + let issues = servers.get("issues").expect("issues server should exist"); + match &issues.transport { + McpServerTransportConfig::StreamableHttp { + url, + bearer_token_env_var, + } => { + assert_eq!(url, "https://example.com/issues"); + assert_eq!(bearer_token_env_var.as_deref(), Some("GITHUB_TOKEN")); + } + other => panic!("unexpected transport: {other:?}"), + } + Ok(()) +} + +#[tokio::test] +async fn add_streamable_http_rejects_removed_flag() -> Result<()> { + let codex_home = TempDir::new()?; + + let mut add_cmd = codex_command(codex_home.path())?; + add_cmd + .args([ + "mcp", + "add", + "github", + "--url", + "https://example.com/mcp", + "--with-bearer-token", + ]) + .assert() + .failure() + .stderr(contains("--with-bearer-token")); + + let servers = load_global_mcp_servers(codex_home.path()).await?; + assert!(servers.is_empty()); + + Ok(()) +} + +#[tokio::test] +async fn add_cant_add_command_and_url() -> Result<()> { + let codex_home = TempDir::new()?; + + let mut add_cmd = codex_command(codex_home.path())?; + add_cmd + .args([ + "mcp", + "add", + "github", + "--url", + "https://example.com/mcp", + "--command", + "--", + "echo", + "hello", + ]) + .assert() + .failure() + .stderr(contains("unexpected argument '--command' found")); + + let servers = load_global_mcp_servers(codex_home.path()).await?; + assert!(servers.is_empty()); + + Ok(()) +} diff --git a/codex-rs/core/src/config.rs b/codex-rs/core/src/config.rs index 43d3f996..d0437163 100644 --- a/codex-rs/core/src/config.rs +++ b/codex-rs/core/src/config.rs @@ -38,8 +38,10 @@ use dirs::home_dir; use serde::Deserialize; use std::collections::BTreeMap; use std::collections::HashMap; +use std::io::ErrorKind; use std::path::Path; use std::path::PathBuf; + use tempfile::NamedTempFile; use toml::Value as TomlValue; use toml_edit::Array as TomlArray; @@ -311,12 +313,35 @@ pub async fn load_global_mcp_servers( return Ok(BTreeMap::new()); }; + ensure_no_inline_bearer_tokens(servers_value)?; + servers_value .clone() .try_into() .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e)) } +/// We briefly allowed plain text bearer_token fields in MCP server configs. +/// We want to warn people who recently added these fields but can remove this after a few months. +fn ensure_no_inline_bearer_tokens(value: &TomlValue) -> std::io::Result<()> { + let Some(servers_table) = value.as_table() else { + return Ok(()); + }; + + for (server_name, server_value) in servers_table { + if let Some(server_table) = server_value.as_table() + && server_table.contains_key("bearer_token") + { + let message = format!( + "mcp_servers.{server_name} uses unsupported `bearer_token`; set `bearer_token_env_var`." + ); + return Err(std::io::Error::new(ErrorKind::InvalidData, message)); + } + } + + Ok(()) +} + pub fn write_global_mcp_servers( codex_home: &Path, servers: &BTreeMap, @@ -365,10 +390,13 @@ pub fn write_global_mcp_servers( entry["env"] = TomlItem::Table(env_table); } } - McpServerTransportConfig::StreamableHttp { url, bearer_token } => { + McpServerTransportConfig::StreamableHttp { + url, + bearer_token_env_var, + } => { entry["url"] = toml_edit::value(url.clone()); - if let Some(token) = bearer_token { - entry["bearer_token"] = toml_edit::value(token.clone()); + if let Some(env_var) = bearer_token_env_var { + entry["bearer_token_env_var"] = toml_edit::value(env_var.clone()); } } } @@ -1571,6 +1599,31 @@ startup_timeout_ms = 2500 Ok(()) } + #[tokio::test] + async fn load_global_mcp_servers_rejects_inline_bearer_token() -> anyhow::Result<()> { + let codex_home = TempDir::new()?; + let config_path = codex_home.path().join(CONFIG_TOML_FILE); + + std::fs::write( + &config_path, + r#" +[mcp_servers.docs] +url = "https://example.com/mcp" +bearer_token = "secret" +"#, + )?; + + let err = load_global_mcp_servers(codex_home.path()) + .await + .expect_err("bearer_token entries should be rejected"); + + assert_eq!(err.kind(), std::io::ErrorKind::InvalidData); + assert!(err.to_string().contains("bearer_token")); + assert!(err.to_string().contains("bearer_token_env_var")); + + Ok(()) + } + #[tokio::test] async fn write_global_mcp_servers_serializes_env_sorted() -> anyhow::Result<()> { let codex_home = TempDir::new()?; @@ -1634,7 +1687,7 @@ ZIG_VAR = "3" McpServerConfig { transport: McpServerTransportConfig::StreamableHttp { url: "https://example.com/mcp".to_string(), - bearer_token: Some("secret-token".to_string()), + bearer_token_env_var: Some("MCP_TOKEN".to_string()), }, startup_timeout_sec: Some(Duration::from_secs(2)), tool_timeout_sec: None, @@ -1649,7 +1702,7 @@ ZIG_VAR = "3" serialized, r#"[mcp_servers.docs] url = "https://example.com/mcp" -bearer_token = "secret-token" +bearer_token_env_var = "MCP_TOKEN" startup_timeout_sec = 2.0 "# ); @@ -1657,9 +1710,12 @@ startup_timeout_sec = 2.0 let loaded = load_global_mcp_servers(codex_home.path()).await?; let docs = loaded.get("docs").expect("docs entry"); match &docs.transport { - McpServerTransportConfig::StreamableHttp { url, bearer_token } => { + McpServerTransportConfig::StreamableHttp { + url, + bearer_token_env_var, + } => { assert_eq!(url, "https://example.com/mcp"); - assert_eq!(bearer_token.as_deref(), Some("secret-token")); + assert_eq!(bearer_token_env_var.as_deref(), Some("MCP_TOKEN")); } other => panic!("unexpected transport {other:?}"), } @@ -1670,7 +1726,7 @@ startup_timeout_sec = 2.0 McpServerConfig { transport: McpServerTransportConfig::StreamableHttp { url: "https://example.com/mcp".to_string(), - bearer_token: None, + bearer_token_env_var: None, }, startup_timeout_sec: None, tool_timeout_sec: None, @@ -1689,9 +1745,12 @@ url = "https://example.com/mcp" let loaded = load_global_mcp_servers(codex_home.path()).await?; let docs = loaded.get("docs").expect("docs entry"); match &docs.transport { - McpServerTransportConfig::StreamableHttp { url, bearer_token } => { + McpServerTransportConfig::StreamableHttp { + url, + bearer_token_env_var, + } => { assert_eq!(url, "https://example.com/mcp"); - assert!(bearer_token.is_none()); + assert!(bearer_token_env_var.is_none()); } other => panic!("unexpected transport {other:?}"), } diff --git a/codex-rs/core/src/config_types.rs b/codex-rs/core/src/config_types.rs index 1b8f3ac0..8940ac5c 100644 --- a/codex-rs/core/src/config_types.rs +++ b/codex-rs/core/src/config_types.rs @@ -48,6 +48,7 @@ impl<'de> Deserialize<'de> for McpServerConfig { url: Option, bearer_token: Option, + bearer_token_env_var: Option, #[serde(default)] startup_timeout_sec: Option, @@ -86,11 +87,15 @@ impl<'de> Deserialize<'de> for McpServerConfig { args, env, url, - bearer_token, + bearer_token_env_var, .. } => { throw_if_set("stdio", "url", url.as_ref())?; - throw_if_set("stdio", "bearer_token", bearer_token.as_ref())?; + throw_if_set( + "stdio", + "bearer_token_env_var", + bearer_token_env_var.as_ref(), + )?; McpServerTransportConfig::Stdio { command, args: args.unwrap_or_default(), @@ -100,6 +105,7 @@ impl<'de> Deserialize<'de> for McpServerConfig { RawMcpServerConfig { url: Some(url), bearer_token, + bearer_token_env_var, command, args, env, @@ -108,7 +114,11 @@ impl<'de> Deserialize<'de> for McpServerConfig { throw_if_set("streamable_http", "command", command.as_ref())?; throw_if_set("streamable_http", "args", args.as_ref())?; throw_if_set("streamable_http", "env", env.as_ref())?; - McpServerTransportConfig::StreamableHttp { url, bearer_token } + throw_if_set("streamable_http", "bearer_token", bearer_token.as_ref())?; + McpServerTransportConfig::StreamableHttp { + url, + bearer_token_env_var, + } } _ => return Err(SerdeError::custom("invalid transport")), }; @@ -135,11 +145,11 @@ pub enum McpServerTransportConfig { /// https://modelcontextprotocol.io/specification/2025-06-18/basic/transports#streamable-http StreamableHttp { url: String, - /// A plain text bearer token to use for authentication. - /// This bearer token will be included in the HTTP request header as an `Authorization: Bearer ` header. - /// This should be used with caution because it lives on disk in clear text. + /// Name of the environment variable to read for an HTTP bearer token. + /// When set, requests will include the token via `Authorization: Bearer `. + /// The actual secret value must be provided via the environment. #[serde(default, skip_serializing_if = "Option::is_none")] - bearer_token: Option, + bearer_token_env_var: Option, }, } @@ -506,17 +516,17 @@ mod tests { cfg.transport, McpServerTransportConfig::StreamableHttp { url: "https://example.com/mcp".to_string(), - bearer_token: None + bearer_token_env_var: None } ); } #[test] - fn deserialize_streamable_http_server_config_with_bearer_token() { + fn deserialize_streamable_http_server_config_with_env_var() { let cfg: McpServerConfig = toml::from_str( r#" url = "https://example.com/mcp" - bearer_token = "secret" + bearer_token_env_var = "GITHUB_TOKEN" "#, ) .expect("should deserialize http config"); @@ -525,7 +535,7 @@ mod tests { cfg.transport, McpServerTransportConfig::StreamableHttp { url: "https://example.com/mcp".to_string(), - bearer_token: Some("secret".to_string()) + bearer_token_env_var: Some("GITHUB_TOKEN".to_string()) } ); } @@ -553,13 +563,18 @@ mod tests { } #[test] - fn deserialize_rejects_bearer_token_for_stdio_transport() { - toml::from_str::( + fn deserialize_rejects_inline_bearer_token_field() { + let err = toml::from_str::( r#" - command = "echo" + url = "https://example.com" bearer_token = "secret" "#, ) - .expect_err("should reject bearer token for stdio transport"); + .expect_err("should reject bearer_token field"); + + assert!( + err.to_string().contains("bearer_token is not supported"), + "unexpected error: {err}" + ); } } diff --git a/codex-rs/core/src/mcp_connection_manager.rs b/codex-rs/core/src/mcp_connection_manager.rs index 37986e4b..0c2fdc63 100644 --- a/codex-rs/core/src/mcp_connection_manager.rs +++ b/codex-rs/core/src/mcp_connection_manager.rs @@ -8,6 +8,7 @@ use std::collections::HashMap; use std::collections::HashSet; +use std::env; use std::ffi::OsString; use std::sync::Arc; use std::time::Duration; @@ -209,6 +210,14 @@ impl McpConnectionManager { let startup_timeout = cfg.startup_timeout_sec.unwrap_or(DEFAULT_STARTUP_TIMEOUT); let tool_timeout = cfg.tool_timeout_sec.unwrap_or(DEFAULT_TOOL_TIMEOUT); + let resolved_bearer_token = match &cfg.transport { + McpServerTransportConfig::StreamableHttp { + bearer_token_env_var, + .. + } => resolve_bearer_token(&server_name, bearer_token_env_var.as_deref()), + _ => Ok(None), + }; + join_set.spawn(async move { let McpServerConfig { transport, .. } = cfg; let params = mcp_types::InitializeRequestParams { @@ -246,11 +255,11 @@ impl McpConnectionManager { ) .await } - McpServerTransportConfig::StreamableHttp { url, bearer_token } => { + McpServerTransportConfig::StreamableHttp { url, .. } => { McpClientAdapter::new_streamable_http_client( server_name.clone(), url, - bearer_token, + resolved_bearer_token.unwrap_or_default(), params, startup_timeout, store_mode, @@ -341,6 +350,33 @@ impl McpConnectionManager { } } +fn resolve_bearer_token( + server_name: &str, + bearer_token_env_var: Option<&str>, +) -> Result> { + let Some(env_var) = bearer_token_env_var else { + return Ok(None); + }; + + match env::var(env_var) { + Ok(value) => { + if value.is_empty() { + Err(anyhow!( + "Environment variable {env_var} for MCP server '{server_name}' is empty" + )) + } else { + Ok(Some(value)) + } + } + Err(env::VarError::NotPresent) => Err(anyhow!( + "Environment variable {env_var} for MCP server '{server_name}' is not set" + )), + Err(env::VarError::NotUnicode(_)) => Err(anyhow!( + "Environment variable {env_var} for MCP server '{server_name}' contains invalid Unicode" + )), + } +} + /// Query every server for its available tools and return a single map that /// contains **all** tools. Each key is the fully-qualified name for the tool. async fn list_all_tools(clients: &HashMap) -> Result> { diff --git a/codex-rs/core/tests/suite/rmcp_client.rs b/codex-rs/core/tests/suite/rmcp_client.rs index 212b03bd..a8a5d3af 100644 --- a/codex-rs/core/tests/suite/rmcp_client.rs +++ b/codex-rs/core/tests/suite/rmcp_client.rs @@ -232,7 +232,7 @@ async fn streamable_http_tool_call_round_trip() -> anyhow::Result<()> { McpServerConfig { transport: McpServerTransportConfig::StreamableHttp { url: server_url, - bearer_token: None, + bearer_token_env_var: None, }, startup_timeout_sec: Some(Duration::from_secs(10)), tool_timeout_sec: None, @@ -412,7 +412,7 @@ async fn streamable_http_with_oauth_round_trip() -> anyhow::Result<()> { McpServerConfig { transport: McpServerTransportConfig::StreamableHttp { url: server_url, - bearer_token: None, + bearer_token_env_var: None, }, startup_timeout_sec: Some(Duration::from_secs(10)), tool_timeout_sec: None,