Add explicit app and account targets to MCP resource reads (#47248)

## What changed

Allow `mcpServer/resource/read` to specify `target: {connectorId, linkId}` for direct hosted app reads without tool discovery, when supported by the backend. Require an explicit `linkId`: a string selects an account, while `null` requests no-auth access subject to backend policy.

Restrict direct targets to `codex_apps` and reject empty connector IDs, empty link IDs, and synthetic link IDs. Preserve originating app/account scope when `originCallId` and `threadId` are supplied, and retain discovery when `target` is omitted.

Update protocol schemas and TypeScript and Python models, preserving required-but-nullable `linkId` semantics in Python code generation.

## Testing

Extend resource-read tests to check target metadata with and without a thread, including explicit no-auth targets and omitted targets. Add Python model tests for string and null `linkId` values and rejection of a missing `linkId`.

GitOrigin-RevId: 8cf5595fbdd937193aeb529901fc95299f6e18ad
This commit is contained in:
victor-openai
2026-09-22 10:42:50 +00:00
committed by copyberry
parent c46aa58a7b
commit 3b50349e1a
19 changed files with 324 additions and 60 deletions
@@ -450,21 +450,41 @@ impl McpRequestProcessor {
server,
uri,
connector_id,
target,
} = params;
let mut resource_params = ReadResourceRequestParams::new(uri);
let mut meta = serde_json::Map::new();
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(),
meta.insert(
"x-codex-turn-metadata".to_string(),
serde_json::json!({
"mcp_request_meta": {"selected_connector_ids": [connector_id]},
}),
);
}
if origin_call_id.is_none()
&& let Some(target) = target
{
if server != codex_mcp::CODEX_APPS_MCP_SERVER_NAME
|| target.connector_id.trim().is_empty()
|| target
.link_id
.as_ref()
.is_some_and(|id| id.trim().is_empty() || id.starts_with("synthetic_link::"))
{
return Err(invalid_request(
"target requires codex_apps, a connectorId, and a real linkId or null",
));
}
meta.insert(
"connector_id".to_string(),
serde_json::json!(target.connector_id),
);
meta.insert("link_id".to_string(), serde_json::json!(target.link_id));
}
if !meta.is_empty() {
resource_params.meta = Some(meta.into());
}
if let Some(thread_id) = thread_id {
let (_, thread) = self.load_thread(&thread_id).await?;