use std::ffi::OsString;
use tokio::process::Command;
use crate::{
builder::{apply_cli_overrides, resolve_cli_overrides},
process::spawn_with_retry,
ApplyDiffArtifacts, CodexClient, CodexError, McpAddRequest, McpAddTransport, McpGetRequest,
McpListOutput, McpListRequest, McpLogoutRequest, McpOauthLoginRequest, McpOverviewRequest,
McpRemoveRequest,
};
impl CodexClient {
pub async fn mcp_overview(
&self,
request: McpOverviewRequest,
) -> Result<ApplyDiffArtifacts, CodexError> {
self.run_simple_command_with_overrides(
vec![OsString::from("mcp"), OsString::from("--help")],
request.overrides,
)
.await
}
pub async fn mcp_list(&self, request: McpListRequest) -> Result<McpListOutput, CodexError> {
let McpListRequest { json, overrides } = request;
let mut args = vec![OsString::from("mcp"), OsString::from("list")];
if json {
args.push(OsString::from("--json"));
}
let artifacts = self
.run_simple_command_with_overrides(args, overrides)
.await?;
let parsed = if json {
Some(serde_json::from_str(&artifacts.stdout).map_err(|source| {
CodexError::JsonParse {
context: "mcp list",
stdout: artifacts.stdout.clone(),
source,
}
})?)
} else {
None
};
Ok(McpListOutput {
status: artifacts.status,
stdout: artifacts.stdout,
stderr: artifacts.stderr,
json: parsed,
})
}
pub async fn mcp_get(&self, request: McpGetRequest) -> Result<McpListOutput, CodexError> {
let name = request.name.trim();
if name.is_empty() {
return Err(CodexError::EmptyMcpServerName);
}
let mut args = vec![OsString::from("mcp"), OsString::from("get")];
if request.json {
args.push(OsString::from("--json"));
}
args.push(OsString::from(name));
let artifacts = self
.run_simple_command_with_overrides(args, request.overrides)
.await?;
let parsed = if request.json {
Some(serde_json::from_str(&artifacts.stdout).map_err(|source| {
CodexError::JsonParse {
context: "mcp get",
stdout: artifacts.stdout.clone(),
source,
}
})?)
} else {
None
};
Ok(McpListOutput {
status: artifacts.status,
stdout: artifacts.stdout,
stderr: artifacts.stderr,
json: parsed,
})
}
pub async fn mcp_add(&self, request: McpAddRequest) -> Result<ApplyDiffArtifacts, CodexError> {
let name = request.name.trim();
if name.is_empty() {
return Err(CodexError::EmptyMcpServerName);
}
let mut args = vec![
OsString::from("mcp"),
OsString::from("add"),
OsString::from(name),
];
match request.transport {
McpAddTransport::StreamableHttp {
url,
bearer_token_env_var,
} => {
let url = url.trim();
if url.is_empty() {
return Err(CodexError::EmptyMcpUrl);
}
args.push(OsString::from("--url"));
args.push(OsString::from(url));
if let Some(env_var) = bearer_token_env_var {
if !env_var.trim().is_empty() {
args.push(OsString::from("--bearer-token-env-var"));
args.push(OsString::from(env_var));
}
}
}
McpAddTransport::Stdio { env, command } => {
if command.is_empty() {
return Err(CodexError::EmptyMcpCommand);
}
for (key, value) in env {
let key = key.trim();
if key.is_empty() {
continue;
}
args.push(OsString::from("--env"));
args.push(OsString::from(format!("{key}={value}")));
}
args.push(OsString::from("--"));
args.extend(command);
}
}
self.run_simple_command_with_overrides(args, request.overrides)
.await
}
pub async fn mcp_remove(
&self,
request: McpRemoveRequest,
) -> Result<ApplyDiffArtifacts, CodexError> {
let name = request.name.trim();
if name.is_empty() {
return Err(CodexError::EmptyMcpServerName);
}
self.run_simple_command_with_overrides(
vec![
OsString::from("mcp"),
OsString::from("remove"),
OsString::from(name),
],
request.overrides,
)
.await
}
pub async fn mcp_logout(
&self,
request: McpLogoutRequest,
) -> Result<ApplyDiffArtifacts, CodexError> {
let name = request.name.trim();
if name.is_empty() {
return Err(CodexError::EmptyMcpServerName);
}
self.run_simple_command_with_overrides(
vec![
OsString::from("mcp"),
OsString::from("logout"),
OsString::from(name),
],
request.overrides,
)
.await
}
pub fn spawn_mcp_oauth_login_process(
&self,
request: McpOauthLoginRequest,
) -> Result<tokio::process::Child, CodexError> {
let name = request.name.trim();
if name.is_empty() {
return Err(CodexError::EmptyMcpServerName);
}
let resolved_overrides = resolve_cli_overrides(
&self.cli_overrides,
&request.overrides,
self.model.as_deref(),
);
let mut command = Command::new(self.command_env.binary_path());
command
.arg("mcp")
.arg("login")
.arg(name)
.stdout(std::process::Stdio::piped())
.stderr(std::process::Stdio::piped())
.kill_on_drop(true);
if !request.scopes.is_empty() {
command.arg("--scopes").arg(request.scopes.join(","));
}
apply_cli_overrides(&mut command, &resolved_overrides, true);
self.command_env.apply(&mut command)?;
spawn_with_retry(&mut command, self.command_env.binary_path())
}
}