mirror of
https://github.com/openai/codex.git
synced 2026-05-03 19:06:58 +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:
@@ -23,6 +23,7 @@ use mcp_types::ListToolsResult;
|
||||
use mcp_types::ReadResourceRequestParams;
|
||||
use mcp_types::ReadResourceResult;
|
||||
use mcp_types::RequestId;
|
||||
use mcp_types::Tool;
|
||||
use reqwest::header::HeaderMap;
|
||||
use rmcp::model::CallToolRequestParam;
|
||||
use rmcp::model::ClientNotification;
|
||||
@@ -44,6 +45,7 @@ use rmcp::transport::auth::AuthClient;
|
||||
use rmcp::transport::auth::OAuthState;
|
||||
use rmcp::transport::child_process::TokioChildProcess;
|
||||
use rmcp::transport::streamable_http_client::StreamableHttpClientTransportConfig;
|
||||
use serde_json::Value;
|
||||
use tokio::io::AsyncBufReadExt;
|
||||
use tokio::io::BufReader;
|
||||
use tokio::process::Command;
|
||||
@@ -95,6 +97,17 @@ pub type SendElicitation = Box<
|
||||
dyn Fn(RequestId, Elicitation) -> BoxFuture<'static, Result<ElicitationResponse>> + Send + Sync,
|
||||
>;
|
||||
|
||||
pub struct ToolWithConnectorId {
|
||||
pub tool: Tool,
|
||||
pub connector_id: Option<String>,
|
||||
pub connector_name: Option<String>,
|
||||
}
|
||||
|
||||
pub struct ListToolsWithConnectorIdResult {
|
||||
pub next_cursor: Option<String>,
|
||||
pub tools: Vec<ToolWithConnectorId>,
|
||||
}
|
||||
|
||||
/// MCP client implemented on top of the official `rmcp` SDK.
|
||||
/// https://github.com/modelcontextprotocol/rust-sdk
|
||||
pub struct RmcpClient {
|
||||
@@ -286,6 +299,18 @@ impl RmcpClient {
|
||||
params: Option<ListToolsRequestParams>,
|
||||
timeout: Option<Duration>,
|
||||
) -> Result<ListToolsResult> {
|
||||
let result = self.list_tools_with_connector_ids(params, timeout).await?;
|
||||
Ok(ListToolsResult {
|
||||
next_cursor: result.next_cursor,
|
||||
tools: result.tools.into_iter().map(|tool| tool.tool).collect(),
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn list_tools_with_connector_ids(
|
||||
&self,
|
||||
params: Option<ListToolsRequestParams>,
|
||||
timeout: Option<Duration>,
|
||||
) -> Result<ListToolsWithConnectorIdResult> {
|
||||
self.refresh_oauth_if_needed().await;
|
||||
let service = self.service().await?;
|
||||
let rmcp_params = params
|
||||
@@ -294,9 +319,35 @@ impl RmcpClient {
|
||||
|
||||
let fut = service.list_tools(rmcp_params);
|
||||
let result = run_with_timeout(fut, timeout, "tools/list").await?;
|
||||
let converted = convert_to_mcp(result)?;
|
||||
let tools = result
|
||||
.tools
|
||||
.into_iter()
|
||||
.map(|tool| {
|
||||
let meta = tool.meta.as_ref();
|
||||
let connector_id = Self::meta_string(meta, "connector_id");
|
||||
let connector_name = Self::meta_string(meta, "connector_name")
|
||||
.or_else(|| Self::meta_string(meta, "connector_display_name"));
|
||||
let tool = convert_to_mcp(tool)?;
|
||||
Ok(ToolWithConnectorId {
|
||||
tool,
|
||||
connector_id,
|
||||
connector_name,
|
||||
})
|
||||
})
|
||||
.collect::<Result<Vec<_>>>()?;
|
||||
self.persist_oauth_tokens().await;
|
||||
Ok(converted)
|
||||
Ok(ListToolsWithConnectorIdResult {
|
||||
next_cursor: result.next_cursor,
|
||||
tools,
|
||||
})
|
||||
}
|
||||
|
||||
fn meta_string(meta: Option<&rmcp::model::Meta>, key: &str) -> Option<String> {
|
||||
meta.and_then(|meta| meta.get(key))
|
||||
.and_then(Value::as_str)
|
||||
.map(str::trim)
|
||||
.filter(|value| !value.is_empty())
|
||||
.map(str::to_string)
|
||||
}
|
||||
|
||||
pub async fn list_resources(
|
||||
|
||||
Reference in New Issue
Block a user