Compare commits

...

2 Commits

Author SHA1 Message Date
shijie-openai
96f34427f6 Update to include ts optional 2025-11-24 11:45:02 -08:00
shijie-openai
cbbd48ab9c Chore: read mcp servers config via app server 2025-11-24 10:53:19 -08:00
3 changed files with 126 additions and 0 deletions

View File

@@ -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,

View File

@@ -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/")]

View File

@@ -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;
@@ -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;
@@ -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;
@@ -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;
}
@@ -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());
@@ -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>>,