2025-09-14 21:30:56 -07:00
use std ::collections ::HashMap ;
use anyhow ::Context ;
use anyhow ::Result ;
use anyhow ::anyhow ;
use anyhow ::bail ;
2025-10-07 20:21:37 -07:00
use clap ::ArgGroup ;
2025-09-14 21:30:56 -07:00
use codex_common ::CliConfigOverrides ;
2025-10-16 21:24:43 -07:00
use codex_common ::format_env_display ::format_env_display ;
2025-09-14 21:30:56 -07:00
use codex_core ::config ::Config ;
use codex_core ::config ::ConfigOverrides ;
2025-10-30 10:28:32 +00:00
use codex_core ::config ::edit ::ConfigEditsBuilder ;
2025-09-14 21:30:56 -07:00
use codex_core ::config ::find_codex_home ;
use codex_core ::config ::load_global_mcp_servers ;
2025-10-30 10:28:32 +00:00
use codex_core ::config ::types ::McpServerConfig ;
use codex_core ::config ::types ::McpServerTransportConfig ;
2025-10-14 18:50:00 +01:00
use codex_core ::features ::Feature ;
2025-10-08 14:37:57 -07:00
use codex_core ::mcp ::auth ::compute_auth_statuses ;
use codex_core ::protocol ::McpAuthStatus ;
2025-10-03 10:43:12 -07:00
use codex_rmcp_client ::delete_oauth_tokens ;
use codex_rmcp_client ::perform_oauth_login ;
2025-10-15 09:27:40 -07:00
use codex_rmcp_client ::supports_oauth_login ;
2025-09-14 21:30:56 -07:00
/// [experimental] Launch Codex as an MCP server or manage configured MCP servers.
///
/// Subcommands:
/// - `serve` — run the MCP server on stdio
/// - `list` — list configured servers (with `--json`)
/// - `get` — show a single server (with `--json`)
/// - `add` — add a server launcher entry to `~/.codex/config.toml`
/// - `remove` — delete a server entry
#[ derive(Debug, clap::Parser) ]
pub struct McpCli {
#[ clap(flatten) ]
pub config_overrides : CliConfigOverrides ,
#[ command(subcommand) ]
fix: separate `codex mcp` into `codex mcp-server` and `codex app-server` (#4471)
This is a very large PR with some non-backwards-compatible changes.
Historically, `codex mcp` (or `codex mcp serve`) started a JSON-RPC-ish
server that had two overlapping responsibilities:
- Running an MCP server, providing some basic tool calls.
- Running the app server used to power experiences such as the VS Code
extension.
This PR aims to separate these into distinct concepts:
- `codex mcp-server` for the MCP server
- `codex app-server` for the "application server"
Note `codex mcp` still exists because it already has its own subcommands
for MCP management (`list`, `add`, etc.)
The MCP logic continues to live in `codex-rs/mcp-server` whereas the
refactored app server logic is in the new `codex-rs/app-server` folder.
Note that most of the existing integration tests in
`codex-rs/mcp-server/tests/suite` were actually for the app server, so
all the tests have been moved with the exception of
`codex-rs/mcp-server/tests/suite/mod.rs`.
Because this is already a large diff, I tried not to change more than I
had to, so `codex-rs/app-server/tests/common/mcp_process.rs` still uses
the name `McpProcess` for now, but I will do some mechanical renamings
to things like `AppServer` in subsequent PRs.
While `mcp-server` and `app-server` share some overlapping functionality
(like reading streams of JSONL and dispatching based on message types)
and some differences (completely different message types), I ended up
doing a bit of copypasta between the two crates, as both have somewhat
similar `message_processor.rs` and `outgoing_message.rs` files for now,
though I expect them to diverge more in the near future.
One material change is that of the initialize handshake for `codex
app-server`, as we no longer use the MCP types for that handshake.
Instead, we update `codex-rs/protocol/src/mcp_protocol.rs` to add an
`Initialize` variant to `ClientRequest`, which takes the `ClientInfo`
object we need to update the `USER_AGENT_SUFFIX` in
`codex-rs/app-server/src/message_processor.rs`.
One other material change is in
`codex-rs/app-server/src/codex_message_processor.rs` where I eliminated
a use of the `send_event_as_notification()` method I am generally trying
to deprecate (because it blindly maps an `EventMsg` into a
`JSONNotification`) in favor of `send_server_notification()`, which
takes a `ServerNotification`, as that is intended to be a custom enum of
all notification types supported by the app server. So to make this
update, I had to introduce a new variant of `ServerNotification`,
`SessionConfigured`, which is a non-backwards compatible change with the
old `codex mcp`, and clients will have to be updated after the next
release that contains this PR. Note that
`codex-rs/app-server/tests/suite/list_resume.rs` also had to be update
to reflect this change.
I introduced `codex-rs/utils/json-to-toml/src/lib.rs` as a small utility
crate to avoid some of the copying between `mcp-server` and
`app-server`.
2025-09-30 00:06:18 -07:00
pub subcommand : McpSubcommand ,
2025-09-14 21:30:56 -07:00
}
#[ derive(Debug, clap::Subcommand) ]
pub enum McpSubcommand {
/// [experimental] List configured MCP servers.
List ( ListArgs ) ,
/// [experimental] Show details for a configured MCP server.
Get ( GetArgs ) ,
/// [experimental] Add a global MCP server entry.
Add ( AddArgs ) ,
/// [experimental] Remove a global MCP server entry.
Remove ( RemoveArgs ) ,
2025-10-03 10:43:12 -07:00
/// [experimental] Authenticate with a configured MCP server via OAuth.
/// Requires experimental_use_rmcp_client = true in config.toml.
Login ( LoginArgs ) ,
/// [experimental] Remove stored OAuth credentials for a server.
/// Requires experimental_use_rmcp_client = true in config.toml.
Logout ( LogoutArgs ) ,
2025-09-14 21:30:56 -07:00
}
#[ derive(Debug, clap::Parser) ]
pub struct ListArgs {
/// Output the configured servers as JSON.
#[ arg(long) ]
pub json : bool ,
}
#[ derive(Debug, clap::Parser) ]
pub struct GetArgs {
/// Name of the MCP server to display.
pub name : String ,
/// Output the server configuration as JSON.
#[ arg(long) ]
pub json : bool ,
}
#[ derive(Debug, clap::Parser) ]
pub struct AddArgs {
/// Name for the MCP server configuration.
pub name : String ,
2025-10-07 20:21:37 -07:00
#[ command(flatten) ]
pub transport_args : AddMcpTransportArgs ,
}
2025-09-14 21:30:56 -07:00
2025-10-07 20:21:37 -07:00
#[ derive(Debug, clap::Args) ]
#[ command(
group (
ArgGroup ::new ( " transport " )
. args ( [ " command " , " url " ] )
. required ( true )
. multiple ( false )
)
) ]
pub struct AddMcpTransportArgs {
#[ command(flatten) ]
pub stdio : Option < AddMcpStdioArgs > ,
#[ command(flatten) ]
pub streamable_http : Option < AddMcpStreamableHttpArgs > ,
}
#[ derive(Debug, clap::Args) ]
pub struct AddMcpStdioArgs {
2025-09-14 21:30:56 -07:00
/// Command to launch the MCP server.
2025-10-07 20:21:37 -07:00
/// Use --url for a streamable HTTP server.
#[ arg(
trailing_var_arg = true ,
num_args = 0 .. ,
) ]
2025-09-14 21:30:56 -07:00
pub command : Vec < String > ,
2025-10-07 20:21:37 -07:00
/// 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 < String > ,
2025-09-14 21:30:56 -07:00
}
#[ derive(Debug, clap::Parser) ]
pub struct RemoveArgs {
/// Name of the MCP server configuration to remove.
pub name : String ,
}
2025-10-03 10:43:12 -07:00
#[ derive(Debug, clap::Parser) ]
pub struct LoginArgs {
/// Name of the MCP server to authenticate with oauth.
pub name : String ,
2025-10-22 09:37:33 -07:00
/// Comma-separated list of OAuth scopes to request.
#[ arg(long, value_delimiter = ',', value_name = " SCOPE,SCOPE " ) ]
pub scopes : Vec < String > ,
2025-10-03 10:43:12 -07:00
}
#[ derive(Debug, clap::Parser) ]
pub struct LogoutArgs {
/// Name of the MCP server to deauthenticate.
pub name : String ,
}
2025-09-14 21:30:56 -07:00
impl McpCli {
fix: separate `codex mcp` into `codex mcp-server` and `codex app-server` (#4471)
This is a very large PR with some non-backwards-compatible changes.
Historically, `codex mcp` (or `codex mcp serve`) started a JSON-RPC-ish
server that had two overlapping responsibilities:
- Running an MCP server, providing some basic tool calls.
- Running the app server used to power experiences such as the VS Code
extension.
This PR aims to separate these into distinct concepts:
- `codex mcp-server` for the MCP server
- `codex app-server` for the "application server"
Note `codex mcp` still exists because it already has its own subcommands
for MCP management (`list`, `add`, etc.)
The MCP logic continues to live in `codex-rs/mcp-server` whereas the
refactored app server logic is in the new `codex-rs/app-server` folder.
Note that most of the existing integration tests in
`codex-rs/mcp-server/tests/suite` were actually for the app server, so
all the tests have been moved with the exception of
`codex-rs/mcp-server/tests/suite/mod.rs`.
Because this is already a large diff, I tried not to change more than I
had to, so `codex-rs/app-server/tests/common/mcp_process.rs` still uses
the name `McpProcess` for now, but I will do some mechanical renamings
to things like `AppServer` in subsequent PRs.
While `mcp-server` and `app-server` share some overlapping functionality
(like reading streams of JSONL and dispatching based on message types)
and some differences (completely different message types), I ended up
doing a bit of copypasta between the two crates, as both have somewhat
similar `message_processor.rs` and `outgoing_message.rs` files for now,
though I expect them to diverge more in the near future.
One material change is that of the initialize handshake for `codex
app-server`, as we no longer use the MCP types for that handshake.
Instead, we update `codex-rs/protocol/src/mcp_protocol.rs` to add an
`Initialize` variant to `ClientRequest`, which takes the `ClientInfo`
object we need to update the `USER_AGENT_SUFFIX` in
`codex-rs/app-server/src/message_processor.rs`.
One other material change is in
`codex-rs/app-server/src/codex_message_processor.rs` where I eliminated
a use of the `send_event_as_notification()` method I am generally trying
to deprecate (because it blindly maps an `EventMsg` into a
`JSONNotification`) in favor of `send_server_notification()`, which
takes a `ServerNotification`, as that is intended to be a custom enum of
all notification types supported by the app server. So to make this
update, I had to introduce a new variant of `ServerNotification`,
`SessionConfigured`, which is a non-backwards compatible change with the
old `codex mcp`, and clients will have to be updated after the next
release that contains this PR. Note that
`codex-rs/app-server/tests/suite/list_resume.rs` also had to be update
to reflect this change.
I introduced `codex-rs/utils/json-to-toml/src/lib.rs` as a small utility
crate to avoid some of the copying between `mcp-server` and
`app-server`.
2025-09-30 00:06:18 -07:00
pub async fn run ( self ) -> Result < ( ) > {
2025-09-14 21:30:56 -07:00
let McpCli {
config_overrides ,
fix: separate `codex mcp` into `codex mcp-server` and `codex app-server` (#4471)
This is a very large PR with some non-backwards-compatible changes.
Historically, `codex mcp` (or `codex mcp serve`) started a JSON-RPC-ish
server that had two overlapping responsibilities:
- Running an MCP server, providing some basic tool calls.
- Running the app server used to power experiences such as the VS Code
extension.
This PR aims to separate these into distinct concepts:
- `codex mcp-server` for the MCP server
- `codex app-server` for the "application server"
Note `codex mcp` still exists because it already has its own subcommands
for MCP management (`list`, `add`, etc.)
The MCP logic continues to live in `codex-rs/mcp-server` whereas the
refactored app server logic is in the new `codex-rs/app-server` folder.
Note that most of the existing integration tests in
`codex-rs/mcp-server/tests/suite` were actually for the app server, so
all the tests have been moved with the exception of
`codex-rs/mcp-server/tests/suite/mod.rs`.
Because this is already a large diff, I tried not to change more than I
had to, so `codex-rs/app-server/tests/common/mcp_process.rs` still uses
the name `McpProcess` for now, but I will do some mechanical renamings
to things like `AppServer` in subsequent PRs.
While `mcp-server` and `app-server` share some overlapping functionality
(like reading streams of JSONL and dispatching based on message types)
and some differences (completely different message types), I ended up
doing a bit of copypasta between the two crates, as both have somewhat
similar `message_processor.rs` and `outgoing_message.rs` files for now,
though I expect them to diverge more in the near future.
One material change is that of the initialize handshake for `codex
app-server`, as we no longer use the MCP types for that handshake.
Instead, we update `codex-rs/protocol/src/mcp_protocol.rs` to add an
`Initialize` variant to `ClientRequest`, which takes the `ClientInfo`
object we need to update the `USER_AGENT_SUFFIX` in
`codex-rs/app-server/src/message_processor.rs`.
One other material change is in
`codex-rs/app-server/src/codex_message_processor.rs` where I eliminated
a use of the `send_event_as_notification()` method I am generally trying
to deprecate (because it blindly maps an `EventMsg` into a
`JSONNotification`) in favor of `send_server_notification()`, which
takes a `ServerNotification`, as that is intended to be a custom enum of
all notification types supported by the app server. So to make this
update, I had to introduce a new variant of `ServerNotification`,
`SessionConfigured`, which is a non-backwards compatible change with the
old `codex mcp`, and clients will have to be updated after the next
release that contains this PR. Note that
`codex-rs/app-server/tests/suite/list_resume.rs` also had to be update
to reflect this change.
I introduced `codex-rs/utils/json-to-toml/src/lib.rs` as a small utility
crate to avoid some of the copying between `mcp-server` and
`app-server`.
2025-09-30 00:06:18 -07:00
subcommand ,
2025-09-14 21:30:56 -07:00
} = self ;
match subcommand {
McpSubcommand ::List ( args ) = > {
2025-10-03 13:02:26 -07:00
run_list ( & config_overrides , args ) . await ? ;
2025-09-14 21:30:56 -07:00
}
McpSubcommand ::Get ( args ) = > {
2025-10-03 13:02:26 -07:00
run_get ( & config_overrides , args ) . await ? ;
2025-09-14 21:30:56 -07:00
}
McpSubcommand ::Add ( args ) = > {
2025-10-03 13:02:26 -07:00
run_add ( & config_overrides , args ) . await ? ;
2025-09-14 21:30:56 -07:00
}
McpSubcommand ::Remove ( args ) = > {
2025-10-03 13:02:26 -07:00
run_remove ( & config_overrides , args ) . await ? ;
2025-09-14 21:30:56 -07:00
}
2025-10-03 10:43:12 -07:00
McpSubcommand ::Login ( args ) = > {
run_login ( & config_overrides , args ) . await ? ;
}
McpSubcommand ::Logout ( args ) = > {
2025-10-03 13:02:26 -07:00
run_logout ( & config_overrides , args ) . await ? ;
2025-10-03 10:43:12 -07:00
}
2025-09-14 21:30:56 -07:00
}
Ok ( ( ) )
}
}
2025-10-03 13:02:26 -07:00
async fn run_add ( config_overrides : & CliConfigOverrides , add_args : AddArgs ) -> Result < ( ) > {
2025-09-14 21:30:56 -07:00
// Validate any provided overrides even though they are not currently applied.
Windows Sandbox - Alpha version (#4905)
- Added the new codex-windows-sandbox crate that builds both a library
entry point (run_windows_sandbox_capture) and a CLI executable to launch
commands inside a Windows restricted-token sandbox, including ACL
management, capability SID provisioning, network lockdown, and output
capture
(windows-sandbox-rs/src/lib.rs:167, windows-sandbox-rs/src/main.rs:54).
- Introduced the experimental WindowsSandbox feature flag and wiring so
Windows builds can opt into the sandbox:
SandboxType::WindowsRestrictedToken, the in-process execution path, and
platform sandbox selection now honor the flag (core/src/features.rs:47,
core/src/config.rs:1224, core/src/safety.rs:19,
core/src/sandboxing/mod.rs:69, core/src/exec.rs:79,
core/src/exec.rs:172).
- Updated workspace metadata to include the new crate and its
Windows-specific dependencies so the core crate can link against it
(codex-rs/
Cargo.toml:91, core/Cargo.toml:86).
- Added a PowerShell bootstrap script that installs the Windows
toolchain, required CLI utilities, and builds the workspace to ease
development
on the platform (scripts/setup-windows.ps1:1).
- Landed a Python smoke-test suite that exercises
read-only/workspace-write policies, ACL behavior, and network denial for
the Windows sandbox
binary (windows-sandbox-rs/sandbox_smoketests.py:1).
2025-10-30 15:51:57 -07:00
let overrides = config_overrides
. parse_overrides ( )
. map_err ( anyhow ::Error ::msg ) ? ;
2025-10-15 09:27:40 -07:00
let config = Config ::load_with_cli_overrides ( overrides , ConfigOverrides ::default ( ) )
. await
. context ( " failed to load configuration " ) ? ;
2025-09-14 21:30:56 -07:00
2025-10-07 20:21:37 -07:00
let AddArgs {
name ,
transport_args ,
} = add_args ;
2025-09-14 21:30:56 -07:00
validate_server_name ( & name ) ? ;
let codex_home = find_codex_home ( ) . context ( " failed to resolve CODEX_HOME " ) ? ;
let mut servers = load_global_mcp_servers ( & codex_home )
2025-10-03 13:02:26 -07:00
. await
2025-09-14 21:30:56 -07:00
. with_context ( | | format! ( " failed to load MCP servers from {} " , codex_home . display ( ) ) ) ? ;
2025-10-07 20:21:37 -07:00
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 < String > = command_parts . collect ( ) ;
let env_map = if stdio . env . is_empty ( ) {
None
} else {
Some ( stdio . env . into_iter ( ) . collect ::< HashMap < _ , _ > > ( ) )
} ;
McpServerTransportConfig ::Stdio {
command : command_bin ,
args : command_args ,
env : env_map ,
2025-10-16 21:24:43 -07:00
env_vars : Vec ::new ( ) ,
cwd : None ,
2025-10-07 20:21:37 -07:00
}
}
AddMcpTransportArgs {
2025-10-15 09:27:40 -07:00
streamable_http :
Some ( AddMcpStreamableHttpArgs {
url ,
bearer_token_env_var ,
} ) ,
2025-10-07 20:21:37 -07:00
..
} = > McpServerTransportConfig ::StreamableHttp {
2025-10-15 09:27:40 -07:00
url ,
bearer_token_env_var ,
2025-10-16 20:15:47 -07:00
http_headers : None ,
env_http_headers : None ,
2025-09-26 18:24:01 -07:00
} ,
2025-10-07 20:21:37 -07:00
AddMcpTransportArgs { .. } = > bail! ( " exactly one of --command or --url must be provided " ) ,
} ;
let new_entry = McpServerConfig {
2025-10-15 09:27:40 -07:00
transport : transport . clone ( ) ,
2025-10-08 13:24:51 -07:00
enabled : true ,
2025-09-22 10:30:59 -07:00
startup_timeout_sec : None ,
tool_timeout_sec : None ,
2025-10-20 15:35:36 -07:00
enabled_tools : None ,
disabled_tools : None ,
2025-09-14 21:30:56 -07:00
} ;
servers . insert ( name . clone ( ) , new_entry ) ;
2025-10-29 20:52:46 +00:00
ConfigEditsBuilder ::new ( & codex_home )
. replace_mcp_servers ( & servers )
. apply ( )
. await
2025-09-14 21:30:56 -07:00
. with_context ( | | format! ( " failed to write MCP servers to {} " , codex_home . display ( ) ) ) ? ;
println! ( " Added global MCP server ' {name} '. " ) ;
2025-10-15 09:27:40 -07:00
if let McpServerTransportConfig ::StreamableHttp {
url ,
bearer_token_env_var : None ,
2025-10-16 20:15:47 -07:00
http_headers ,
env_http_headers ,
2025-10-15 09:27:40 -07:00
} = transport
{
2025-10-24 15:32:15 -07:00
match supports_oauth_login ( & url ) . await {
Ok ( true ) = > {
if ! config . features . enabled ( Feature ::RmcpClient ) {
println! (
" MCP server supports login. Add `experimental_use_rmcp_client = true` \
to your config . toml and run ` codex mcp login { name } ` to login . "
) ;
} else {
println! ( " Detected OAuth support. Starting OAuth flow… " ) ;
perform_oauth_login (
& name ,
& url ,
config . mcp_oauth_credentials_store_mode ,
http_headers . clone ( ) ,
env_http_headers . clone ( ) ,
& Vec ::new ( ) ,
)
. await ? ;
println! ( " Successfully logged in. " ) ;
}
}
Ok ( false ) = > { }
Err ( _ ) = > println! (
" MCP server may or may not require login. Run `codex mcp login {name}` to login. "
) ,
}
2025-10-15 09:27:40 -07:00
}
2025-09-14 21:30:56 -07:00
Ok ( ( ) )
}
2025-10-03 13:02:26 -07:00
async fn run_remove ( config_overrides : & CliConfigOverrides , remove_args : RemoveArgs ) -> Result < ( ) > {
Windows Sandbox - Alpha version (#4905)
- Added the new codex-windows-sandbox crate that builds both a library
entry point (run_windows_sandbox_capture) and a CLI executable to launch
commands inside a Windows restricted-token sandbox, including ACL
management, capability SID provisioning, network lockdown, and output
capture
(windows-sandbox-rs/src/lib.rs:167, windows-sandbox-rs/src/main.rs:54).
- Introduced the experimental WindowsSandbox feature flag and wiring so
Windows builds can opt into the sandbox:
SandboxType::WindowsRestrictedToken, the in-process execution path, and
platform sandbox selection now honor the flag (core/src/features.rs:47,
core/src/config.rs:1224, core/src/safety.rs:19,
core/src/sandboxing/mod.rs:69, core/src/exec.rs:79,
core/src/exec.rs:172).
- Updated workspace metadata to include the new crate and its
Windows-specific dependencies so the core crate can link against it
(codex-rs/
Cargo.toml:91, core/Cargo.toml:86).
- Added a PowerShell bootstrap script that installs the Windows
toolchain, required CLI utilities, and builds the workspace to ease
development
on the platform (scripts/setup-windows.ps1:1).
- Landed a Python smoke-test suite that exercises
read-only/workspace-write policies, ACL behavior, and network denial for
the Windows sandbox
binary (windows-sandbox-rs/sandbox_smoketests.py:1).
2025-10-30 15:51:57 -07:00
config_overrides
. parse_overrides ( )
. map_err ( anyhow ::Error ::msg ) ? ;
2025-09-14 21:30:56 -07:00
let RemoveArgs { name } = remove_args ;
validate_server_name ( & name ) ? ;
let codex_home = find_codex_home ( ) . context ( " failed to resolve CODEX_HOME " ) ? ;
let mut servers = load_global_mcp_servers ( & codex_home )
2025-10-03 13:02:26 -07:00
. await
2025-09-14 21:30:56 -07:00
. with_context ( | | format! ( " failed to load MCP servers from {} " , codex_home . display ( ) ) ) ? ;
let removed = servers . remove ( & name ) . is_some ( ) ;
if removed {
2025-10-29 20:52:46 +00:00
ConfigEditsBuilder ::new ( & codex_home )
. replace_mcp_servers ( & servers )
. apply ( )
. await
2025-09-14 21:30:56 -07:00
. with_context ( | | format! ( " failed to write MCP servers to {} " , codex_home . display ( ) ) ) ? ;
}
if removed {
println! ( " Removed global MCP server ' {name} '. " ) ;
} else {
println! ( " No MCP server named ' {name} ' found. " ) ;
}
Ok ( ( ) )
}
2025-10-03 10:43:12 -07:00
async fn run_login ( config_overrides : & CliConfigOverrides , login_args : LoginArgs ) -> Result < ( ) > {
Windows Sandbox - Alpha version (#4905)
- Added the new codex-windows-sandbox crate that builds both a library
entry point (run_windows_sandbox_capture) and a CLI executable to launch
commands inside a Windows restricted-token sandbox, including ACL
management, capability SID provisioning, network lockdown, and output
capture
(windows-sandbox-rs/src/lib.rs:167, windows-sandbox-rs/src/main.rs:54).
- Introduced the experimental WindowsSandbox feature flag and wiring so
Windows builds can opt into the sandbox:
SandboxType::WindowsRestrictedToken, the in-process execution path, and
platform sandbox selection now honor the flag (core/src/features.rs:47,
core/src/config.rs:1224, core/src/safety.rs:19,
core/src/sandboxing/mod.rs:69, core/src/exec.rs:79,
core/src/exec.rs:172).
- Updated workspace metadata to include the new crate and its
Windows-specific dependencies so the core crate can link against it
(codex-rs/
Cargo.toml:91, core/Cargo.toml:86).
- Added a PowerShell bootstrap script that installs the Windows
toolchain, required CLI utilities, and builds the workspace to ease
development
on the platform (scripts/setup-windows.ps1:1).
- Landed a Python smoke-test suite that exercises
read-only/workspace-write policies, ACL behavior, and network denial for
the Windows sandbox
binary (windows-sandbox-rs/sandbox_smoketests.py:1).
2025-10-30 15:51:57 -07:00
let overrides = config_overrides
. parse_overrides ( )
. map_err ( anyhow ::Error ::msg ) ? ;
2025-10-03 10:43:12 -07:00
let config = Config ::load_with_cli_overrides ( overrides , ConfigOverrides ::default ( ) )
2025-10-03 13:02:26 -07:00
. await
2025-10-03 10:43:12 -07:00
. context ( " failed to load configuration " ) ? ;
2025-10-14 18:50:00 +01:00
if ! config . features . enabled ( Feature ::RmcpClient ) {
2025-11-04 13:02:27 -08:00
bail! (
" OAuth login is only supported when [features].rmcp_client is true in config.toml. See https://github.com/openai/codex/blob/main/docs/config.md#feature-flags for details. "
) ;
2025-10-03 10:43:12 -07:00
}
2025-10-22 09:37:33 -07:00
let LoginArgs { name , scopes } = login_args ;
2025-10-03 10:43:12 -07:00
let Some ( server ) = config . mcp_servers . get ( & name ) else {
bail! ( " No MCP server named '{name}' found. " ) ;
} ;
2025-10-16 20:15:47 -07:00
let ( url , http_headers , env_http_headers ) = match & server . transport {
McpServerTransportConfig ::StreamableHttp {
url ,
http_headers ,
env_http_headers ,
..
} = > ( url . clone ( ) , http_headers . clone ( ) , env_http_headers . clone ( ) ) ,
2025-10-03 10:43:12 -07:00
_ = > bail! ( " OAuth login is only supported for streamable HTTP servers. " ) ,
} ;
2025-10-16 20:15:47 -07:00
perform_oauth_login (
& name ,
& url ,
config . mcp_oauth_credentials_store_mode ,
http_headers ,
env_http_headers ,
2025-10-22 09:37:33 -07:00
& scopes ,
2025-10-16 20:15:47 -07:00
)
. await ? ;
2025-10-03 10:43:12 -07:00
println! ( " Successfully logged in to MCP server ' {name} '. " ) ;
Ok ( ( ) )
}
2025-10-03 13:02:26 -07:00
async fn run_logout ( config_overrides : & CliConfigOverrides , logout_args : LogoutArgs ) -> Result < ( ) > {
Windows Sandbox - Alpha version (#4905)
- Added the new codex-windows-sandbox crate that builds both a library
entry point (run_windows_sandbox_capture) and a CLI executable to launch
commands inside a Windows restricted-token sandbox, including ACL
management, capability SID provisioning, network lockdown, and output
capture
(windows-sandbox-rs/src/lib.rs:167, windows-sandbox-rs/src/main.rs:54).
- Introduced the experimental WindowsSandbox feature flag and wiring so
Windows builds can opt into the sandbox:
SandboxType::WindowsRestrictedToken, the in-process execution path, and
platform sandbox selection now honor the flag (core/src/features.rs:47,
core/src/config.rs:1224, core/src/safety.rs:19,
core/src/sandboxing/mod.rs:69, core/src/exec.rs:79,
core/src/exec.rs:172).
- Updated workspace metadata to include the new crate and its
Windows-specific dependencies so the core crate can link against it
(codex-rs/
Cargo.toml:91, core/Cargo.toml:86).
- Added a PowerShell bootstrap script that installs the Windows
toolchain, required CLI utilities, and builds the workspace to ease
development
on the platform (scripts/setup-windows.ps1:1).
- Landed a Python smoke-test suite that exercises
read-only/workspace-write policies, ACL behavior, and network denial for
the Windows sandbox
binary (windows-sandbox-rs/sandbox_smoketests.py:1).
2025-10-30 15:51:57 -07:00
let overrides = config_overrides
. parse_overrides ( )
. map_err ( anyhow ::Error ::msg ) ? ;
2025-10-03 10:43:12 -07:00
let config = Config ::load_with_cli_overrides ( overrides , ConfigOverrides ::default ( ) )
2025-10-03 13:02:26 -07:00
. await
2025-10-03 10:43:12 -07:00
. context ( " failed to load configuration " ) ? ;
let LogoutArgs { name } = logout_args ;
let server = config
. mcp_servers
. get ( & name )
. ok_or_else ( | | anyhow! ( " No MCP server named '{name}' found in configuration. " ) ) ? ;
let url = match & server . transport {
McpServerTransportConfig ::StreamableHttp { url , .. } = > url . clone ( ) ,
_ = > bail! ( " OAuth logout is only supported for streamable_http transports. " ) ,
} ;
2025-10-07 19:39:32 -07:00
match delete_oauth_tokens ( & name , & url , config . mcp_oauth_credentials_store_mode ) {
2025-10-03 10:43:12 -07:00
Ok ( true ) = > println! ( " Removed OAuth credentials for ' {name} '. " ) ,
Ok ( false ) = > println! ( " No OAuth credentials stored for ' {name} '. " ) ,
Err ( err ) = > return Err ( anyhow! ( " failed to delete OAuth credentials: {err} " ) ) ,
}
Ok ( ( ) )
}
2025-10-03 13:02:26 -07:00
async fn run_list ( config_overrides : & CliConfigOverrides , list_args : ListArgs ) -> Result < ( ) > {
Windows Sandbox - Alpha version (#4905)
- Added the new codex-windows-sandbox crate that builds both a library
entry point (run_windows_sandbox_capture) and a CLI executable to launch
commands inside a Windows restricted-token sandbox, including ACL
management, capability SID provisioning, network lockdown, and output
capture
(windows-sandbox-rs/src/lib.rs:167, windows-sandbox-rs/src/main.rs:54).
- Introduced the experimental WindowsSandbox feature flag and wiring so
Windows builds can opt into the sandbox:
SandboxType::WindowsRestrictedToken, the in-process execution path, and
platform sandbox selection now honor the flag (core/src/features.rs:47,
core/src/config.rs:1224, core/src/safety.rs:19,
core/src/sandboxing/mod.rs:69, core/src/exec.rs:79,
core/src/exec.rs:172).
- Updated workspace metadata to include the new crate and its
Windows-specific dependencies so the core crate can link against it
(codex-rs/
Cargo.toml:91, core/Cargo.toml:86).
- Added a PowerShell bootstrap script that installs the Windows
toolchain, required CLI utilities, and builds the workspace to ease
development
on the platform (scripts/setup-windows.ps1:1).
- Landed a Python smoke-test suite that exercises
read-only/workspace-write policies, ACL behavior, and network denial for
the Windows sandbox
binary (windows-sandbox-rs/sandbox_smoketests.py:1).
2025-10-30 15:51:57 -07:00
let overrides = config_overrides
. parse_overrides ( )
. map_err ( anyhow ::Error ::msg ) ? ;
2025-09-14 21:30:56 -07:00
let config = Config ::load_with_cli_overrides ( overrides , ConfigOverrides ::default ( ) )
2025-10-03 13:02:26 -07:00
. await
2025-09-14 21:30:56 -07:00
. context ( " failed to load configuration " ) ? ;
let mut entries : Vec < _ > = config . mcp_servers . iter ( ) . collect ( ) ;
entries . sort_by ( | ( a , _ ) , ( b , _ ) | a . cmp ( b ) ) ;
2025-10-08 14:37:57 -07:00
let auth_statuses = compute_auth_statuses (
config . mcp_servers . iter ( ) ,
config . mcp_oauth_credentials_store_mode ,
)
. await ;
2025-09-14 21:30:56 -07:00
if list_args . json {
let json_entries : Vec < _ > = entries
. into_iter ( )
. map ( | ( name , cfg ) | {
2025-10-08 14:37:57 -07:00
let auth_status = auth_statuses
. get ( name . as_str ( ) )
2025-10-20 16:23:26 -07:00
. map ( | entry | entry . auth_status )
2025-10-08 14:37:57 -07:00
. unwrap_or ( McpAuthStatus ::Unsupported ) ;
2025-09-26 18:24:01 -07:00
let transport = match & cfg . transport {
2025-10-16 21:24:43 -07:00
McpServerTransportConfig ::Stdio {
command ,
args ,
env ,
env_vars ,
cwd ,
} = > serde_json ::json! ( {
2025-09-26 18:24:01 -07:00
" type " : " stdio " ,
" command " : command ,
" args " : args ,
" env " : env ,
2025-10-16 21:24:43 -07:00
" env_vars " : env_vars ,
" cwd " : cwd ,
2025-09-26 18:24:01 -07:00
} ) ,
2025-10-07 20:21:37 -07:00
McpServerTransportConfig ::StreamableHttp {
url ,
bearer_token_env_var ,
2025-10-16 20:15:47 -07:00
http_headers ,
env_http_headers ,
2025-10-07 20:21:37 -07:00
} = > {
2025-09-26 18:24:01 -07:00
serde_json ::json! ( {
" type " : " streamable_http " ,
" url " : url ,
2025-10-07 20:21:37 -07:00
" bearer_token_env_var " : bearer_token_env_var ,
2025-10-16 20:15:47 -07:00
" http_headers " : http_headers ,
" env_http_headers " : env_http_headers ,
2025-09-26 18:24:01 -07:00
} )
}
} ;
2025-09-14 21:30:56 -07:00
serde_json ::json! ( {
" name " : name ,
2025-10-08 13:24:51 -07:00
" enabled " : cfg . enabled ,
2025-09-26 18:24:01 -07:00
" transport " : transport ,
2025-09-22 10:30:59 -07:00
" startup_timeout_sec " : cfg
. startup_timeout_sec
. map ( | timeout | timeout . as_secs_f64 ( ) ) ,
" tool_timeout_sec " : cfg
. tool_timeout_sec
. map ( | timeout | timeout . as_secs_f64 ( ) ) ,
2025-10-08 14:37:57 -07:00
" auth_status " : auth_status ,
2025-09-14 21:30:56 -07:00
} )
} )
. collect ( ) ;
let output = serde_json ::to_string_pretty ( & json_entries ) ? ;
println! ( " {output} " ) ;
return Ok ( ( ) ) ;
}
if entries . is_empty ( ) {
println! ( " No MCP servers configured yet. Try `codex mcp add my-tool -- my-command`. " ) ;
return Ok ( ( ) ) ;
}
2025-10-16 21:24:43 -07:00
let mut stdio_rows : Vec < [ String ; 7 ] > = Vec ::new ( ) ;
2025-10-08 14:37:57 -07:00
let mut http_rows : Vec < [ String ; 5 ] > = Vec ::new ( ) ;
2025-09-14 21:30:56 -07:00
2025-09-26 18:24:01 -07:00
for ( name , cfg ) in entries {
match & cfg . transport {
2025-10-16 21:24:43 -07:00
McpServerTransportConfig ::Stdio {
command ,
args ,
env ,
env_vars ,
cwd ,
} = > {
2025-09-26 18:24:01 -07:00
let args_display = if args . is_empty ( ) {
" - " . to_string ( )
} else {
args . join ( " " )
} ;
2025-10-16 21:24:43 -07:00
let env_display = format_env_display ( env . as_ref ( ) , env_vars ) ;
let cwd_display = cwd
. as_ref ( )
. map ( | path | path . display ( ) . to_string ( ) )
. filter ( | value | ! value . is_empty ( ) )
. unwrap_or_else ( | | " - " . to_string ( ) ) ;
2025-10-08 13:24:51 -07:00
let status = if cfg . enabled {
" enabled " . to_string ( )
} else {
" disabled " . to_string ( )
} ;
2025-10-08 14:37:57 -07:00
let auth_status = auth_statuses
. get ( name . as_str ( ) )
2025-10-20 16:23:26 -07:00
. map ( | entry | entry . auth_status )
2025-10-08 14:37:57 -07:00
. unwrap_or ( McpAuthStatus ::Unsupported )
. to_string ( ) ;
2025-10-08 13:24:51 -07:00
stdio_rows . push ( [
name . clone ( ) ,
command . clone ( ) ,
args_display ,
env_display ,
2025-10-16 21:24:43 -07:00
cwd_display ,
2025-10-08 13:24:51 -07:00
status ,
2025-10-08 14:37:57 -07:00
auth_status ,
2025-10-08 13:24:51 -07:00
] ) ;
2025-09-14 21:30:56 -07:00
}
2025-10-07 20:21:37 -07:00
McpServerTransportConfig ::StreamableHttp {
url ,
bearer_token_env_var ,
2025-10-16 20:15:47 -07:00
..
2025-10-07 20:21:37 -07:00
} = > {
2025-10-08 13:24:51 -07:00
let status = if cfg . enabled {
" enabled " . to_string ( )
} else {
" disabled " . to_string ( )
} ;
2025-10-08 14:37:57 -07:00
let auth_status = auth_statuses
. get ( name . as_str ( ) )
2025-10-20 16:23:26 -07:00
. map ( | entry | entry . auth_status )
2025-10-08 14:37:57 -07:00
. unwrap_or ( McpAuthStatus ::Unsupported )
. to_string ( ) ;
2025-10-24 15:30:20 -07:00
let bearer_token_display =
bearer_token_env_var . as_deref ( ) . unwrap_or ( " - " ) . to_string ( ) ;
2025-10-07 20:21:37 -07:00
http_rows . push ( [
name . clone ( ) ,
url . clone ( ) ,
2025-10-24 15:30:20 -07:00
bearer_token_display ,
2025-10-08 13:24:51 -07:00
status ,
2025-10-08 14:37:57 -07:00
auth_status ,
2025-10-07 20:21:37 -07:00
] ) ;
2025-09-26 18:24:01 -07:00
}
}
2025-09-14 21:30:56 -07:00
}
2025-09-26 18:24:01 -07:00
if ! stdio_rows . is_empty ( ) {
2025-10-08 13:24:51 -07:00
let mut widths = [
" Name " . len ( ) ,
" Command " . len ( ) ,
" Args " . len ( ) ,
" Env " . len ( ) ,
2025-10-16 21:24:43 -07:00
" Cwd " . len ( ) ,
2025-10-08 13:24:51 -07:00
" Status " . len ( ) ,
2025-10-08 14:37:57 -07:00
" Auth " . len ( ) ,
2025-10-08 13:24:51 -07:00
] ;
2025-09-26 18:24:01 -07:00
for row in & stdio_rows {
for ( i , cell ) in row . iter ( ) . enumerate ( ) {
widths [ i ] = widths [ i ] . max ( cell . len ( ) ) ;
}
2025-09-14 21:30:56 -07:00
}
println! (
2025-10-16 21:24:43 -07:00
" {name:<name_w$} {command:<cmd_w$} {args:<args_w$} {env:<env_w$} {cwd:<cwd_w$} {status:<status_w$} {auth:<auth_w$} " ,
2025-10-08 14:37:57 -07:00
name = " Name " ,
command = " Command " ,
args = " Args " ,
env = " Env " ,
2025-10-16 21:24:43 -07:00
cwd = " Cwd " ,
2025-10-08 14:37:57 -07:00
status = " Status " ,
auth = " Auth " ,
2025-09-14 21:30:56 -07:00
name_w = widths [ 0 ] ,
cmd_w = widths [ 1 ] ,
args_w = widths [ 2 ] ,
env_w = widths [ 3 ] ,
2025-10-16 21:24:43 -07:00
cwd_w = widths [ 4 ] ,
status_w = widths [ 5 ] ,
auth_w = widths [ 6 ] ,
2025-09-14 21:30:56 -07:00
) ;
2025-09-26 18:24:01 -07:00
for row in & stdio_rows {
println! (
2025-10-16 21:24:43 -07:00
" {name:<name_w$} {command:<cmd_w$} {args:<args_w$} {env:<env_w$} {cwd:<cwd_w$} {status:<status_w$} {auth:<auth_w$} " ,
2025-10-08 14:37:57 -07:00
name = row [ 0 ] . as_str ( ) ,
command = row [ 1 ] . as_str ( ) ,
args = row [ 2 ] . as_str ( ) ,
env = row [ 3 ] . as_str ( ) ,
2025-10-16 21:24:43 -07:00
cwd = row [ 4 ] . as_str ( ) ,
status = row [ 5 ] . as_str ( ) ,
auth = row [ 6 ] . as_str ( ) ,
2025-09-26 18:24:01 -07:00
name_w = widths [ 0 ] ,
cmd_w = widths [ 1 ] ,
args_w = widths [ 2 ] ,
env_w = widths [ 3 ] ,
2025-10-16 21:24:43 -07:00
cwd_w = widths [ 4 ] ,
status_w = widths [ 5 ] ,
auth_w = widths [ 6 ] ,
2025-09-26 18:24:01 -07:00
) ;
}
}
if ! stdio_rows . is_empty ( ) & & ! http_rows . is_empty ( ) {
println! ( ) ;
}
if ! http_rows . is_empty ( ) {
2025-10-08 13:24:51 -07:00
let mut widths = [
" Name " . len ( ) ,
" Url " . len ( ) ,
" Bearer Token Env Var " . len ( ) ,
" Status " . len ( ) ,
2025-10-08 14:37:57 -07:00
" Auth " . len ( ) ,
2025-10-08 13:24:51 -07:00
] ;
2025-09-26 18:24:01 -07:00
for row in & http_rows {
for ( i , cell ) in row . iter ( ) . enumerate ( ) {
widths [ i ] = widths [ i ] . max ( cell . len ( ) ) ;
}
}
println! (
2025-10-08 14:37:57 -07:00
" {name:<name_w$} {url:<url_w$} {token:<token_w$} {status:<status_w$} {auth:<auth_w$} " ,
name = " Name " ,
url = " Url " ,
token = " Bearer Token Env Var " ,
status = " Status " ,
auth = " Auth " ,
2025-09-26 18:24:01 -07:00
name_w = widths [ 0 ] ,
url_w = widths [ 1 ] ,
token_w = widths [ 2 ] ,
2025-10-08 13:24:51 -07:00
status_w = widths [ 3 ] ,
2025-10-08 14:37:57 -07:00
auth_w = widths [ 4 ] ,
2025-09-26 18:24:01 -07:00
) ;
for row in & http_rows {
println! (
2025-10-08 14:37:57 -07:00
" {name:<name_w$} {url:<url_w$} {token:<token_w$} {status:<status_w$} {auth:<auth_w$} " ,
name = row [ 0 ] . as_str ( ) ,
url = row [ 1 ] . as_str ( ) ,
token = row [ 2 ] . as_str ( ) ,
status = row [ 3 ] . as_str ( ) ,
auth = row [ 4 ] . as_str ( ) ,
2025-09-26 18:24:01 -07:00
name_w = widths [ 0 ] ,
url_w = widths [ 1 ] ,
token_w = widths [ 2 ] ,
2025-10-08 13:24:51 -07:00
status_w = widths [ 3 ] ,
2025-10-08 14:37:57 -07:00
auth_w = widths [ 4 ] ,
2025-09-26 18:24:01 -07:00
) ;
}
2025-09-14 21:30:56 -07:00
}
Ok ( ( ) )
}
2025-10-03 13:02:26 -07:00
async fn run_get ( config_overrides : & CliConfigOverrides , get_args : GetArgs ) -> Result < ( ) > {
Windows Sandbox - Alpha version (#4905)
- Added the new codex-windows-sandbox crate that builds both a library
entry point (run_windows_sandbox_capture) and a CLI executable to launch
commands inside a Windows restricted-token sandbox, including ACL
management, capability SID provisioning, network lockdown, and output
capture
(windows-sandbox-rs/src/lib.rs:167, windows-sandbox-rs/src/main.rs:54).
- Introduced the experimental WindowsSandbox feature flag and wiring so
Windows builds can opt into the sandbox:
SandboxType::WindowsRestrictedToken, the in-process execution path, and
platform sandbox selection now honor the flag (core/src/features.rs:47,
core/src/config.rs:1224, core/src/safety.rs:19,
core/src/sandboxing/mod.rs:69, core/src/exec.rs:79,
core/src/exec.rs:172).
- Updated workspace metadata to include the new crate and its
Windows-specific dependencies so the core crate can link against it
(codex-rs/
Cargo.toml:91, core/Cargo.toml:86).
- Added a PowerShell bootstrap script that installs the Windows
toolchain, required CLI utilities, and builds the workspace to ease
development
on the platform (scripts/setup-windows.ps1:1).
- Landed a Python smoke-test suite that exercises
read-only/workspace-write policies, ACL behavior, and network denial for
the Windows sandbox
binary (windows-sandbox-rs/sandbox_smoketests.py:1).
2025-10-30 15:51:57 -07:00
let overrides = config_overrides
. parse_overrides ( )
. map_err ( anyhow ::Error ::msg ) ? ;
2025-09-14 21:30:56 -07:00
let config = Config ::load_with_cli_overrides ( overrides , ConfigOverrides ::default ( ) )
2025-10-03 13:02:26 -07:00
. await
2025-09-14 21:30:56 -07:00
. context ( " failed to load configuration " ) ? ;
let Some ( server ) = config . mcp_servers . get ( & get_args . name ) else {
bail! ( " No MCP server named '{name}' found. " , name = get_args . name ) ;
} ;
if get_args . json {
2025-09-26 18:24:01 -07:00
let transport = match & server . transport {
2025-10-16 21:24:43 -07:00
McpServerTransportConfig ::Stdio {
command ,
args ,
env ,
env_vars ,
cwd ,
} = > serde_json ::json! ( {
2025-09-26 18:24:01 -07:00
" type " : " stdio " ,
" command " : command ,
" args " : args ,
" env " : env ,
2025-10-16 21:24:43 -07:00
" env_vars " : env_vars ,
" cwd " : cwd ,
2025-09-26 18:24:01 -07:00
} ) ,
2025-10-07 20:21:37 -07:00
McpServerTransportConfig ::StreamableHttp {
url ,
bearer_token_env_var ,
2025-10-16 20:15:47 -07:00
http_headers ,
env_http_headers ,
2025-10-07 20:21:37 -07:00
} = > serde_json ::json! ( {
2025-09-26 18:24:01 -07:00
" type " : " streamable_http " ,
" url " : url ,
2025-10-07 20:21:37 -07:00
" bearer_token_env_var " : bearer_token_env_var ,
2025-10-16 20:15:47 -07:00
" http_headers " : http_headers ,
" env_http_headers " : env_http_headers ,
2025-09-26 18:24:01 -07:00
} ) ,
} ;
2025-09-14 21:30:56 -07:00
let output = serde_json ::to_string_pretty ( & serde_json ::json! ( {
" name " : get_args . name ,
2025-10-08 13:24:51 -07:00
" enabled " : server . enabled ,
2025-09-26 18:24:01 -07:00
" transport " : transport ,
2025-10-20 15:35:36 -07:00
" enabled_tools " : server . enabled_tools . clone ( ) ,
" disabled_tools " : server . disabled_tools . clone ( ) ,
2025-09-22 10:30:59 -07:00
" startup_timeout_sec " : server
. startup_timeout_sec
. map ( | timeout | timeout . as_secs_f64 ( ) ) ,
" tool_timeout_sec " : server
. tool_timeout_sec
. map ( | timeout | timeout . as_secs_f64 ( ) ) ,
2025-09-14 21:30:56 -07:00
} ) ) ? ;
println! ( " {output} " ) ;
return Ok ( ( ) ) ;
}
2025-10-20 15:35:36 -07:00
if ! server . enabled {
println! ( " {} (disabled) " , get_args . name ) ;
return Ok ( ( ) ) ;
}
2025-09-14 21:30:56 -07:00
println! ( " {} " , get_args . name ) ;
2025-10-08 13:24:51 -07:00
println! ( " enabled: {} " , server . enabled ) ;
2025-10-20 15:35:36 -07:00
let format_tool_list = | tools : & Option < Vec < String > > | -> String {
match tools {
Some ( list ) if list . is_empty ( ) = > " [] " . to_string ( ) ,
Some ( list ) = > list . join ( " , " ) ,
None = > " - " . to_string ( ) ,
}
} ;
if server . enabled_tools . is_some ( ) {
let enabled_tools_display = format_tool_list ( & server . enabled_tools ) ;
println! ( " enabled_tools: {enabled_tools_display} " ) ;
}
if server . disabled_tools . is_some ( ) {
let disabled_tools_display = format_tool_list ( & server . disabled_tools ) ;
println! ( " disabled_tools: {disabled_tools_display} " ) ;
}
2025-09-26 18:24:01 -07:00
match & server . transport {
2025-10-16 21:24:43 -07:00
McpServerTransportConfig ::Stdio {
command ,
args ,
env ,
env_vars ,
cwd ,
} = > {
2025-09-26 18:24:01 -07:00
println! ( " transport: stdio " ) ;
println! ( " command: {command} " ) ;
let args_display = if args . is_empty ( ) {
" - " . to_string ( )
} else {
args . join ( " " )
} ;
println! ( " args: {args_display} " ) ;
2025-10-16 21:24:43 -07:00
let cwd_display = cwd
. as_ref ( )
. map ( | path | path . display ( ) . to_string ( ) )
. filter ( | value | ! value . is_empty ( ) )
. unwrap_or_else ( | | " - " . to_string ( ) ) ;
println! ( " cwd: {cwd_display} " ) ;
let env_display = format_env_display ( env . as_ref ( ) , env_vars ) ;
2025-09-26 18:24:01 -07:00
println! ( " env: {env_display} " ) ;
2025-09-14 21:30:56 -07:00
}
2025-10-07 20:21:37 -07:00
McpServerTransportConfig ::StreamableHttp {
url ,
bearer_token_env_var ,
2025-10-16 20:15:47 -07:00
http_headers ,
env_http_headers ,
2025-10-07 20:21:37 -07:00
} = > {
2025-09-26 18:24:01 -07:00
println! ( " transport: streamable_http " ) ;
println! ( " url: {url} " ) ;
2025-10-24 15:30:20 -07:00
let bearer_token_display = bearer_token_env_var . as_deref ( ) . unwrap_or ( " - " ) ;
println! ( " bearer_token_env_var: {bearer_token_display} " ) ;
2025-10-16 20:15:47 -07:00
let headers_display = match http_headers {
Some ( map ) if ! map . is_empty ( ) = > {
let mut pairs : Vec < _ > = map . iter ( ) . collect ( ) ;
pairs . sort_by ( | ( a , _ ) , ( b , _ ) | a . cmp ( b ) ) ;
pairs
. into_iter ( )
2025-10-24 15:30:20 -07:00
. map ( | ( k , _ ) | format! ( " {k} =***** " ) )
2025-10-16 20:15:47 -07:00
. collect ::< Vec < _ > > ( )
. join ( " , " )
}
_ = > " - " . to_string ( ) ,
} ;
println! ( " http_headers: {headers_display} " ) ;
let env_headers_display = match env_http_headers {
Some ( map ) if ! map . is_empty ( ) = > {
let mut pairs : Vec < _ > = map . iter ( ) . collect ( ) ;
pairs . sort_by ( | ( a , _ ) , ( b , _ ) | a . cmp ( b ) ) ;
pairs
. into_iter ( )
2025-10-24 15:30:20 -07:00
. map ( | ( k , var ) | format! ( " {k} = {var} " ) )
2025-10-16 20:15:47 -07:00
. collect ::< Vec < _ > > ( )
. join ( " , " )
}
_ = > " - " . to_string ( ) ,
} ;
println! ( " env_http_headers: {env_headers_display} " ) ;
2025-09-26 18:24:01 -07:00
}
}
2025-09-22 10:30:59 -07:00
if let Some ( timeout ) = server . startup_timeout_sec {
println! ( " startup_timeout_sec: {} " , timeout . as_secs_f64 ( ) ) ;
}
if let Some ( timeout ) = server . tool_timeout_sec {
println! ( " tool_timeout_sec: {} " , timeout . as_secs_f64 ( ) ) ;
2025-09-14 21:30:56 -07:00
}
println! ( " remove: codex mcp remove {} " , get_args . name ) ;
Ok ( ( ) )
}
fn parse_env_pair ( raw : & str ) -> Result < ( String , String ) , String > {
let mut parts = raw . splitn ( 2 , '=' ) ;
let key = parts
. next ( )
. map ( str ::trim )
. filter ( | s | ! s . is_empty ( ) )
. ok_or_else ( | | " environment entries must be in KEY=VALUE form " . to_string ( ) ) ? ;
let value = parts
. next ( )
. map ( str ::to_string )
. ok_or_else ( | | " environment entries must be in KEY=VALUE form " . to_string ( ) ) ? ;
Ok ( ( key . to_string ( ) , value ) )
}
fn validate_server_name ( name : & str ) -> Result < ( ) > {
let is_valid = ! name . is_empty ( )
& & name
. chars ( )
. all ( | c | c . is_ascii_alphanumeric ( ) | | c = = '-' | | c = = '_' ) ;
if is_valid {
Ok ( ( ) )
} else {
bail! ( " invalid server name '{name}' (use letters, numbers, '-', '_') " ) ;
}
}