Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions codex-rs/app-server-protocol/src/protocol/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,10 @@ client_request_definitions! {
params: #[ts(type = "undefined")] #[serde(skip_serializing_if = "Option::is_none")] Option<()>,
response: v1::GetUserSavedConfigResponse,
},
GetMcpServersConfig {
params: #[ts(type = "undefined")] #[serde(skip_serializing_if = "Option::is_none")] Option<()>,
response: v2::GetMcpServersConfigResponse,
},
SetDefaultModel {
params: v1::SetDefaultModelParams,
response: v1::SetDefaultModelResponse,
Expand Down
58 changes: 58 additions & 0 deletions codex-rs/app-server-protocol/src/protocol/v2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,64 @@ impl From<codex_protocol::protocol::SandboxPolicy> for SandboxPolicy {
}
}

#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct GetMcpServersConfigResponse {
pub servers: HashMap<String, McpServerConfig>,
}

#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase", tag = "type")]
#[ts(export_to = "v2/")]
pub enum McpServerTransportConfig {
Stdio {
command: String,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
args: Vec<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
env: Option<HashMap<String, String>>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
env_vars: Vec<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
cwd: Option<PathBuf>,
},
StreamableHttp {
url: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
bearer_token_env_var: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
http_headers: Option<HashMap<String, String>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
env_http_headers: Option<HashMap<String, String>>,
},
}

#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct McpServerConfig {
pub transport: McpServerTransportConfig,
pub enabled: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub startup_timeout_sec: Option<i64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub tool_timeout_sec: Option<i64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub enabled_tools: Option<Vec<String>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub disabled_tools: Option<Vec<String>>,
}

#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
Expand Down
64 changes: 64 additions & 0 deletions codex-rs/app-server/src/codex_message_processor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ use codex_app_server_protocol::GetAuthStatusParams;
use codex_app_server_protocol::GetAuthStatusResponse;
use codex_app_server_protocol::GetConversationSummaryParams;
use codex_app_server_protocol::GetConversationSummaryResponse;
use codex_app_server_protocol::GetMcpServersConfigResponse;
use codex_app_server_protocol::GetUserAgentResponse;
use codex_app_server_protocol::GetUserSavedConfigResponse;
use codex_app_server_protocol::GitDiffToRemoteResponse;
Expand All @@ -52,6 +53,8 @@ use codex_app_server_protocol::LoginChatGptCompleteNotification;
use codex_app_server_protocol::LoginChatGptResponse;
use codex_app_server_protocol::LogoutAccountResponse;
use codex_app_server_protocol::LogoutChatGptResponse;
use codex_app_server_protocol::McpServerConfig as WireMcpServerConfig;
use codex_app_server_protocol::McpServerTransportConfig as WireMcpServerTransportConfig;
use codex_app_server_protocol::ModelListParams;
use codex_app_server_protocol::ModelListResponse;
use codex_app_server_protocol::NewConversationParams;
Expand Down Expand Up @@ -110,6 +113,8 @@ use codex_core::config::Config;
use codex_core::config::ConfigOverrides;
use codex_core::config::ConfigToml;
use codex_core::config::edit::ConfigEditsBuilder;
use codex_core::config::types::McpServerConfig as CoreMcpServerConfig;
use codex_core::config::types::McpServerTransportConfig as CoreMcpServerTransportConfig;
use codex_core::config_loader::load_config_as_toml;
use codex_core::default_client::get_codex_user_agent;
use codex_core::exec::ExecParams;
Expand Down Expand Up @@ -451,6 +456,12 @@ impl CodexMessageProcessor {
} => {
self.get_user_saved_config(request_id).await;
}
ClientRequest::GetMcpServersConfig {
request_id,
params: _,
} => {
self.get_mcp_servers_config(request_id).await;
}
ClientRequest::SetDefaultModel { request_id, params } => {
self.set_default_model(request_id, params).await;
}
Expand Down Expand Up @@ -1116,6 +1127,17 @@ impl CodexMessageProcessor {
self.outgoing.send_response(request_id, response).await;
}

async fn get_mcp_servers_config(&self, request_id: RequestId) {
let servers = self
.config
.mcp_servers
.iter()
.map(|(name, server)| (name.clone(), to_wire_mcp_server_config(server)))
.collect();
let response = GetMcpServersConfigResponse { servers };
self.outgoing.send_response(request_id, response).await;
}

async fn get_user_info(&self, request_id: RequestId) {
// Read alleged user email from cached auth (best-effort; not verified).
let alleged_user_email = self.auth_manager.auth().and_then(|a| a.get_account_email());
Expand Down Expand Up @@ -2847,6 +2869,48 @@ impl CodexMessageProcessor {
}
}

fn to_wire_mcp_server_config(config: &CoreMcpServerConfig) -> WireMcpServerConfig {
let transport = match &config.transport {
CoreMcpServerTransportConfig::Stdio {
command,
args,
env,
env_vars,
cwd,
} => WireMcpServerTransportConfig::Stdio {
command: command.clone(),
args: args.clone(),
env: env.clone(),
env_vars: env_vars.clone(),
cwd: cwd.clone(),
},
CoreMcpServerTransportConfig::StreamableHttp {
url,
bearer_token_env_var,
http_headers,
env_http_headers,
} => WireMcpServerTransportConfig::StreamableHttp {
url: url.clone(),
bearer_token_env_var: bearer_token_env_var.clone(),
http_headers: http_headers.clone(),
env_http_headers: env_http_headers.clone(),
},
};

WireMcpServerConfig {
transport,
enabled: config.enabled,
startup_timeout_sec: config.startup_timeout_sec.and_then(duration_to_seconds),
tool_timeout_sec: config.tool_timeout_sec.and_then(duration_to_seconds),
enabled_tools: config.enabled_tools.clone(),
disabled_tools: config.disabled_tools.clone(),
}
}

fn duration_to_seconds(duration: Duration) -> Option<i64> {
i64::try_from(duration.as_secs()).ok()
}

async fn derive_config_from_params(
overrides: ConfigOverrides,
cli_overrides: Option<std::collections::HashMap<String, serde_json::Value>>,
Expand Down
Loading