mirror of
https://github.com/openai/codex.git
synced 2026-11-10 21:00:47 +00:00
feat(core): allow configurable apps MCP URL
This commit is contained in:
@@ -1222,6 +1222,10 @@
|
||||
"default": null,
|
||||
"description": "Settings for app-specific controls."
|
||||
},
|
||||
"apps_mcp_url": {
|
||||
"description": "Optional override for the Codex Apps MCP endpoint URL.",
|
||||
"type": "string"
|
||||
},
|
||||
"chatgpt_base_url": {
|
||||
"description": "Base URL for requests to ChatGPT (as opposed to the OpenAI API).",
|
||||
"type": "string"
|
||||
|
||||
@@ -324,6 +324,8 @@ pub struct Config {
|
||||
|
||||
/// Base URL for requests to ChatGPT (as opposed to the OpenAI API).
|
||||
pub chatgpt_base_url: String,
|
||||
/// Optional override for the Codex Apps MCP endpoint URL.
|
||||
pub apps_mcp_url: Option<String>,
|
||||
|
||||
/// When set, restricts ChatGPT login to a specific workspace identifier.
|
||||
pub forced_chatgpt_workspace_id: Option<String>,
|
||||
@@ -989,6 +991,8 @@ pub struct ConfigToml {
|
||||
|
||||
/// Base URL for requests to ChatGPT (as opposed to the OpenAI API).
|
||||
pub chatgpt_base_url: Option<String>,
|
||||
/// Optional override for the Codex Apps MCP endpoint URL.
|
||||
pub apps_mcp_url: Option<String>,
|
||||
|
||||
pub projects: Option<HashMap<String, ProjectConfig>>,
|
||||
|
||||
@@ -1790,6 +1794,10 @@ impl Config {
|
||||
.chatgpt_base_url
|
||||
.or(cfg.chatgpt_base_url)
|
||||
.unwrap_or("https://chatgpt.com/backend-api/".to_string()),
|
||||
apps_mcp_url: cfg
|
||||
.apps_mcp_url
|
||||
.map(|url| url.trim().to_string())
|
||||
.filter(|url| !url.is_empty()),
|
||||
forced_chatgpt_workspace_id,
|
||||
forced_login_method,
|
||||
include_apply_patch_tool: include_apply_patch_tool_flag,
|
||||
@@ -4054,6 +4062,7 @@ model_verbosity = "high"
|
||||
model_verbosity: None,
|
||||
personality: Some(Personality::Pragmatic),
|
||||
chatgpt_base_url: "https://chatgpt.com/backend-api/".to_string(),
|
||||
apps_mcp_url: None,
|
||||
base_instructions: None,
|
||||
developer_instructions: None,
|
||||
compact_prompt: None,
|
||||
@@ -4161,6 +4170,7 @@ model_verbosity = "high"
|
||||
model_verbosity: None,
|
||||
personality: Some(Personality::Pragmatic),
|
||||
chatgpt_base_url: "https://chatgpt.com/backend-api/".to_string(),
|
||||
apps_mcp_url: None,
|
||||
base_instructions: None,
|
||||
developer_instructions: None,
|
||||
compact_prompt: None,
|
||||
@@ -4266,6 +4276,7 @@ model_verbosity = "high"
|
||||
model_verbosity: None,
|
||||
personality: Some(Personality::Pragmatic),
|
||||
chatgpt_base_url: "https://chatgpt.com/backend-api/".to_string(),
|
||||
apps_mcp_url: None,
|
||||
base_instructions: None,
|
||||
developer_instructions: None,
|
||||
compact_prompt: None,
|
||||
@@ -4357,6 +4368,7 @@ model_verbosity = "high"
|
||||
model_verbosity: Some(Verbosity::High),
|
||||
personality: Some(Personality::Pragmatic),
|
||||
chatgpt_base_url: "https://chatgpt.com/backend-api/".to_string(),
|
||||
apps_mcp_url: None,
|
||||
base_instructions: None,
|
||||
developer_instructions: None,
|
||||
compact_prompt: None,
|
||||
|
||||
@@ -30,6 +30,8 @@ const MCP_TOOL_NAME_PREFIX: &str = "mcp";
|
||||
const MCP_TOOL_NAME_DELIMITER: &str = "__";
|
||||
pub(crate) const CODEX_APPS_MCP_SERVER_NAME: &str = "codex_apps";
|
||||
const CODEX_CONNECTORS_TOKEN_ENV_VAR: &str = "CODEX_CONNECTORS_TOKEN";
|
||||
const DEFAULT_CODEX_APPS_MCP_URL: &str =
|
||||
"https://connectorsapi.gateway.unified-0.api.openai.com/v1/connectors/mcp/";
|
||||
|
||||
fn codex_apps_mcp_bearer_token_env_var() -> Option<String> {
|
||||
match env::var(CODEX_CONNECTORS_TOKEN_ENV_VAR) {
|
||||
@@ -65,21 +67,13 @@ fn codex_apps_mcp_http_headers(auth: Option<&CodexAuth>) -> Option<HashMap<Strin
|
||||
}
|
||||
}
|
||||
|
||||
fn codex_apps_mcp_url(base_url: &str) -> String {
|
||||
let mut base_url = base_url.trim_end_matches('/').to_string();
|
||||
if (base_url.starts_with("https://chatgpt.com")
|
||||
|| base_url.starts_with("https://chat.openai.com"))
|
||||
&& !base_url.contains("/backend-api")
|
||||
{
|
||||
base_url = format!("{base_url}/backend-api");
|
||||
}
|
||||
if base_url.contains("/backend-api") {
|
||||
format!("{base_url}/wham/apps")
|
||||
} else if base_url.contains("/api/codex") {
|
||||
format!("{base_url}/apps")
|
||||
} else {
|
||||
format!("{base_url}/api/codex/apps")
|
||||
}
|
||||
fn codex_apps_mcp_url(configured_url: Option<&str>) -> String {
|
||||
let base_url = configured_url
|
||||
.map(str::trim)
|
||||
.filter(|url| !url.is_empty())
|
||||
.unwrap_or(DEFAULT_CODEX_APPS_MCP_URL)
|
||||
.trim_end_matches('/');
|
||||
format!("{base_url}/")
|
||||
}
|
||||
|
||||
fn codex_apps_mcp_server_config(config: &Config, auth: Option<&CodexAuth>) -> McpServerConfig {
|
||||
@@ -89,7 +83,7 @@ fn codex_apps_mcp_server_config(config: &Config, auth: Option<&CodexAuth>) -> Mc
|
||||
} else {
|
||||
codex_apps_mcp_http_headers(auth)
|
||||
};
|
||||
let url = codex_apps_mcp_url(&config.chatgpt_base_url);
|
||||
let url = codex_apps_mcp_url(config.apps_mcp_url.as_deref());
|
||||
|
||||
McpServerConfig {
|
||||
transport: McpServerTransportConfig::StreamableHttp {
|
||||
@@ -385,4 +379,24 @@ mod tests {
|
||||
|
||||
assert_eq!(group_tools_by_server(&tools), expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn codex_apps_mcp_url_defaults_to_internal_endpoint() {
|
||||
assert_eq!(
|
||||
codex_apps_mcp_url(None),
|
||||
"https://connectorsapi.gateway.unified-0.api.openai.com/v1/connectors/mcp/"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn codex_apps_mcp_url_normalizes_custom_value() {
|
||||
assert_eq!(
|
||||
codex_apps_mcp_url(Some("https://example.com/custom/path")),
|
||||
"https://example.com/custom/path/"
|
||||
);
|
||||
assert_eq!(
|
||||
codex_apps_mcp_url(Some("https://example.com/custom/path/")),
|
||||
"https://example.com/custom/path/"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,6 +65,7 @@ struct StatusHistoryCell {
|
||||
model_details: Vec<String>,
|
||||
directory: PathBuf,
|
||||
permissions: String,
|
||||
app_mcp_url: Option<String>,
|
||||
agents_summary: String,
|
||||
collaboration_mode: Option<String>,
|
||||
model_provider: Option<String>,
|
||||
@@ -251,6 +252,11 @@ impl StatusHistoryCell {
|
||||
model_details,
|
||||
directory: config.cwd.clone(),
|
||||
permissions,
|
||||
app_mcp_url: if config.active_profile.as_deref() == Some("dev") {
|
||||
config.apps_mcp_url.clone()
|
||||
} else {
|
||||
None
|
||||
},
|
||||
agents_summary,
|
||||
collaboration_mode: collaboration_mode.map(ToString::to_string),
|
||||
model_provider,
|
||||
@@ -437,6 +443,9 @@ impl HistoryCell for StatusHistoryCell {
|
||||
let mut seen: BTreeSet<String> = labels.iter().cloned().collect();
|
||||
let thread_name = self.thread_name.as_deref().filter(|name| !name.is_empty());
|
||||
|
||||
if self.app_mcp_url.is_some() {
|
||||
push_label(&mut labels, &mut seen, "App MCP URL");
|
||||
}
|
||||
if self.model_provider.is_some() {
|
||||
push_label(&mut labels, &mut seen, "Model provider");
|
||||
}
|
||||
@@ -497,6 +506,9 @@ impl HistoryCell for StatusHistoryCell {
|
||||
}
|
||||
lines.push(formatter.line("Directory", vec![Span::from(directory_value)]));
|
||||
lines.push(formatter.line("Permissions", vec![Span::from(self.permissions.clone())]));
|
||||
if let Some(app_mcp_url) = self.app_mcp_url.as_ref() {
|
||||
lines.push(formatter.line("App MCP URL", vec![Span::from(app_mcp_url.clone())]));
|
||||
}
|
||||
lines.push(formatter.line("Agents.md", vec![Span::from(self.agents_summary.clone())]));
|
||||
|
||||
if let Some(account_value) = account_value {
|
||||
|
||||
Reference in New Issue
Block a user