mirror of
https://github.com/openai/codex.git
synced 2026-09-06 15:29:32 +00:00
fix(mcp): keep client capabilities request scoped
This commit is contained in:
@@ -1218,7 +1218,9 @@ impl MessageProcessor {
|
||||
self.catalog_processor.skills_config_write(params).await
|
||||
}
|
||||
ClientRequest::PluginInstall { params, .. } => {
|
||||
self.plugin_processor.plugin_install(params).await
|
||||
self.plugin_processor
|
||||
.plugin_install(params, mcp_client_capabilities.clone())
|
||||
.await
|
||||
}
|
||||
ClientRequest::PluginUninstall { params, .. } => {
|
||||
self.plugin_processor.plugin_uninstall(params).await
|
||||
@@ -1316,7 +1318,7 @@ impl MessageProcessor {
|
||||
}
|
||||
ClientRequest::McpServerToolCall { params, .. } => {
|
||||
self.mcp_processor
|
||||
.mcp_server_tool_call(&request_id, params)
|
||||
.mcp_server_tool_call(&request_id, params, mcp_client_capabilities.clone())
|
||||
.await
|
||||
}
|
||||
ClientRequest::WindowsSandboxSetupStart { params, .. } => {
|
||||
|
||||
@@ -3,6 +3,7 @@ use std::sync::atomic::Ordering;
|
||||
|
||||
use axum::http::HeaderValue;
|
||||
use codex_analytics::AppServerRpcTransport;
|
||||
use codex_app_server_protocol::InitializeCapabilities;
|
||||
use codex_login::default_client::SetOriginatorError;
|
||||
use codex_login::default_client::USER_AGENT_SUFFIX;
|
||||
use codex_login::default_client::get_codex_user_agent;
|
||||
@@ -67,22 +68,13 @@ impl InitializeRequestProcessor {
|
||||
// experimental API). Proposed direction is instance-global first-write-wins
|
||||
// with initialize-time mismatch rejection.
|
||||
let analytics_initialize_params = params.clone();
|
||||
let (
|
||||
experimental_api_enabled,
|
||||
let InitializeCapabilities {
|
||||
experimental_api: experimental_api_enabled,
|
||||
request_attestation,
|
||||
opt_out_notification_methods,
|
||||
mcp_client_capabilities,
|
||||
) = match params.capabilities {
|
||||
Some(capabilities) => (
|
||||
capabilities.experimental_api,
|
||||
capabilities.request_attestation,
|
||||
capabilities
|
||||
.opt_out_notification_methods
|
||||
.unwrap_or_default(),
|
||||
capabilities.mcp_client_capabilities,
|
||||
),
|
||||
None => (false, false, Vec::new(), None),
|
||||
};
|
||||
} = params.capabilities.unwrap_or_default();
|
||||
let opt_out_notification_methods = opt_out_notification_methods.unwrap_or_default();
|
||||
let ClientInfo {
|
||||
name,
|
||||
title: _title,
|
||||
|
||||
@@ -70,8 +70,9 @@ impl McpRequestProcessor {
|
||||
&self,
|
||||
request_id: &ConnectionRequestId,
|
||||
params: McpServerToolCallParams,
|
||||
mcp_client_capabilities: Option<McpClientCapabilities>,
|
||||
) -> Result<Option<ClientResponsePayload>, JSONRPCErrorError> {
|
||||
self.call_mcp_server_tool(request_id, params)
|
||||
self.call_mcp_server_tool(request_id, params, mcp_client_capabilities)
|
||||
.await
|
||||
.map(|()| None)
|
||||
}
|
||||
@@ -364,7 +365,9 @@ impl McpRequestProcessor {
|
||||
let request_id = request_id.clone();
|
||||
|
||||
tokio::spawn(async move {
|
||||
let result = thread.read_mcp_resource(&server, &uri).await;
|
||||
let result = thread
|
||||
.read_mcp_resource(&server, &uri, mcp_client_capabilities.as_ref())
|
||||
.await;
|
||||
Self::send_mcp_resource_read_response(outgoing, request_id, result).await;
|
||||
});
|
||||
return Ok(());
|
||||
@@ -420,6 +423,7 @@ impl McpRequestProcessor {
|
||||
&self,
|
||||
request_id: &ConnectionRequestId,
|
||||
params: McpServerToolCallParams,
|
||||
mcp_client_capabilities: Option<McpClientCapabilities>,
|
||||
) -> Result<(), JSONRPCErrorError> {
|
||||
let outgoing = Arc::clone(&self.outgoing);
|
||||
let thread_id = params.thread_id.clone();
|
||||
@@ -429,7 +433,13 @@ impl McpRequestProcessor {
|
||||
|
||||
tokio::spawn(async move {
|
||||
let result = thread
|
||||
.call_mcp_tool(¶ms.server, ¶ms.tool, params.arguments, meta)
|
||||
.call_mcp_tool(
|
||||
¶ms.server,
|
||||
¶ms.tool,
|
||||
params.arguments,
|
||||
meta,
|
||||
mcp_client_capabilities.as_ref(),
|
||||
)
|
||||
.await
|
||||
.map(McpServerToolCallResponse::from)
|
||||
.map_err(|error| internal_error(format!("{error:#}")));
|
||||
|
||||
@@ -428,8 +428,9 @@ impl PluginRequestProcessor {
|
||||
pub(crate) async fn plugin_install(
|
||||
&self,
|
||||
params: PluginInstallParams,
|
||||
mcp_client_capabilities: Option<McpClientCapabilities>,
|
||||
) -> Result<Option<ClientResponsePayload>, JSONRPCErrorError> {
|
||||
self.plugin_install_response(params)
|
||||
self.plugin_install_response(params, mcp_client_capabilities)
|
||||
.await
|
||||
.map(|response| Some(response.into()))
|
||||
}
|
||||
@@ -1364,6 +1365,7 @@ impl PluginRequestProcessor {
|
||||
async fn plugin_install_response(
|
||||
&self,
|
||||
params: PluginInstallParams,
|
||||
mcp_client_capabilities: Option<McpClientCapabilities>,
|
||||
) -> Result<PluginInstallResponse, JSONRPCErrorError> {
|
||||
let PluginInstallParams {
|
||||
marketplace_path,
|
||||
@@ -1374,7 +1376,11 @@ impl PluginRequestProcessor {
|
||||
(Some(marketplace_path), None) => marketplace_path,
|
||||
(None, Some(remote_marketplace_name)) => {
|
||||
return self
|
||||
.remote_plugin_install_response(remote_marketplace_name, plugin_name)
|
||||
.remote_plugin_install_response(
|
||||
remote_marketplace_name,
|
||||
plugin_name,
|
||||
mcp_client_capabilities,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
(Some(_), Some(_)) | (None, None) => {
|
||||
@@ -1432,6 +1438,7 @@ impl PluginRequestProcessor {
|
||||
auth.as_ref().is_some_and(CodexAuth::is_chatgpt_auth),
|
||||
&result.plugin_id.as_key(),
|
||||
&plugin_apps,
|
||||
mcp_client_capabilities,
|
||||
)
|
||||
.await;
|
||||
|
||||
@@ -1445,6 +1452,7 @@ impl PluginRequestProcessor {
|
||||
&self,
|
||||
remote_marketplace_name: String,
|
||||
remote_plugin_id: String,
|
||||
mcp_client_capabilities: Option<McpClientCapabilities>,
|
||||
) -> Result<PluginInstallResponse, JSONRPCErrorError> {
|
||||
let config = self.load_latest_config(/*fallback_cwd*/ None).await?;
|
||||
if !config.features.enabled(Feature::Plugins) {
|
||||
@@ -1547,6 +1555,7 @@ impl PluginRequestProcessor {
|
||||
auth.as_ref().is_some_and(CodexAuth::is_chatgpt_auth),
|
||||
&result.plugin_id.as_key(),
|
||||
&plugin_apps,
|
||||
mcp_client_capabilities,
|
||||
)
|
||||
.await;
|
||||
|
||||
@@ -1562,6 +1571,7 @@ impl PluginRequestProcessor {
|
||||
is_chatgpt_auth: bool,
|
||||
plugin_id: &str,
|
||||
plugin_apps: &[codex_plugin::AppConnectorId],
|
||||
mcp_client_capabilities: Option<McpClientCapabilities>,
|
||||
) -> Vec<AppSummary> {
|
||||
if plugin_apps.is_empty() || !config.features.apps_enabled_for_auth(is_chatgpt_auth) {
|
||||
return Vec::new();
|
||||
@@ -1574,7 +1584,7 @@ impl PluginRequestProcessor {
|
||||
config,
|
||||
/*force_refetch*/ true,
|
||||
Arc::clone(&environment_manager),
|
||||
None,
|
||||
mcp_client_capabilities.clone(),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -1599,9 +1609,12 @@ impl PluginRequestProcessor {
|
||||
"failed to load accessible apps after plugin install: {err:#}"
|
||||
);
|
||||
(
|
||||
connectors::list_cached_accessible_connectors_from_mcp_tools(config, None)
|
||||
.await
|
||||
.unwrap_or_default(),
|
||||
connectors::list_cached_accessible_connectors_from_mcp_tools(
|
||||
config,
|
||||
mcp_client_capabilities.as_ref(),
|
||||
)
|
||||
.await
|
||||
.unwrap_or_default(),
|
||||
false,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -10,9 +10,13 @@ use app_test_support::create_mock_responses_server_sequence;
|
||||
use app_test_support::to_response;
|
||||
use app_test_support::write_mock_responses_config_toml;
|
||||
use axum::Router;
|
||||
use codex_app_server_protocol::ClientInfo;
|
||||
use codex_app_server_protocol::InitializeCapabilities;
|
||||
use codex_app_server_protocol::ItemCompletedNotification;
|
||||
use codex_app_server_protocol::JSONRPCError;
|
||||
use codex_app_server_protocol::JSONRPCResponse;
|
||||
use codex_app_server_protocol::McpAppUiCapability;
|
||||
use codex_app_server_protocol::McpClientCapabilities;
|
||||
use codex_app_server_protocol::McpElicitationSchema;
|
||||
use codex_app_server_protocol::McpServerElicitationAction;
|
||||
use codex_app_server_protocol::McpServerElicitationRequest;
|
||||
@@ -95,7 +99,24 @@ url = "{mcp_server_url}/mcp"
|
||||
std::fs::write(config_path, config_toml)?;
|
||||
|
||||
let mut mcp = TestAppServer::new(codex_home.path()).await?;
|
||||
timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??;
|
||||
timeout(
|
||||
DEFAULT_READ_TIMEOUT,
|
||||
mcp.initialize_with_capabilities(
|
||||
ClientInfo {
|
||||
name: "mcp-tool-capability-test".to_string(),
|
||||
title: None,
|
||||
version: "1.0.0".to_string(),
|
||||
},
|
||||
Some(InitializeCapabilities {
|
||||
experimental_api: true,
|
||||
mcp_client_capabilities: Some(McpClientCapabilities {
|
||||
app_ui: [McpAppUiCapability::WebView].into_iter().collect(),
|
||||
}),
|
||||
..Default::default()
|
||||
}),
|
||||
),
|
||||
)
|
||||
.await??;
|
||||
|
||||
let thread_start_id = mcp
|
||||
.send_thread_start_request(ThreadStartParams {
|
||||
@@ -577,6 +598,7 @@ impl ServerHandler for ToolAppsMcpServer {
|
||||
context: RequestContext<RoleServer>,
|
||||
) -> Result<CallToolResult, rmcp::ErrorData> {
|
||||
assert_eq!(request.name.as_ref(), TEST_TOOL_NAME);
|
||||
assert!(!context.meta.0.contains_key("openai/clientCapabilities"));
|
||||
let message = request
|
||||
.arguments
|
||||
.as_ref()
|
||||
|
||||
@@ -13,7 +13,6 @@ use codex_connectors::merge::merge_connectors;
|
||||
use codex_connectors::merge::merge_plugin_connectors;
|
||||
use codex_core::config::Config;
|
||||
pub use codex_core::connectors::list_accessible_connectors_from_mcp_tools;
|
||||
pub use codex_core::connectors::list_accessible_connectors_from_mcp_tools_with_client_capabilities;
|
||||
pub use codex_core::connectors::list_accessible_connectors_from_mcp_tools_with_environment_manager;
|
||||
pub use codex_core::connectors::list_accessible_connectors_from_mcp_tools_with_options;
|
||||
pub use codex_core::connectors::list_accessible_connectors_from_mcp_tools_with_options_and_status;
|
||||
|
||||
@@ -268,11 +268,15 @@ impl McpConnectionManager {
|
||||
},
|
||||
)
|
||||
.await;
|
||||
let effective_client_capabilities = (host_owned_codex_apps_enabled
|
||||
&& server_name == CODEX_APPS_MCP_SERVER_NAME)
|
||||
.then(|| codex_apps_client_capabilities.clone())
|
||||
.flatten();
|
||||
let codex_apps_tools_cache_context = if server_name == CODEX_APPS_MCP_SERVER_NAME {
|
||||
Some(CodexAppsToolsCacheContext {
|
||||
codex_home: codex_home.clone(),
|
||||
user_key: codex_apps_tools_cache_key.clone(),
|
||||
client_capabilities_fingerprint: codex_apps_client_capabilities
|
||||
client_capabilities_fingerprint: effective_client_capabilities
|
||||
.as_ref()
|
||||
.map(client_capabilities::fingerprint),
|
||||
})
|
||||
@@ -303,9 +307,7 @@ impl McpConnectionManager {
|
||||
tx_event.clone(),
|
||||
elicitation_requests.clone(),
|
||||
codex_apps_tools_cache_context,
|
||||
(host_owned_codex_apps_enabled && server_name == CODEX_APPS_MCP_SERVER_NAME)
|
||||
.then(|| codex_apps_client_capabilities.clone())
|
||||
.flatten(),
|
||||
effective_client_capabilities,
|
||||
Arc::clone(&tool_plugin_provenance),
|
||||
runtime_context.clone(),
|
||||
runtime_auth_provider,
|
||||
@@ -616,7 +618,53 @@ impl McpConnectionManager {
|
||||
.client()
|
||||
.await
|
||||
.context("failed to get client")?;
|
||||
let client_capabilities = managed_client.client_capabilities.clone();
|
||||
self.hard_refresh_codex_apps_tools_cache_for_client(
|
||||
&managed_client,
|
||||
client_capabilities.as_ref(),
|
||||
managed_client.codex_apps_tools_cache_context.clone(),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn hard_refresh_codex_apps_tools_cache_with_client_capabilities(
|
||||
&self,
|
||||
client_capabilities: Option<&McpClientCapabilities>,
|
||||
) -> Result<Vec<ToolInfo>> {
|
||||
let client_capabilities = self
|
||||
.is_host_owned_codex_apps_server(CODEX_APPS_MCP_SERVER_NAME)
|
||||
.then_some(client_capabilities)
|
||||
.flatten();
|
||||
let managed_client = self
|
||||
.clients
|
||||
.get(CODEX_APPS_MCP_SERVER_NAME)
|
||||
.ok_or_else(|| anyhow!("unknown MCP server '{CODEX_APPS_MCP_SERVER_NAME}'"))?
|
||||
.client()
|
||||
.await
|
||||
.context("failed to get client")?;
|
||||
let cache_context =
|
||||
managed_client
|
||||
.codex_apps_tools_cache_context
|
||||
.clone()
|
||||
.map(|mut context| {
|
||||
context.client_capabilities_fingerprint =
|
||||
client_capabilities.map(client_capabilities::fingerprint);
|
||||
context
|
||||
});
|
||||
self.hard_refresh_codex_apps_tools_cache_for_client(
|
||||
&managed_client,
|
||||
client_capabilities,
|
||||
cache_context,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn hard_refresh_codex_apps_tools_cache_for_client(
|
||||
&self,
|
||||
managed_client: &ManagedClient,
|
||||
client_capabilities: Option<&McpClientCapabilities>,
|
||||
cache_context: Option<CodexAppsToolsCacheContext>,
|
||||
) -> Result<Vec<ToolInfo>> {
|
||||
let list_start = Instant::now();
|
||||
let fetch_start = Instant::now();
|
||||
let tools = list_tools_for_client_uncached(
|
||||
@@ -624,7 +672,7 @@ impl McpConnectionManager {
|
||||
&managed_client.client,
|
||||
managed_client.tool_timeout,
|
||||
managed_client.server_instructions.as_deref(),
|
||||
managed_client.client_capabilities.as_ref(),
|
||||
client_capabilities,
|
||||
)
|
||||
.await
|
||||
.with_context(|| {
|
||||
@@ -638,7 +686,7 @@ impl McpConnectionManager {
|
||||
|
||||
write_cached_codex_apps_tools_if_needed(
|
||||
CODEX_APPS_MCP_SERVER_NAME,
|
||||
managed_client.codex_apps_tools_cache_context.as_ref(),
|
||||
cache_context.as_ref(),
|
||||
&managed_client.server_info,
|
||||
&tools,
|
||||
);
|
||||
@@ -818,16 +866,53 @@ impl McpConnectionManager {
|
||||
server: &str,
|
||||
tool: &str,
|
||||
arguments: Option<serde_json::Value>,
|
||||
mut meta: Option<serde_json::Value>,
|
||||
meta: Option<serde_json::Value>,
|
||||
) -> Result<CallToolResult> {
|
||||
let client = self.client_by_name(server).await?;
|
||||
let client_capabilities = client.client_capabilities.clone();
|
||||
Self::call_tool_for_client(
|
||||
&client,
|
||||
server,
|
||||
tool,
|
||||
arguments,
|
||||
meta,
|
||||
client_capabilities.as_ref(),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn call_tool_with_client_capabilities(
|
||||
&self,
|
||||
server: &str,
|
||||
tool: &str,
|
||||
arguments: Option<serde_json::Value>,
|
||||
meta: Option<serde_json::Value>,
|
||||
client_capabilities: Option<&McpClientCapabilities>,
|
||||
) -> Result<CallToolResult> {
|
||||
let client_capabilities = self
|
||||
.is_host_owned_codex_apps_server(server)
|
||||
.then_some(client_capabilities)
|
||||
.flatten();
|
||||
let client = self.client_by_name(server).await?;
|
||||
Self::call_tool_for_client(&client, server, tool, arguments, meta, client_capabilities)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn call_tool_for_client(
|
||||
client: &ManagedClient,
|
||||
server: &str,
|
||||
tool: &str,
|
||||
arguments: Option<serde_json::Value>,
|
||||
mut meta: Option<serde_json::Value>,
|
||||
client_capabilities: Option<&McpClientCapabilities>,
|
||||
) -> Result<CallToolResult> {
|
||||
if !client.tool_filter.allows(tool) {
|
||||
return Err(anyhow!(
|
||||
"tool '{tool}' is disabled for MCP server '{server}'"
|
||||
));
|
||||
}
|
||||
|
||||
client_capabilities::add_json_meta(&mut meta, client.client_capabilities.as_ref());
|
||||
client_capabilities::add_json_meta(&mut meta, client_capabilities);
|
||||
let result: rmcp::model::CallToolResult = client
|
||||
.client
|
||||
.call_tool(tool.to_string(), arguments, meta, client.tool_timeout)
|
||||
@@ -870,9 +955,9 @@ impl McpConnectionManager {
|
||||
let managed = self.client_by_name(server).await?;
|
||||
let timeout = managed.tool_timeout;
|
||||
|
||||
if managed.client_capabilities.is_some() {
|
||||
if let Some(capabilities) = managed.client_capabilities.as_ref() {
|
||||
let params = params.get_or_insert_with(PaginatedRequestParams::default);
|
||||
client_capabilities::add_meta(&mut params.meta, managed.client_capabilities.as_ref());
|
||||
client_capabilities::add_meta(&mut params.meta, Some(capabilities));
|
||||
}
|
||||
managed
|
||||
.client
|
||||
@@ -891,9 +976,9 @@ impl McpConnectionManager {
|
||||
let client = managed.client.clone();
|
||||
let timeout = managed.tool_timeout;
|
||||
|
||||
if managed.client_capabilities.is_some() {
|
||||
if let Some(capabilities) = managed.client_capabilities.as_ref() {
|
||||
let params = params.get_or_insert_with(PaginatedRequestParams::default);
|
||||
client_capabilities::add_meta(&mut params.meta, managed.client_capabilities.as_ref());
|
||||
client_capabilities::add_meta(&mut params.meta, Some(capabilities));
|
||||
}
|
||||
client
|
||||
.list_resource_templates(params, timeout)
|
||||
@@ -905,13 +990,37 @@ impl McpConnectionManager {
|
||||
pub async fn read_resource(
|
||||
&self,
|
||||
server: &str,
|
||||
mut params: ReadResourceRequestParams,
|
||||
params: ReadResourceRequestParams,
|
||||
) -> Result<ReadResourceResult> {
|
||||
let managed = self.client_by_name(server).await?;
|
||||
let client_capabilities = managed.client_capabilities.clone();
|
||||
Self::read_resource_for_client(&managed, server, params, client_capabilities.as_ref()).await
|
||||
}
|
||||
|
||||
pub async fn read_resource_with_client_capabilities(
|
||||
&self,
|
||||
server: &str,
|
||||
params: ReadResourceRequestParams,
|
||||
client_capabilities: Option<&McpClientCapabilities>,
|
||||
) -> Result<ReadResourceResult> {
|
||||
let client_capabilities = self
|
||||
.is_host_owned_codex_apps_server(server)
|
||||
.then_some(client_capabilities)
|
||||
.flatten();
|
||||
let managed = self.client_by_name(server).await?;
|
||||
Self::read_resource_for_client(&managed, server, params, client_capabilities).await
|
||||
}
|
||||
|
||||
async fn read_resource_for_client(
|
||||
managed: &ManagedClient,
|
||||
server: &str,
|
||||
mut params: ReadResourceRequestParams,
|
||||
client_capabilities: Option<&McpClientCapabilities>,
|
||||
) -> Result<ReadResourceResult> {
|
||||
let client = managed.client.clone();
|
||||
let timeout = managed.tool_timeout;
|
||||
let uri = params.uri.clone();
|
||||
client_capabilities::add_meta(&mut params.meta, managed.client_capabilities.as_ref());
|
||||
client_capabilities::add_meta(&mut params.meta, client_capabilities);
|
||||
|
||||
client
|
||||
.read_resource(params, timeout)
|
||||
|
||||
@@ -553,11 +553,16 @@ impl CodexThread {
|
||||
&self,
|
||||
server: &str,
|
||||
uri: &str,
|
||||
client_capabilities: Option<&McpClientCapabilities>,
|
||||
) -> anyhow::Result<serde_json::Value> {
|
||||
let result = self
|
||||
.codex
|
||||
.session
|
||||
.read_resource(server, ReadResourceRequestParams::new(uri))
|
||||
.read_resource_with_client_capabilities(
|
||||
server,
|
||||
ReadResourceRequestParams::new(uri),
|
||||
client_capabilities,
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(serde_json::to_value(result)?)
|
||||
@@ -569,10 +574,11 @@ impl CodexThread {
|
||||
tool: &str,
|
||||
arguments: Option<serde_json::Value>,
|
||||
meta: Option<serde_json::Value>,
|
||||
client_capabilities: Option<&McpClientCapabilities>,
|
||||
) -> anyhow::Result<CallToolResult> {
|
||||
self.codex
|
||||
.session
|
||||
.call_tool(server, tool, arguments, meta)
|
||||
.call_tool_with_client_capabilities(server, tool, arguments, meta, client_capabilities)
|
||||
.await
|
||||
}
|
||||
|
||||
|
||||
@@ -203,22 +203,6 @@ pub async fn list_accessible_connectors_from_mcp_tools_with_options(
|
||||
)
|
||||
}
|
||||
|
||||
pub async fn list_accessible_connectors_from_mcp_tools_with_client_capabilities(
|
||||
config: &Config,
|
||||
force_refetch: bool,
|
||||
mcp_client_capabilities: Option<McpClientCapabilities>,
|
||||
) -> anyhow::Result<Vec<AppInfo>> {
|
||||
Ok(
|
||||
list_accessible_connectors_from_mcp_tools_with_options_and_status(
|
||||
config,
|
||||
force_refetch,
|
||||
mcp_client_capabilities,
|
||||
)
|
||||
.await?
|
||||
.connectors,
|
||||
)
|
||||
}
|
||||
|
||||
pub async fn list_accessible_connectors_from_mcp_tools_with_options_and_status(
|
||||
config: &Config,
|
||||
force_refetch: bool,
|
||||
|
||||
@@ -569,11 +569,12 @@ async fn execute_mcp_tool_call(
|
||||
.start_mcp_call_trace(call_id);
|
||||
let request_meta = mcp_call_trace.add_request_meta(request_meta);
|
||||
let result = sess
|
||||
.call_tool(
|
||||
.call_tool_with_client_capabilities(
|
||||
&invocation.server,
|
||||
&invocation.tool,
|
||||
rewritten_arguments,
|
||||
request_meta,
|
||||
turn_context.mcp_client_capabilities.as_ref(),
|
||||
)
|
||||
.await
|
||||
.map_err(|e| format!("tool call error: {e:?}"))?;
|
||||
@@ -676,7 +677,11 @@ async fn maybe_request_codex_apps_auth_elicitation(
|
||||
async fn refresh_codex_apps_after_connector_auth(sess: &Session, turn_context: &TurnContext) {
|
||||
let mcp_tools_result = {
|
||||
let manager = sess.services.mcp_connection_manager.read().await;
|
||||
manager.hard_refresh_codex_apps_tools_cache().await
|
||||
manager
|
||||
.hard_refresh_codex_apps_tools_cache_with_client_capabilities(
|
||||
turn_context.mcp_client_capabilities.as_ref(),
|
||||
)
|
||||
.await
|
||||
};
|
||||
|
||||
match mcp_tools_result {
|
||||
@@ -1441,13 +1446,14 @@ pub(crate) async fn lookup_mcp_tool_metadata(
|
||||
.await
|
||||
{
|
||||
Some(connectors) => Some(connectors),
|
||||
None => connectors::list_accessible_connectors_from_mcp_tools_with_client_capabilities(
|
||||
None => connectors::list_accessible_connectors_from_mcp_tools_with_options_and_status(
|
||||
turn_context.config.as_ref(),
|
||||
false,
|
||||
turn_context.mcp_client_capabilities.clone(),
|
||||
)
|
||||
.await
|
||||
.ok(),
|
||||
.ok()
|
||||
.map(|status| status.connectors),
|
||||
};
|
||||
connectors.and_then(|connectors| {
|
||||
let connector_id = tool_info.connector_id.as_deref()?;
|
||||
|
||||
@@ -270,16 +270,17 @@ impl Session {
|
||||
clippy::await_holding_invalid_type,
|
||||
reason = "MCP resource calls are serialized through the session-owned manager guard"
|
||||
)]
|
||||
pub async fn read_resource(
|
||||
pub async fn read_resource_with_client_capabilities(
|
||||
&self,
|
||||
server: &str,
|
||||
params: ReadResourceRequestParams,
|
||||
client_capabilities: Option<&McpClientCapabilities>,
|
||||
) -> anyhow::Result<ReadResourceResult> {
|
||||
self.services
|
||||
.mcp_connection_manager
|
||||
.read()
|
||||
.await
|
||||
.read_resource(server, params)
|
||||
.read_resource_with_client_capabilities(server, params, client_capabilities)
|
||||
.await
|
||||
}
|
||||
|
||||
@@ -287,18 +288,19 @@ impl Session {
|
||||
clippy::await_holding_invalid_type,
|
||||
reason = "MCP tool calls are serialized through the session-owned manager guard"
|
||||
)]
|
||||
pub async fn call_tool(
|
||||
pub async fn call_tool_with_client_capabilities(
|
||||
&self,
|
||||
server: &str,
|
||||
tool: &str,
|
||||
arguments: Option<serde_json::Value>,
|
||||
meta: Option<serde_json::Value>,
|
||||
client_capabilities: Option<&McpClientCapabilities>,
|
||||
) -> anyhow::Result<CallToolResult> {
|
||||
self.services
|
||||
.mcp_connection_manager
|
||||
.read()
|
||||
.await
|
||||
.call_tool(server, tool, arguments, meta)
|
||||
.call_tool_with_client_capabilities(server, tool, arguments, meta, client_capabilities)
|
||||
.await
|
||||
}
|
||||
|
||||
|
||||
@@ -78,7 +78,11 @@ impl ToolExecutor<ToolInvocation> for ReadMcpResourceHandler {
|
||||
|
||||
let payload_result: Result<ReadResourcePayload, FunctionCallError> = async {
|
||||
let result = session
|
||||
.read_resource(&server, ReadResourceRequestParams::new(uri.clone()))
|
||||
.read_resource_with_client_capabilities(
|
||||
&server,
|
||||
ReadResourceRequestParams::new(uri.clone()),
|
||||
turn.mcp_client_capabilities.as_ref(),
|
||||
)
|
||||
.await
|
||||
.map_err(|err| {
|
||||
FunctionCallError::RespondToModel(format!("resources/read failed: {err:#}"))
|
||||
|
||||
@@ -8,6 +8,8 @@ use codex_features::Feature;
|
||||
use codex_protocol::config_types::CollaborationMode;
|
||||
use codex_protocol::config_types::ModeKind;
|
||||
use codex_protocol::config_types::Settings;
|
||||
use codex_protocol::mcp::McpAppUiCapability;
|
||||
use codex_protocol::mcp::McpClientCapabilities;
|
||||
use codex_protocol::models::PermissionProfile;
|
||||
use codex_protocol::protocol::AskForApproval;
|
||||
use codex_protocol::protocol::ElicitationAction;
|
||||
@@ -142,6 +144,17 @@ async fn approved_mcp_tool_call_metadata_records_prior_user_input_request() -> R
|
||||
});
|
||||
let test = builder.build(&server).await?;
|
||||
|
||||
test.codex
|
||||
.set_app_server_client_info(
|
||||
Some("turn-client".to_string()),
|
||||
Some("1.0.0".to_string()),
|
||||
false,
|
||||
Some(McpClientCapabilities {
|
||||
app_ui: [McpAppUiCapability::WebView].into_iter().collect(),
|
||||
}),
|
||||
)
|
||||
.await?;
|
||||
|
||||
submit_user_turn(
|
||||
&test,
|
||||
"Use [$calendar](app://calendar) to create a calendar event.",
|
||||
@@ -167,6 +180,15 @@ async fn approved_mcp_tool_call_metadata_records_prior_user_input_request() -> R
|
||||
unreachable!("event guard guarantees ElicitationRequest");
|
||||
};
|
||||
|
||||
test.codex
|
||||
.set_app_server_client_info(
|
||||
Some("resumed-client".to_string()),
|
||||
Some("1.0.0".to_string()),
|
||||
false,
|
||||
Some(McpClientCapabilities::default()),
|
||||
)
|
||||
.await?;
|
||||
|
||||
test.codex
|
||||
.submit(Op::ResolveElicitation {
|
||||
server_name: request.server_name,
|
||||
@@ -190,6 +212,12 @@ async fn approved_mcp_tool_call_metadata_records_prior_user_input_request() -> R
|
||||
.pointer("/params/_meta/x-codex-turn-metadata/user_input_requested_during_turn"),
|
||||
Some(&json!(true))
|
||||
);
|
||||
assert_eq!(
|
||||
apps_tool_call.pointer(
|
||||
"/params/_meta/openai~1clientCapabilities/extensions/io.modelcontextprotocol~1ui/mimeTypes"
|
||||
),
|
||||
Some(&json!(["text/html;profile=mcp-app"]))
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user