mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
[codex] Surface MCP reauthentication-required startup failures (#29877)
## Summary - distinguish expired, non-refreshable stored MCP OAuth credentials from first-time missing credentials - carry a typed `failureReason: "reauthenticationRequired"` on the existing `mcpServer/startupStatus/updated` notification only when user action is required - keep the public MCP auth-status API unchanged and regenerate the app-server protocol schemas and documentation ## Why An MCP server with an expired access token and no usable refresh token currently fails startup without giving clients a reliable, typed recovery signal. The existing startup-status notification is the natural place to carry this state. Its nullable `failureReason` keeps the recovery reason attached to the failed startup transition without adding a one-off notification. Internally, Codex distinguishes first-time login from reauthentication and emits the reason only when the startup error itself requires authentication. ## User impact App clients can prompt an existing user to reconnect an MCP server when automatic recovery is impossible by handling a failed `mcpServer/startupStatus/updated` notification whose `failureReason` is `reauthenticationRequired`. Starting, ready, cancelled, unrelated failures, and first-time setup carry no reauthentication reason. ## Companion app PR - openai/openai#1069582 ## Validation - `just test -p codex-app-server-protocol` — 248 passed; schema fixture tests passed - `cargo check -p codex-app-server -p codex-tui` - `just test -p codex-rmcp-client -p codex-mcp` — 184 passed, 2 skipped - `just test -p codex-protocol -p codex-app-server-protocol -p codex-mcp` — 579 passed - `just write-app-server-schema` - `just fmt`
This commit is contained in:
@@ -23,6 +23,12 @@ v2_enum_from_core!(
|
||||
}
|
||||
);
|
||||
|
||||
v2_enum_from_core!(
|
||||
pub enum McpServerStartupFailureReason from codex_protocol::protocol::McpStartupFailureReason {
|
||||
ReauthenticationRequired
|
||||
}
|
||||
);
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
@@ -242,6 +248,7 @@ pub struct McpServerStatusUpdatedNotification {
|
||||
pub name: String,
|
||||
pub status: McpServerStartupState,
|
||||
pub error: Option<String>,
|
||||
pub failure_reason: Option<McpServerStartupFailureReason>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use super::*;
|
||||
use crate::ServerNotification;
|
||||
use codex_protocol::approvals::ElicitationRequest as CoreElicitationRequest;
|
||||
use codex_protocol::config_types::MultiAgentMode;
|
||||
use codex_protocol::items::AgentMessageContent;
|
||||
@@ -2155,6 +2156,7 @@ fn mcp_server_status_updated_accepts_missing_thread_id() {
|
||||
name: "optional_broken".to_string(),
|
||||
status: McpServerStartupState::Failed,
|
||||
error: Some("handshake failed".to_string()),
|
||||
failure_reason: None,
|
||||
};
|
||||
assert_eq!(notification, expected);
|
||||
assert_eq!(
|
||||
@@ -2164,6 +2166,33 @@ fn mcp_server_status_updated_accepts_missing_thread_id() {
|
||||
"name": "optional_broken",
|
||||
"status": "failed",
|
||||
"error": "handshake failed",
|
||||
"failureReason": null,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mcp_server_status_updated_serializes_failure_reason() {
|
||||
let notification =
|
||||
ServerNotification::McpServerStatusUpdated(McpServerStatusUpdatedNotification {
|
||||
thread_id: Some("thread-1".to_string()),
|
||||
name: "expired-oauth".to_string(),
|
||||
status: McpServerStartupState::Failed,
|
||||
error: Some("OAuth credentials expired".to_string()),
|
||||
failure_reason: Some(McpServerStartupFailureReason::ReauthenticationRequired),
|
||||
});
|
||||
|
||||
assert_eq!(
|
||||
serde_json::to_value(notification).expect("notification should serialize"),
|
||||
json!({
|
||||
"method": "mcpServer/startupStatus/updated",
|
||||
"params": {
|
||||
"threadId": "thread-1",
|
||||
"name": "expired-oauth",
|
||||
"status": "failed",
|
||||
"error": "OAuth credentials expired",
|
||||
"failureReason": "reauthenticationRequired",
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user