mirror of
https://github.com/openai/codex.git
synced 2026-10-19 17:28:46 +00:00
## Why Configured MCP servers must not be able to request user verification, even when they reuse the hosted apps server name. Local verification identity selection also needs to distinguish a user's workspace memberships. ## What changed - Advertise `userVerification` only to the host-owned apps MCP server and cancel verification requests from other servers before prompting the client. - Add `CodexAuth::get_chatgpt_account_user_id()` to read the access token's opaque `chatgpt_account_user_id` only when its workspace matches the selected account. Missing or malformed claims return no identity, without falling back to another user ID or breaking ordinary authentication. ## Testing Add coverage for capability filtering, rejection of configured servers including those using the hosted apps name, and account-user identity selection with malformed or mismatched claims. Add an MCP round-trip test showing that verification waits for a client response even in full access mode, returns the supplied proof, and discards response metadata. GitOrigin-RevId: 156e83d5a0302c013b85e564b46b4bfe858cbdb0
74 lines
2.5 KiB
Rust
74 lines
2.5 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;
|
|
|
|
/// Restricts verification to the host-owned plugin service, even when another server uses its name.
|
|
pub(crate) fn server_mcp_extensions(
|
|
extensions: &ClientMcpExtensions,
|
|
server_name: &str,
|
|
server: Option<&crate::catalog::ResolvedMcpServer>,
|
|
) -> ClientMcpExtensions {
|
|
let plugin_service = server.is_some_and(|server| {
|
|
server
|
|
.source()
|
|
.is_host_owned_apps(server_name, server.config())
|
|
});
|
|
ClientMcpExtensions::new(extensions.iter().map(|(id, settings)| {
|
|
let mut settings = settings.clone();
|
|
if !plugin_service
|
|
&& id == OPENAI_ELICITATION_EXTENSION_ID
|
|
&& let Some(settings) = settings.as_object_mut()
|
|
{
|
|
settings.remove("userVerification");
|
|
}
|
|
(id.to_string(), settings)
|
|
}))
|
|
}
|
|
|
|
/// 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;
|