Merge branch 'main' of github.com:openai/codex into dev/mzeng/codex_apps_mcp_declare

# Conflicts:
#	codex-rs/Cargo.lock
#	codex-rs/codex-mcp/Cargo.toml
#	codex-rs/codex-mcp/src/connection_manager.rs
#	codex-rs/codex-mcp/src/connection_manager_tests.rs
#	codex-rs/codex-mcp/src/rmcp_client.rs
#	codex-rs/codex-mcp/src/tools.rs
#	codex-rs/core/src/connectors_tests.rs
#	codex-rs/core/src/mcp_tool_exposure_test.rs
#	codex-rs/core/src/tools/handlers/tool_search.rs
#	codex-rs/core/src/tools/spec_tests.rs
This commit is contained in:
Matthew Zeng
2026-05-12 14:48:05 -07:00
939 changed files with 47255 additions and 16342 deletions
+2
View File
@@ -12,6 +12,7 @@ path = "src/main.rs"
[lib]
name = "codex_cli"
path = "src/lib.rs"
doctest = false
[lints]
workspace = true
@@ -21,6 +22,7 @@ anyhow = { workspace = true }
clap = { workspace = true, features = ["derive"] }
clap_complete = { workspace = true }
codex-app-server = { workspace = true }
codex-app-server-daemon = { workspace = true }
codex-app-server-protocol = { workspace = true }
codex-app-server-test-client = { workspace = true }
codex-arg0 = { workspace = true }
+7
View File
@@ -370,6 +370,11 @@ async fn run_command_under_windows_session(
cwd.as_path(),
env,
None,
/*read_roots_override*/ None,
/*read_roots_include_platform_defaults*/ false,
/*write_roots_override*/ None,
/*deny_read_paths_override*/ &[],
/*deny_write_paths_override*/ &[],
/*tty*/ false,
/*stdin_open*/ true,
config.permissions.windows_sandbox_private_desktop,
@@ -384,6 +389,8 @@ async fn run_command_under_windows_session(
cwd.as_path(),
env,
None,
/*additional_deny_read_paths*/ &[],
/*additional_deny_write_paths*/ &[],
/*tty*/ false,
/*stdin_open*/ true,
config.permissions.windows_sandbox_private_desktop,
+197 -107
View File
@@ -3,6 +3,9 @@ use clap::CommandFactory;
use clap::Parser;
use clap_complete::Shell;
use clap_complete::generate;
use codex_app_server_daemon::BootstrapOptions as AppServerBootstrapOptions;
use codex_app_server_daemon::LifecycleCommand as AppServerLifecycleCommand;
use codex_app_server_daemon::RemoteControlMode as AppServerRemoteControlMode;
use codex_arg0::Arg0DispatchPaths;
use codex_arg0::arg0_dispatch_or_else;
use codex_chatgpt::apply_command::ApplyCommand;
@@ -126,7 +129,7 @@ enum Subcommand {
/// [experimental] Run the app server or related tooling.
AppServer(AppServerCommand),
/// [experimental] Start a headless app-server with remote control enabled.
/// [experimental] Ensure the app-server daemon is running with remote control enabled.
RemoteControl,
/// Launch the Codex desktop app (opens the app installer if missing).
@@ -417,7 +420,7 @@ struct AppServerCommand {
subcommand: Option<AppServerSubcommand>,
/// Transport endpoint URL. Supported values: `stdio://` (default),
/// `unix://`, `unix://PATH`, `ws://IP:PORT`, `off`.
/// `unix://`, `unix://PATH`, `off`.
#[arg(
long = "listen",
value_name = "URL",
@@ -442,9 +445,6 @@ struct AppServerCommand {
/// See https://developers.openai.com/codex/config-advanced/#metrics for more details.
#[arg(long = "analytics-default-enabled")]
analytics_default_enabled: bool,
#[command(flatten)]
auth: codex_app_server::AppServerWebsocketAuthArgs,
}
#[derive(Debug, Parser)]
@@ -469,6 +469,9 @@ struct ExecServerCommand {
#[derive(Debug, clap::Subcommand)]
#[allow(clippy::enum_variant_names)]
enum AppServerSubcommand {
/// Manage the local app-server daemon.
Daemon(AppServerDaemonCommand),
/// Proxy stdio bytes to the running app-server control socket.
Proxy(AppServerProxyCommand),
@@ -483,6 +486,40 @@ enum AppServerSubcommand {
GenerateInternalJsonSchema(GenerateInternalJsonSchemaCommand),
}
#[derive(Debug, Args)]
struct AppServerDaemonCommand {
#[command(subcommand)]
subcommand: AppServerDaemonSubcommand,
}
#[derive(Debug, clap::Subcommand)]
enum AppServerDaemonSubcommand {
/// Install durable local app-server management for SSH-driven use.
Bootstrap(AppServerBootstrapCommand),
/// Start the local app server daemon if it is not already running.
Start,
/// Restart the local app server daemon.
Restart,
/// Enable remote_control for future starts and a currently running managed daemon.
EnableRemoteControl,
/// Disable remote_control for future starts and a currently running managed daemon.
DisableRemoteControl,
/// Stop the local app server daemon.
Stop,
/// Print local CLI and running app-server versions as JSON.
Version,
/// [internal] Run the detached pid-backed standalone updater loop.
#[clap(hide = true)]
PidUpdateLoop,
}
#[derive(Debug, Args)]
struct AppServerProxyCommand {
/// Path to the app-server Unix domain socket to connect to.
@@ -490,6 +527,13 @@ struct AppServerProxyCommand {
socket_path: Option<AbsolutePathBuf>,
}
#[derive(Debug, Args)]
struct AppServerBootstrapCommand {
/// Launch the managed app-server with remote_control enabled.
#[arg(long = "remote-control")]
remote_control: bool,
}
#[derive(Debug, Args)]
struct GenerateTsCommand {
/// Output directory where .ts files will be written
@@ -728,14 +772,6 @@ struct FeatureSetArgs {
feature: String,
}
const REMOTE_CONTROL_FEATURE_OVERRIDE: &str = "features.remote_control=true";
fn enable_remote_control_for_invocation(config_overrides: &mut CliConfigOverrides) {
config_overrides
.raw_overrides
.push(REMOTE_CONTROL_FEATURE_OVERRIDE.to_string());
}
fn stage_str(stage: Stage) -> &'static str {
match stage {
Stage::UnderDevelopment => "under development",
@@ -853,7 +889,6 @@ async fn cli_main(arg0_paths: Arg0DispatchPaths) -> anyhow::Result<()> {
subcommand,
listen,
analytics_default_enabled,
auth,
} = app_server_cli;
reject_remote_mode_for_app_server_subcommand(
root_remote.as_deref(),
@@ -863,7 +898,6 @@ async fn cli_main(arg0_paths: Arg0DispatchPaths) -> anyhow::Result<()> {
match subcommand {
None => {
let transport = listen;
let auth = auth.try_into_settings()?;
codex_app_server::run_main_with_transport(
arg0_paths.clone(),
root_config_overrides,
@@ -871,10 +905,44 @@ async fn cli_main(arg0_paths: Arg0DispatchPaths) -> anyhow::Result<()> {
analytics_default_enabled,
transport,
codex_protocol::protocol::SessionSource::VSCode,
auth,
)
.await?;
}
Some(AppServerSubcommand::Daemon(daemon_cli)) => match daemon_cli.subcommand {
AppServerDaemonSubcommand::Start => {
print_app_server_daemon_output(AppServerLifecycleCommand::Start).await?;
}
AppServerDaemonSubcommand::Bootstrap(bootstrap_cli) => {
let output =
codex_app_server_daemon::bootstrap(AppServerBootstrapOptions {
remote_control_enabled: bootstrap_cli.remote_control,
})
.await?;
println!("{}", serde_json::to_string(&output)?);
}
AppServerDaemonSubcommand::Restart => {
print_app_server_daemon_output(AppServerLifecycleCommand::Restart).await?;
}
AppServerDaemonSubcommand::EnableRemoteControl => {
print_app_server_remote_control_output(AppServerRemoteControlMode::Enabled)
.await?;
}
AppServerDaemonSubcommand::DisableRemoteControl => {
print_app_server_remote_control_output(
AppServerRemoteControlMode::Disabled,
)
.await?;
}
AppServerDaemonSubcommand::Stop => {
print_app_server_daemon_output(AppServerLifecycleCommand::Stop).await?;
}
AppServerDaemonSubcommand::Version => {
print_app_server_daemon_output(AppServerLifecycleCommand::Version).await?;
}
AppServerDaemonSubcommand::PidUpdateLoop => {
codex_app_server_daemon::run_pid_update_loop().await?;
}
},
Some(AppServerSubcommand::Proxy(proxy_cli)) => {
let socket_path = match proxy_cli.socket_path {
Some(socket_path) => socket_path,
@@ -913,17 +981,8 @@ async fn cli_main(arg0_paths: Arg0DispatchPaths) -> anyhow::Result<()> {
root_remote_auth_token_env.as_deref(),
"remote-control",
)?;
enable_remote_control_for_invocation(&mut root_config_overrides);
codex_app_server::run_main_with_transport(
arg0_paths.clone(),
root_config_overrides,
codex_config::LoaderOverrides::default(),
/*default_analytics_enabled*/ false,
codex_app_server::AppServerTransport::Off,
codex_protocol::protocol::SessionSource::Cli,
codex_app_server::AppServerWebsocketAuthSettings::default(),
)
.await?;
let output = codex_app_server_daemon::ensure_remote_control_started().await?;
println!("{}", serde_json::to_string(&output)?);
}
#[cfg(any(target_os = "macos", target_os = "windows"))]
Some(Subcommand::App(app_cli)) => {
@@ -1547,6 +1606,20 @@ fn reject_remote_mode_for_app_server_subcommand(
) -> anyhow::Result<()> {
let subcommand_name = match subcommand {
None => "app-server",
Some(AppServerSubcommand::Daemon(daemon)) => match daemon.subcommand {
AppServerDaemonSubcommand::Bootstrap(_) => "app-server daemon bootstrap",
AppServerDaemonSubcommand::Start => "app-server daemon start",
AppServerDaemonSubcommand::Restart => "app-server daemon restart",
AppServerDaemonSubcommand::EnableRemoteControl => {
"app-server daemon enable-remote-control"
}
AppServerDaemonSubcommand::DisableRemoteControl => {
"app-server daemon disable-remote-control"
}
AppServerDaemonSubcommand::Stop => "app-server daemon stop",
AppServerDaemonSubcommand::Version => "app-server daemon version",
AppServerDaemonSubcommand::PidUpdateLoop => "app-server daemon pid-update-loop",
},
Some(AppServerSubcommand::Proxy(_)) => "app-server proxy",
Some(AppServerSubcommand::GenerateTs(_)) => "app-server generate-ts",
Some(AppServerSubcommand::GenerateJsonSchema(_)) => "app-server generate-json-schema",
@@ -1557,6 +1630,20 @@ fn reject_remote_mode_for_app_server_subcommand(
reject_remote_mode_for_subcommand(remote, remote_auth_token_env, subcommand_name)
}
async fn print_app_server_daemon_output(command: AppServerLifecycleCommand) -> anyhow::Result<()> {
let output = codex_app_server_daemon::run(command).await?;
println!("{}", serde_json::to_string(&output)?);
Ok(())
}
async fn print_app_server_remote_control_output(
mode: AppServerRemoteControlMode,
) -> anyhow::Result<()> {
let output = codex_app_server_daemon::set_remote_control(mode).await?;
println!("{}", serde_json::to_string(&output)?);
Ok(())
}
fn read_remote_auth_token_from_env_var_with<F>(
env_var_name: &str,
get_var: F,
@@ -2305,24 +2392,6 @@ mod tests {
assert!(app_server.analytics_default_enabled);
}
#[test]
fn remote_control_override_is_appended_after_root_toggles() {
let mut config_overrides = CliConfigOverrides::default();
config_overrides
.raw_overrides
.push("features.remote_control=false".to_string());
enable_remote_control_for_invocation(&mut config_overrides);
assert_eq!(
config_overrides.raw_overrides,
vec![
"features.remote_control=false".to_string(),
REMOTE_CONTROL_FEATURE_OVERRIDE.to_string(),
]
);
}
#[test]
fn reject_remote_flag_for_remote_control() {
let cli = MultitoolCli::try_parse_from([
@@ -2452,16 +2521,14 @@ mod tests {
}
#[test]
fn app_server_listen_websocket_url_parses() {
let app_server = app_server_from_args(
["codex", "app-server", "--listen", "ws://127.0.0.1:4500"].as_ref(),
);
assert_eq!(
app_server.listen,
codex_app_server::AppServerTransport::WebSocket {
bind_address: "127.0.0.1:4500".parse().expect("valid socket address"),
}
);
fn app_server_listen_websocket_url_fails_to_parse() {
let parse_result = MultitoolCli::try_parse_from([
"codex",
"app-server",
"--listen",
"ws://127.0.0.1:4500",
]);
assert!(parse_result.is_err());
}
#[test]
@@ -2524,6 +2591,70 @@ mod tests {
));
}
#[test]
fn app_server_daemon_subcommands_parse() {
assert!(matches!(
app_server_from_args(
[
"codex",
"app-server",
"daemon",
"bootstrap",
"--remote-control"
]
.as_ref()
)
.subcommand,
Some(AppServerSubcommand::Daemon(AppServerDaemonCommand {
subcommand: AppServerDaemonSubcommand::Bootstrap(AppServerBootstrapCommand {
remote_control: true
})
}))
));
assert!(matches!(
app_server_from_args(["codex", "app-server", "daemon", "start"].as_ref()).subcommand,
Some(AppServerSubcommand::Daemon(AppServerDaemonCommand {
subcommand: AppServerDaemonSubcommand::Start
}))
));
assert!(matches!(
app_server_from_args(["codex", "app-server", "daemon", "restart"].as_ref()).subcommand,
Some(AppServerSubcommand::Daemon(AppServerDaemonCommand {
subcommand: AppServerDaemonSubcommand::Restart
}))
));
assert!(matches!(
app_server_from_args(
["codex", "app-server", "daemon", "enable-remote-control"].as_ref()
)
.subcommand,
Some(AppServerSubcommand::Daemon(AppServerDaemonCommand {
subcommand: AppServerDaemonSubcommand::EnableRemoteControl
}))
));
assert!(matches!(
app_server_from_args(
["codex", "app-server", "daemon", "disable-remote-control"].as_ref()
)
.subcommand,
Some(AppServerSubcommand::Daemon(AppServerDaemonCommand {
subcommand: AppServerDaemonSubcommand::DisableRemoteControl
}))
));
assert!(matches!(
app_server_from_args(["codex", "app-server", "daemon", "stop"].as_ref()).subcommand,
Some(AppServerSubcommand::Daemon(AppServerDaemonCommand {
subcommand: AppServerDaemonSubcommand::Stop
}))
));
assert!(matches!(
app_server_from_args(["codex", "app-server", "daemon", "version"].as_ref()).subcommand,
Some(AppServerSubcommand::Daemon(AppServerDaemonCommand {
subcommand: AppServerDaemonSubcommand::Version
}))
));
}
#[test]
fn app_server_proxy_sock_path_parses() {
let app_server =
@@ -2553,58 +2684,17 @@ mod tests {
}
#[test]
fn app_server_capability_token_flags_parse() {
let app_server = app_server_from_args(
[
"codex",
"app-server",
"--ws-auth",
"capability-token",
"--ws-token-file",
"/tmp/codex-token",
]
.as_ref(),
);
assert_eq!(
app_server.auth.ws_auth,
Some(codex_app_server::WebsocketAuthCliMode::CapabilityToken)
);
assert_eq!(
app_server.auth.ws_token_file,
Some(PathBuf::from("/tmp/codex-token"))
);
}
#[test]
fn app_server_signed_bearer_flags_parse() {
let app_server = app_server_from_args(
[
"codex",
"app-server",
"--ws-auth",
"signed-bearer-token",
"--ws-shared-secret-file",
"/tmp/codex-secret",
"--ws-issuer",
"issuer",
"--ws-audience",
"audience",
"--ws-max-clock-skew-seconds",
"9",
]
.as_ref(),
);
assert_eq!(
app_server.auth.ws_auth,
Some(codex_app_server::WebsocketAuthCliMode::SignedBearerToken)
);
assert_eq!(
app_server.auth.ws_shared_secret_file,
Some(PathBuf::from("/tmp/codex-secret"))
);
assert_eq!(app_server.auth.ws_issuer.as_deref(), Some("issuer"));
assert_eq!(app_server.auth.ws_audience.as_deref(), Some("audience"));
assert_eq!(app_server.auth.ws_max_clock_skew_seconds, Some(9));
fn reject_remote_auth_token_env_for_app_server_version() {
let subcommand = AppServerSubcommand::Daemon(AppServerDaemonCommand {
subcommand: AppServerDaemonSubcommand::Version,
});
let err = reject_remote_mode_for_app_server_subcommand(
/*remote*/ None,
Some("CODEX_REMOTE_AUTH_TOKEN"),
Some(&subcommand),
)
.expect_err("app-server daemon version should reject --remote-auth-token-env");
assert!(err.to_string().contains("app-server daemon version"));
}
#[test]