This commit is contained in:
Matthew Zeng
2026-01-22 23:24:43 -08:00
parent 8266055b1f
commit ac4dff66e4
6 changed files with 80 additions and 4 deletions
@@ -20,6 +20,7 @@ use codex_app_server_protocol::AppsListResponse;
use codex_app_server_protocol::JSONRPCResponse;
use codex_app_server_protocol::RequestId;
use codex_core::auth::AuthCredentialsStoreMode;
use codex_core::auth::login_with_api_key;
use pretty_assertions::assert_eq;
use rmcp::handler::server::ServerHandler;
use rmcp::model::JsonObject;
@@ -67,6 +68,53 @@ async fn list_apps_returns_empty_when_connectors_disabled() -> Result<()> {
Ok(())
}
#[tokio::test]
async fn list_apps_returns_empty_when_using_api_key() -> Result<()> {
let connectors = vec![AppInfo {
id: "alpha".to_string(),
name: "Alpha".to_string(),
description: Some("Alpha connector".to_string()),
logo_url: None,
install_url: None,
is_accessible: false,
}];
let tools = vec![connector_tool("alpha", "Alpha App")?];
let (server_url, server_handle) = start_apps_server(connectors, tools).await?;
let codex_home = TempDir::new()?;
write_connectors_config(codex_home.path(), &server_url)?;
login_with_api_key(
codex_home.path(),
"sk-test-key",
AuthCredentialsStoreMode::File,
)?;
let mut mcp = McpProcess::new(codex_home.path()).await?;
timeout(DEFAULT_TIMEOUT, mcp.initialize()).await??;
let request_id = mcp
.send_apps_list_request(AppsListParams {
limit: Some(50),
cursor: None,
})
.await?;
let response: JSONRPCResponse = timeout(
DEFAULT_TIMEOUT,
mcp.read_stream_until_response_message(RequestId::Integer(request_id)),
)
.await??;
let AppsListResponse { data, next_cursor } = to_response(response)?;
assert!(data.is_empty());
assert!(next_cursor.is_none());
server_handle.abort();
Ok(())
}
#[tokio::test]
async fn list_apps_returns_connectors_with_accessible_flags() -> Result<()> {
let connectors = vec![
+17 -2
View File
@@ -1,3 +1,5 @@
use codex_core::AuthManager;
use codex_core::CodexAuth;
use codex_core::config::Config;
use codex_core::features::Feature;
use serde::Deserialize;
@@ -36,8 +38,21 @@ struct ListConnectorsResponse {
connectors: Vec<AppInfo>,
}
pub async fn list_connectors(config: &Config) -> anyhow::Result<Vec<AppInfo>> {
async fn connectors_allowed(config: &Config) -> bool {
if !config.features.enabled(Feature::Connectors) {
return false;
}
let auth_manager = AuthManager::new(
config.codex_home.clone(),
false,
config.cli_auth_credentials_store_mode,
);
let auth = auth_manager.auth().await;
!auth.as_ref().is_some_and(CodexAuth::is_api_key)
}
pub async fn list_connectors(config: &Config) -> anyhow::Result<Vec<AppInfo>> {
if !connectors_allowed(config).await {
return Ok(Vec::new());
}
let (connectors_result, accessible_result) = tokio::join!(
@@ -50,7 +65,7 @@ pub async fn list_connectors(config: &Config) -> anyhow::Result<Vec<AppInfo>> {
}
pub async fn list_all_connectors(config: &Config) -> anyhow::Result<Vec<AppInfo>> {
if !config.features.enabled(Feature::Connectors) {
if !connectors_allowed(config).await {
return Ok(Vec::new());
}
init_chatgpt_token_from_auth(&config.codex_home, config.cli_auth_credentials_store_mode)
+4
View File
@@ -125,6 +125,10 @@ impl CodexAuth {
self.get_current_token_data().and_then(|t| t.id_token.email)
}
pub fn is_api_key(&self) -> bool {
self.mode == AuthMode::ApiKey
}
/// Account-facing plan classification derived from the current token.
/// Returns a high-level `AccountPlanType` (e.g., Free/Plus/Pro/Team/…)
/// mapped from the ID token's internal plan value. Prefer this when you
+4
View File
@@ -8,6 +8,7 @@ use codex_protocol::protocol::SandboxPolicy;
use tokio_util::sync::CancellationToken;
use crate::AuthManager;
use crate::CodexAuth;
use crate::SandboxState;
use crate::config::Config;
use crate::features::Feature;
@@ -25,6 +26,9 @@ pub async fn list_accessible_connectors_from_mcp_tools(
let auth_manager = auth_manager_from_config(config);
let auth = auth_manager.auth().await;
if auth.as_ref().is_some_and(CodexAuth::is_api_key) {
return Ok(Vec::new());
}
let mcp_servers = with_codex_apps_mcp(HashMap::new(), true, auth.as_ref(), config);
if mcp_servers.is_empty() {
return Ok(Vec::new());
+5 -1
View File
@@ -100,13 +100,17 @@ fn codex_apps_mcp_server_config(config: &Config, auth: Option<&CodexAuth>) -> Mc
}
}
fn connectors_allowed(connectors_enabled: bool, auth: Option<&CodexAuth>) -> bool {
connectors_enabled && !auth.is_some_and(CodexAuth::is_api_key)
}
pub(crate) fn with_codex_apps_mcp(
mut servers: HashMap<String, McpServerConfig>,
connectors_enabled: bool,
auth: Option<&CodexAuth>,
config: &Config,
) -> HashMap<String, McpServerConfig> {
if connectors_enabled {
if connectors_allowed(connectors_enabled, auth) {
servers.insert(
CODEX_APPS_MCP_SERVER_NAME.to_string(),
codex_apps_mcp_server_config(config, auth),
+2 -1
View File
@@ -2052,7 +2052,7 @@ impl ChatWidget {
widget
.bottom_pane
.set_connectors_enabled(widget.config.features.enabled(Feature::Connectors));
.set_connectors_enabled(widget.connectors_enabled());
widget
}
@@ -4614,6 +4614,7 @@ impl ChatWidget {
fn connectors_enabled(&self) -> bool {
self.config.features.enabled(Feature::Connectors)
&& self.auth_manager.get_auth_mode() != Some(AuthMode::ApiKey)
}
/// Build a placeholder header cell while the session is configuring.