Files
codex/codex-rs/codex-mcp/src/client_capabilities.rs
T
victor-openai eec4a23cb1 Support openai/elicitation form requests (#41447)
## What changed

- Advertise `openai/elicitation` form support when the client declares an
  object-valued `form` capability, without deriving it from the legacy
  `openai/form` capability.
- Handle `openai/elicitation/create` requests in `form` mode and forward their
  metadata and opaque schema through app-server as `openaiForm`, preserving
  `x-openai-*` annotations.
- Keep legacy form handling independent and have the TUI automatically decline
  the new form requests it cannot render.

## Testing

- Add an app-server round-trip test for capability negotiation, request
  forwarding, and accepted responses.
- Extend MCP capability filtering coverage for `openai/elicitation.form`.

GitOrigin-RevId: 88f39257bffafe4ee98a9c910e38507843cd7eeb
2026-08-29 02:43:10 +00:00

51 lines
1.7 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_ELICITATION_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_ELICITATION_EXTENSION_ID
| OPENAI_STANDARD_FORM_INPUT_EXTENSION_ID
| MCP_APP_UI_EXTENSION_ID
)
})
.map(|(id, value)| (id.clone(), value.clone()))
.collect::<HashMap<_, _>>();
if let Some(settings) = selected
.get_mut(OPENAI_ELICITATION_EXTENSION_ID)
.and_then(Value::as_object_mut)
{
settings.retain(|key, _| key == "form");
}
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;