Files
codex/codex-rs/codex-mcp/src/client_capabilities.rs
mchen-oai 4b0e2a0bff Support MCP form input in full-access user threads (#37864)
## Why

Standard MCP forms can require user-entered values even when tool permissions are
otherwise auto-approved in full-access sessions.

## What changed

- Recognize the `openai/standard-form-input` client extension and surface
  non-approval forms in full-access, user-initiated root threads.
- Keep approval forms, automation and subagent threads, headless sessions, and
  clients without the capability on their existing decline or review paths.
- Treat the capability as client-only so it is not advertised to MCP servers,
  and enable it after session startup so required servers cannot block startup
  waiting for form input.

## Testing

Add unit and app-server coverage for accepted form round trips, declined cases,
approval metadata safeguards, resumed threads, and extension filtering.

GitOrigin-RevId: 053bfe397a5c79eceef90a81d13e2aca6353af43
2026-08-10 18:20:12 +00:00

43 lines
1.4 KiB
Rust

use std::collections::HashMap;
use codex_protocol::mcp::ClientMcpExtensions;
use codex_protocol::mcp::MCP_APP_UI_EXTENSION_ID;
use codex_protocol::mcp::OPENAI_FORM_EXTENSION_ID;
use codex_protocol::mcp::OPENAI_STANDARD_FORM_INPUT_EXTENSION_ID;
use serde_json::Map;
use serde_json::Value;
/// Selects the MCP extensions Codex supports from those declared by the app-server host.
///
/// App-server clients may declare unrelated extensions. Codex retains only the
/// trusted extension namespaces it knows how to project downstream. The
/// legacy form capability is normalized into the same extension map.
pub fn client_mcp_extensions(
extensions: Option<&HashMap<String, Value>>,
legacy_openai_form_elicitation: bool,
) -> ClientMcpExtensions {
let mut selected = extensions
.into_iter()
.flat_map(HashMap::iter)
.filter(|(id, _)| {
matches!(
id.as_str(),
OPENAI_FORM_EXTENSION_ID
| OPENAI_STANDARD_FORM_INPUT_EXTENSION_ID
| MCP_APP_UI_EXTENSION_ID
)
})
.map(|(id, value)| (id.clone(), value.clone()))
.collect::<HashMap<_, _>>();
if legacy_openai_form_elicitation {
selected
.entry(OPENAI_FORM_EXTENSION_ID.to_string())
.or_insert_with(|| Value::Object(Map::new()));
}
ClientMcpExtensions::new(selected)
}
#[cfg(test)]
#[path = "client_capabilities_tests.rs"]
mod tests;