diff --git a/codex-rs/app-server-protocol/schema/json/ClientRequest.json b/codex-rs/app-server-protocol/schema/json/ClientRequest.json index 830672e5ae..c5fe07c1d1 100644 --- a/codex-rs/app-server-protocol/schema/json/ClientRequest.json +++ b/codex-rs/app-server-protocol/schema/json/ClientRequest.json @@ -1912,6 +1912,12 @@ }, "McpResourceReadParams": { "properties": { + "connectorId": { + "type": [ + "string", + "null" + ] + }, "originCallId": { "description": "Originating MCP tool call used to select the resource's app.", "type": [ diff --git a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json index cc99f737b8..751cdc7130 100644 --- a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json +++ b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json @@ -13295,6 +13295,12 @@ "McpResourceReadParams": { "$schema": "http://json-schema.org/draft-07/schema#", "properties": { + "connectorId": { + "type": [ + "string", + "null" + ] + }, "originCallId": { "description": "Originating MCP tool call used to select the resource's app.", "type": [ diff --git a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json index d2f9019e8b..3a7962b086 100644 --- a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json +++ b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json @@ -9525,6 +9525,12 @@ "McpResourceReadParams": { "$schema": "http://json-schema.org/draft-07/schema#", "properties": { + "connectorId": { + "type": [ + "string", + "null" + ] + }, "originCallId": { "description": "Originating MCP tool call used to select the resource's app.", "type": [ diff --git a/codex-rs/app-server-protocol/schema/json/v2/McpResourceReadParams.json b/codex-rs/app-server-protocol/schema/json/v2/McpResourceReadParams.json index 26435c73e6..ed20238ab4 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/McpResourceReadParams.json +++ b/codex-rs/app-server-protocol/schema/json/v2/McpResourceReadParams.json @@ -1,6 +1,12 @@ { "$schema": "http://json-schema.org/draft-07/schema#", "properties": { + "connectorId": { + "type": [ + "string", + "null" + ] + }, "originCallId": { "description": "Originating MCP tool call used to select the resource's app.", "type": [ diff --git a/codex-rs/app-server-protocol/schema/precomputed/app-server-exports-experimental.json.zst b/codex-rs/app-server-protocol/schema/precomputed/app-server-exports-experimental.json.zst index 7175b91988..2d03974b51 100644 Binary files a/codex-rs/app-server-protocol/schema/precomputed/app-server-exports-experimental.json.zst and b/codex-rs/app-server-protocol/schema/precomputed/app-server-exports-experimental.json.zst differ diff --git a/codex-rs/app-server-protocol/schema/precomputed/app-server-exports-stable.json.zst b/codex-rs/app-server-protocol/schema/precomputed/app-server-exports-stable.json.zst index e5e6fc8683..6e58e8df4b 100644 Binary files a/codex-rs/app-server-protocol/schema/precomputed/app-server-exports-stable.json.zst and b/codex-rs/app-server-protocol/schema/precomputed/app-server-exports-stable.json.zst differ diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/McpResourceReadParams.ts b/codex-rs/app-server-protocol/schema/typescript/v2/McpResourceReadParams.ts index c09a3ab2a4..7e150661c8 100644 --- a/codex-rs/app-server-protocol/schema/typescript/v2/McpResourceReadParams.ts +++ b/codex-rs/app-server-protocol/schema/typescript/v2/McpResourceReadParams.ts @@ -6,4 +6,4 @@ export type McpResourceReadParams = { threadId?: string | null, /** * Originating MCP tool call used to select the resource's app. */ -originCallId?: string | null, server: string, uri: string, }; +originCallId?: string | null, server: string, uri: string, connectorId?: string | null, }; diff --git a/codex-rs/app-server-protocol/src/protocol/common.rs b/codex-rs/app-server-protocol/src/protocol/common.rs index a73455111f..04e1e378a8 100644 --- a/codex-rs/app-server-protocol/src/protocol/common.rs +++ b/codex-rs/app-server-protocol/src/protocol/common.rs @@ -2331,6 +2331,7 @@ mod tests { origin_call_id: None, server: "server-a".to_string(), uri: "file:///tmp/resource".to_string(), + connector_id: None, }, }; assert_eq!( @@ -2519,6 +2520,7 @@ mod tests { origin_call_id: None, server: "server-a".to_string(), uri: "file:///tmp/resource".to_string(), + connector_id: None, }, }; assert_eq!(mcp_resource_read.serialization_scope(), None); diff --git a/codex-rs/app-server-protocol/src/protocol/v2/mcp.rs b/codex-rs/app-server-protocol/src/protocol/v2/mcp.rs index 2196a3b96a..283c1be360 100644 --- a/codex-rs/app-server-protocol/src/protocol/v2/mcp.rs +++ b/codex-rs/app-server-protocol/src/protocol/v2/mcp.rs @@ -90,6 +90,8 @@ pub struct McpResourceReadParams { pub origin_call_id: Option, pub server: String, pub uri: String, + #[ts(optional = nullable)] + pub connector_id: Option, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] diff --git a/codex-rs/app-server/README.md b/codex-rs/app-server/README.md index 40479fcd23..e0c388ca87 100644 --- a/codex-rs/app-server/README.md +++ b/codex-rs/app-server/README.md @@ -278,7 +278,7 @@ Example with notification opt-out: - `tool/requestUserInput` — prompt the user with 1–3 short questions for a tool call and return their answers (experimental). - `config/mcpServer/reload` — reload MCP server config from disk and queue a refresh for loaded threads (applied on each thread's next active turn); returns `{}`. Use this after editing `config.toml` without restarting the server. - `mcpServerStatus/list` — enumerate configured MCP servers with their tools, auth status, server info, owning `pluginId` (`null` for servers not contributed by a plugin), plus resources/resource templates for `full` detail; supports optional `threadId` and cursor+limit pagination. If `threadId` is omitted, the server reads from the latest global config directly. If `detail` is omitted, the server defaults to `full`. An `unknown` auth status means OAuth support could not be determined; `unsupported` means OAuth is known not to be supported. -- `mcpServer/resource/read` — read a resource from a configured MCP server by optional `threadId`, `server`, and `uri`, returning text/blob resource `contents`. Pass `originCallId` with `threadId` to scope a Codex app widget to the app and account of the completed tool call that produced it; successful scoped reads return the same `originCallId`. If `threadId` is omitted, the server reads from the latest MCP config directly. +- `mcpServer/resource/read` — read a resource from a configured MCP server by optional `threadId`, `server`, and `uri`, returning text/blob resource `contents`. Pass `originCallId` with `threadId` to scope a Codex app widget to the app and account of the completed tool call that produced it; successful scoped reads return the same `originCallId`. Optional `connectorId` restricts other hosted app resources to their originating connector. If `threadId` is omitted, the server reads from the latest MCP config directly. - `mcpServer/tool/call` — call a tool on a thread's configured MCP server by `threadId`, `server`, `tool`, optional `arguments`, and optional `_meta`, returning the MCP tool result. - `windowsSandbox/setupStart` — start Windows sandbox setup for the selected mode (`elevated` or `unelevated`); accepts an optional absolute `cwd` to target setup for a specific workspace, returns `{ started: true }` immediately, and later emits `windowsSandbox/setupCompleted`. - `feedback/upload` — submit a feedback report (classification + optional reason/logs, conversation_id, and optional `extraLogFiles` attachments array); returns the tracking thread id. diff --git a/codex-rs/app-server/src/request_processors/mcp_processor.rs b/codex-rs/app-server/src/request_processors/mcp_processor.rs index eea21cfd1d..e5b2217c2e 100644 --- a/codex-rs/app-server/src/request_processors/mcp_processor.rs +++ b/codex-rs/app-server/src/request_processors/mcp_processor.rs @@ -1,6 +1,7 @@ use super::*; use codex_core::McpManager; use codex_mcp::McpServerSource; +use codex_mcp::ReadResourceRequestParams; const MCP_TOOL_THREAD_ID_META_KEY: &str = "threadId"; @@ -425,7 +426,22 @@ impl McpRequestProcessor { origin_call_id, server, uri, + connector_id, } = params; + let mut resource_params = ReadResourceRequestParams::new(uri); + if let Some(connector_id) = connector_id { + resource_params.meta = Some( + serde_json::Map::from_iter([( + "x-codex-turn-metadata".to_string(), + serde_json::json!({ + "mcp_request_meta": { + "selected_connector_ids": [connector_id], + }, + }), + )]) + .into(), + ); + } if let Some(thread_id) = thread_id { let (_, thread) = self.load_thread(&thread_id).await?; @@ -435,8 +451,12 @@ impl McpRequestProcessor { let origin_call_id = origin_call_id.filter(|_| server == codex_mcp::CODEX_APPS_MCP_SERVER_NAME); let result = match origin_call_id.as_deref() { - Some(call_id) => thread.read_mcp_resource_for_call(call_id, &uri).await, - None => thread.read_mcp_resource(&server, &uri).await, + Some(call_id) => { + thread + .read_mcp_resource_for_call(call_id, &resource_params.uri) + .await + } + None => thread.read_mcp_resource(&server, resource_params).await, }; Self::send_mcp_resource_read_response(outgoing, request_id, result, origin_call_id) .await; @@ -470,7 +490,7 @@ impl McpRequestProcessor { codex_apps_tools_cache, tool_catalog_cache, &server, - &uri, + resource_params, ) .await .and_then(|result| serde_json::to_value(result).map_err(anyhow::Error::from)); diff --git a/codex-rs/app-server/tests/suite/v2/mcp_resource.rs b/codex-rs/app-server/tests/suite/v2/mcp_resource.rs index 2860ee994d..fcbe5ed9ef 100644 --- a/codex-rs/app-server/tests/suite/v2/mcp_resource.rs +++ b/codex-rs/app-server/tests/suite/v2/mcp_resource.rs @@ -124,6 +124,7 @@ async fn mcp_resource_read_returns_resource_contents() -> Result<()> { origin_call_id: None, server: "codex_apps".to_string(), uri: TEST_RESOURCE_URI.to_string(), + connector_id: None, }, }) .await?; @@ -597,6 +598,7 @@ apps = true origin_call_id: None, server: "codex_apps".to_string(), uri: TEST_RESOURCE_URI.to_string(), + connector_id: None, }, }) .await?; @@ -609,6 +611,7 @@ apps = true origin_call_id: None, server: "codex_apps".to_string(), uri: TEST_ELICITATION_RESOURCE_URI.to_string(), + connector_id: None, }, }) .await?; @@ -677,6 +680,7 @@ async fn mcp_resource_read_returns_error_for_unknown_thread() -> Result<()> { origin_call_id: None, server: "codex_apps".to_string(), uri: TEST_RESOURCE_URI.to_string(), + connector_id: None, }, }) .await; diff --git a/codex-rs/app-server/tests/suite/v2/mcp_resource_origin.rs b/codex-rs/app-server/tests/suite/v2/mcp_resource_origin.rs index edbf872fbe..49250930fa 100644 --- a/codex-rs/app-server/tests/suite/v2/mcp_resource_origin.rs +++ b/codex-rs/app-server/tests/suite/v2/mcp_resource_origin.rs @@ -167,6 +167,7 @@ async fn widget_reads_survive_history_modes_compaction_restarts_and_app_only_vis origin_call_id: Some(call_id.to_string()), server: "codex_apps".to_string(), uri: uri.to_string(), + connector_id: None, }) .await?; let error = timeout( @@ -289,6 +290,7 @@ async fn read_widget( origin_call_id: Some(call_id.to_string()), server: "codex_apps".to_string(), uri: TEST_WIDGET_RESOURCE_URI.to_string(), + connector_id: None, }, }) .await diff --git a/codex-rs/codex-mcp/src/lib.rs b/codex-rs/codex-mcp/src/lib.rs index f47b926d14..4bbd723a40 100644 --- a/codex-rs/codex-mcp/src/lib.rs +++ b/codex-rs/codex-mcp/src/lib.rs @@ -15,6 +15,7 @@ pub use resource_client::McpResourceClient; pub use resource_client::McpResourceClientCacheKey; pub use resource_client::McpResourcePage; pub use resource_client::McpResourceReadResult; +pub use rmcp::model::ReadResourceRequestParams; pub use rmcp_client::MCP_SANDBOX_STATE_META_CAPABILITY; pub use runtime::McpRuntime; pub use runtime::McpRuntimeContext; diff --git a/codex-rs/codex-mcp/src/mcp/mod.rs b/codex-rs/codex-mcp/src/mcp/mod.rs index e4cf9946b2..05dae0c570 100644 --- a/codex-rs/codex-mcp/src/mcp/mod.rs +++ b/codex-rs/codex-mcp/src/mcp/mod.rs @@ -349,7 +349,7 @@ pub async fn read_mcp_resource( codex_apps_tools_cache: ConnectorRuntimeManager, tool_catalog_cache: crate::McpToolCatalogCache, server: &str, - uri: &str, + params: ReadResourceRequestParams, ) -> anyhow::Result { let mut mcp_servers = effective_mcp_servers(config, auth); mcp_servers.retain(|name, _| name == server); @@ -382,9 +382,7 @@ pub async fn read_mcp_resource( ) .await; - let result = manager - .read_resource(server, ReadResourceRequestParams::new(uri)) - .await; + let result = manager.read_resource(server, params).await; cancel_token.cancel(); result } diff --git a/codex-rs/core/src/codex_thread.rs b/codex-rs/core/src/codex_thread.rs index f03e00767e..5a579ece06 100644 --- a/codex-rs/core/src/codex_thread.rs +++ b/codex-rs/core/src/codex_thread.rs @@ -734,14 +734,14 @@ impl CodexThread { pub async fn read_mcp_resource( &self, server: &str, - uri: &str, + params: ReadResourceRequestParams, ) -> anyhow::Result { self.session.refresh_mcp_if_dirty().await; let result = self .session .services .mcp_runtime - .latest_read_resource(server, ReadResourceRequestParams::new(uri)) + .latest_read_resource(server, params) .await?; Ok(serde_json::to_value(result)?) diff --git a/codex-rs/core/tests/suite/mcp_tool_exposure.rs b/codex-rs/core/tests/suite/mcp_tool_exposure.rs index 9f371df5c1..d7df6569e6 100644 --- a/codex-rs/core/tests/suite/mcp_tool_exposure.rs +++ b/codex-rs/core/tests/suite/mcp_tool_exposure.rs @@ -49,6 +49,7 @@ use core_test_support::skip_if_no_network; use core_test_support::wait_for_event; use core_test_support::wait_for_mcp_server; use pretty_assertions::assert_eq; +use rmcp::model::ReadResourceRequestParams; use serde::Deserialize; use serde_json::Value; use serde_json::json; @@ -597,7 +598,10 @@ startup_timeout_sec = 0.1 let _ = test .codex - .read_mcp_resource("refreshed", "test://resource") + .read_mcp_resource( + "refreshed", + ReadResourceRequestParams::new("test://resource"), + ) .await; assert!(resource_client.has_server("refreshed").await); Ok(())