Auto-deny MCP elicitations for Xcode 26.4 clients (#21113)

## Summary

Xcode 26.4 was built against app-server behavior from before MCP
elicitation requests became client-visible in CLI 0.120.0 via #17043.
That client line does not expect the new events/messages, so this PR
restores the old behavior for exactly that client/version combination.

The compatibility handling stays in the app-server layer: when the
initialized client is `Xcode` and its version starts with `26.4`, the
app server marks the live Codex thread so MCP elicitations are
auto-denied. The flag is applied on thread start/resume/fork/turn
attachment, carried through `Codex`/`CodexThread`, and stored on
`McpConnectionManager` so refreshed MCP managers preserve the behavior.

## Notes

This is intentionally narrow and includes a TODO to remove the
compatibility path once Xcode 26.4 ages out.
This commit is contained in:
Eric Traut authored and GitHub committed 2026-05-05 14:05:42 -07:00
1 parent f593323ef1
commit 8c88f9a304
8 files changed
+162 -13

No files matched your search

+29
View File
@@ -36,6 +36,7 @@ pub(crate) struct ElicitationRequestManager {
requests: Arc<Mutex<ResponderMap>>,
pub(crate) approval_policy: Arc<StdMutex<AskForApproval>>,
pub(crate) permission_profile: Arc<StdMutex<PermissionProfile>>,
auto_deny: Arc<StdMutex<bool>>,
}
impl ElicitationRequestManager {
@@ -47,6 +48,20 @@ impl ElicitationRequestManager {
requests: Arc::new(Mutex::new(HashMap::new())),
approval_policy: Arc::new(StdMutex::new(approval_policy)),
permission_profile: Arc::new(StdMutex::new(permission_profile)),
auto_deny: Arc::new(StdMutex::new(false)),
}
}
pub(crate) fn auto_deny(&self) -> bool {
self.auto_deny
.lock()
.map(|auto_deny| *auto_deny)
.unwrap_or(false)
}
pub(crate) fn set_auto_deny(&self, auto_deny: bool) {
if let Ok(mut current) = self.auto_deny.lock() {
*current = auto_deny;
}
}
@@ -73,13 +88,27 @@ impl ElicitationRequestManager {
let elicitation_requests = self.requests.clone();
let approval_policy = self.approval_policy.clone();
let permission_profile = self.permission_profile.clone();
let auto_deny = self.auto_deny.clone();
Box::new(move |id, elicitation| {
let elicitation_requests = elicitation_requests.clone();
let tx_event = tx_event.clone();
let server_name = server_name.clone();
let approval_policy = approval_policy.clone();
let permission_profile = permission_profile.clone();
let auto_deny = auto_deny.clone();
async move {
let auto_deny = auto_deny
.lock()
.map(|auto_deny| *auto_deny)
.unwrap_or(false);
if auto_deny {
return Ok(ElicitationResponse {
action: ElicitationAction::Decline,
content: None,
meta: None,
});
}
let approval_policy = approval_policy
.lock()
.map(|policy| *policy)