mirror of
https://github.com/openai/codex.git
synced 2026-04-24 22:54:54 +00:00
[connectors] Support connectors part 1 - App server & MCP (#9667)
In order to make Codex work with connectors, we add a built-in gateway MCP that acts as a transparent proxy between the client and the connectors. The gateway MCP collects actions that are accessible to the user and sends them down to the user, when a connector action is chosen to be called, the client invokes the action through the gateway MCP as well. - [x] Add the system built-in gateway MCP to list and run connectors. - [x] Add the app server methods and protocol
This commit is contained in:
@@ -13,6 +13,9 @@ use codex_app_server_protocol::AccountLoginCompletedNotification;
|
||||
use codex_app_server_protocol::AccountUpdatedNotification;
|
||||
use codex_app_server_protocol::AddConversationListenerParams;
|
||||
use codex_app_server_protocol::AddConversationSubscriptionResponse;
|
||||
use codex_app_server_protocol::AppInfo as ApiAppInfo;
|
||||
use codex_app_server_protocol::AppsListParams;
|
||||
use codex_app_server_protocol::AppsListResponse;
|
||||
use codex_app_server_protocol::ArchiveConversationParams;
|
||||
use codex_app_server_protocol::ArchiveConversationResponse;
|
||||
use codex_app_server_protocol::AskForApproval;
|
||||
@@ -122,6 +125,7 @@ use codex_app_server_protocol::UserInput as V2UserInput;
|
||||
use codex_app_server_protocol::UserSavedConfig;
|
||||
use codex_app_server_protocol::build_turns_from_event_msgs;
|
||||
use codex_backend_client::Client as BackendClient;
|
||||
use codex_chatgpt::connectors;
|
||||
use codex_core::AuthManager;
|
||||
use codex_core::CodexThread;
|
||||
use codex_core::Cursor as RolloutCursor;
|
||||
@@ -411,6 +415,9 @@ impl CodexMessageProcessor {
|
||||
ClientRequest::SkillsList { request_id, params } => {
|
||||
self.skills_list(request_id, params).await;
|
||||
}
|
||||
ClientRequest::AppsList { request_id, params } => {
|
||||
self.apps_list(request_id, params).await;
|
||||
}
|
||||
ClientRequest::SkillsConfigWrite { request_id, params } => {
|
||||
self.skills_config_write(request_id, params).await;
|
||||
}
|
||||
@@ -3406,6 +3413,102 @@ impl CodexMessageProcessor {
|
||||
.await;
|
||||
}
|
||||
|
||||
async fn apps_list(&self, request_id: RequestId, params: AppsListParams) {
|
||||
let AppsListParams { cursor, limit } = params;
|
||||
let config = match self.load_latest_config().await {
|
||||
Ok(config) => config,
|
||||
Err(error) => {
|
||||
self.outgoing.send_error(request_id, error).await;
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
if !config.features.enabled(Feature::Connectors) {
|
||||
self.outgoing
|
||||
.send_response(
|
||||
request_id,
|
||||
AppsListResponse {
|
||||
data: Vec::new(),
|
||||
next_cursor: None,
|
||||
},
|
||||
)
|
||||
.await;
|
||||
return;
|
||||
}
|
||||
|
||||
let connectors = match connectors::list_connectors(&config).await {
|
||||
Ok(connectors) => connectors,
|
||||
Err(err) => {
|
||||
self.send_internal_error(request_id, format!("failed to list apps: {err}"))
|
||||
.await;
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let total = connectors.len();
|
||||
if total == 0 {
|
||||
self.outgoing
|
||||
.send_response(
|
||||
request_id,
|
||||
AppsListResponse {
|
||||
data: Vec::new(),
|
||||
next_cursor: None,
|
||||
},
|
||||
)
|
||||
.await;
|
||||
return;
|
||||
}
|
||||
|
||||
let effective_limit = limit.unwrap_or(total as u32).max(1) as usize;
|
||||
let effective_limit = effective_limit.min(total);
|
||||
let start = match cursor {
|
||||
Some(cursor) => match cursor.parse::<usize>() {
|
||||
Ok(idx) => idx,
|
||||
Err(_) => {
|
||||
self.send_invalid_request_error(
|
||||
request_id,
|
||||
format!("invalid cursor: {cursor}"),
|
||||
)
|
||||
.await;
|
||||
return;
|
||||
}
|
||||
},
|
||||
None => 0,
|
||||
};
|
||||
|
||||
if start > total {
|
||||
self.send_invalid_request_error(
|
||||
request_id,
|
||||
format!("cursor {start} exceeds total apps {total}"),
|
||||
)
|
||||
.await;
|
||||
return;
|
||||
}
|
||||
|
||||
let end = start.saturating_add(effective_limit).min(total);
|
||||
let data = connectors[start..end]
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(|connector| ApiAppInfo {
|
||||
id: connector.connector_id,
|
||||
name: connector.connector_name,
|
||||
description: connector.connector_description,
|
||||
logo_url: connector.logo_url,
|
||||
install_url: connector.install_url,
|
||||
is_accessible: connector.is_accessible,
|
||||
})
|
||||
.collect();
|
||||
|
||||
let next_cursor = if end < total {
|
||||
Some(end.to_string())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
self.outgoing
|
||||
.send_response(request_id, AppsListResponse { data, next_cursor })
|
||||
.await;
|
||||
}
|
||||
|
||||
async fn skills_list(&self, request_id: RequestId, params: SkillsListParams) {
|
||||
let SkillsListParams { cwds, force_reload } = params;
|
||||
let cwds = if cwds.is_empty() {
|
||||
|
||||
Reference in New Issue
Block a user