Skip to content

Commit a6d20ed

Browse files
authored
[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`
1 parent b80fbb7 commit a6d20ed

27 files changed

Lines changed: 350 additions & 55 deletions

codex-rs/app-server-protocol/schema/json/ServerNotification.json

Lines changed: 16 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json

Lines changed: 16 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json

Lines changed: 16 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

codex-rs/app-server-protocol/schema/json/v2/McpServerStatusUpdatedNotification.json

Lines changed: 16 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

codex-rs/app-server-protocol/schema/typescript/v2/McpServerStartupFailureReason.ts

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

codex-rs/app-server-protocol/schema/typescript/v2/McpServerStatusUpdatedNotification.ts

Lines changed: 2 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

codex-rs/app-server-protocol/schema/typescript/v2/index.ts

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

codex-rs/app-server-protocol/src/protocol/v2/mcp.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,12 @@ v2_enum_from_core!(
2323
}
2424
);
2525

26+
v2_enum_from_core!(
27+
pub enum McpServerStartupFailureReason from codex_protocol::protocol::McpStartupFailureReason {
28+
ReauthenticationRequired
29+
}
30+
);
31+
2632
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
2733
#[serde(rename_all = "camelCase")]
2834
#[ts(export_to = "v2/")]
@@ -242,6 +248,7 @@ pub struct McpServerStatusUpdatedNotification {
242248
pub name: String,
243249
pub status: McpServerStartupState,
244250
pub error: Option<String>,
251+
pub failure_reason: Option<McpServerStartupFailureReason>,
245252
}
246253

247254
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]

codex-rs/app-server-protocol/src/protocol/v2/tests.rs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use super::*;
2+
use crate::ServerNotification;
23
use codex_protocol::approvals::ElicitationRequest as CoreElicitationRequest;
34
use codex_protocol::config_types::MultiAgentMode;
45
use codex_protocol::items::AgentMessageContent;
@@ -2155,6 +2156,7 @@ fn mcp_server_status_updated_accepts_missing_thread_id() {
21552156
name: "optional_broken".to_string(),
21562157
status: McpServerStartupState::Failed,
21572158
error: Some("handshake failed".to_string()),
2159+
failure_reason: None,
21582160
};
21592161
assert_eq!(notification, expected);
21602162
assert_eq!(
@@ -2164,6 +2166,33 @@ fn mcp_server_status_updated_accepts_missing_thread_id() {
21642166
"name": "optional_broken",
21652167
"status": "failed",
21662168
"error": "handshake failed",
2169+
"failureReason": null,
2170+
})
2171+
);
2172+
}
2173+
2174+
#[test]
2175+
fn mcp_server_status_updated_serializes_failure_reason() {
2176+
let notification =
2177+
ServerNotification::McpServerStatusUpdated(McpServerStatusUpdatedNotification {
2178+
thread_id: Some("thread-1".to_string()),
2179+
name: "expired-oauth".to_string(),
2180+
status: McpServerStartupState::Failed,
2181+
error: Some("OAuth credentials expired".to_string()),
2182+
failure_reason: Some(McpServerStartupFailureReason::ReauthenticationRequired),
2183+
});
2184+
2185+
assert_eq!(
2186+
serde_json::to_value(notification).expect("notification should serialize"),
2187+
json!({
2188+
"method": "mcpServer/startupStatus/updated",
2189+
"params": {
2190+
"threadId": "thread-1",
2191+
"name": "expired-oauth",
2192+
"status": "failed",
2193+
"error": "OAuth credentials expired",
2194+
"failureReason": "reauthenticationRequired",
2195+
},
21672196
})
21682197
);
21692198
}

codex-rs/app-server/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1345,7 +1345,7 @@ Because audio is intentionally separate from `ThreadItem`, clients can opt out o
13451345

13461346
### MCP server startup events
13471347

1348-
- `mcpServer/startupStatus/updated``{ threadId, name, status, error }` when app-server observes an MCP server startup transition. `threadId` identifies the owning thread when startup is thread-scoped and is `null` when startup is app-scoped. `status` is one of `starting`, `ready`, `failed`, or `cancelled`. `error` is `null` except for `failed`.
1348+
- `mcpServer/startupStatus/updated``{ threadId, name, status, error, failureReason }` when app-server observes an MCP server startup transition. `threadId` identifies the owning thread when startup is thread-scoped and is `null` when startup is app-scoped. `status` is one of `starting`, `ready`, `failed`, or `cancelled`. `error` and `failureReason` are `null` except for `failed`; `failureReason` is `reauthenticationRequired` when stored OAuth credentials have expired and cannot be refreshed, so clients can prompt the user to reconnect the named server.
13491349

13501350
### Turn events
13511351

@@ -1935,7 +1935,7 @@ Codex supports these authentication modes. The current mode is surfaced in `acco
19351935
- `account/rateLimits/updated` (notify) — emitted whenever a user's ChatGPT rate limits change. This is a sparse rolling update; merge available values into the most recent `account/rateLimits/read` response or refetch that snapshot.
19361936
- `account/sendAddCreditsNudgeEmail` — ask ChatGPT to email the workspace owner about depleted credits or a reached usage limit.
19371937
- `mcpServer/oauthLogin/completed` (notify) — emitted after a `mcpServer/oauth/login` flow finishes for a server; payload includes `{ name, threadId, success, error? }`.
1938-
- `mcpServer/startupStatus/updated` (notify) — emitted when a configured MCP server's startup status changes; payload includes `{ threadId, name, status, error }`, where `threadId` is the owning thread when startup is thread-scoped and `null` when it is app-scoped, and `status` is `starting`, `ready`, `failed`, or `cancelled`.
1938+
- `mcpServer/startupStatus/updated` (notify) — emitted when a configured MCP server's startup status changes; payload includes `{ threadId, name, status, error, failureReason }`, where `threadId` is the owning thread when startup is thread-scoped and `null` when it is app-scoped, and `status` is `starting`, `ready`, `failed`, or `cancelled`. `failureReason` is `reauthenticationRequired` when stored OAuth credentials have expired and cannot be refreshed, so clients can prompt the user to reconnect the named server.
19391939

19401940
### 1) Check auth state
19411941

0 commit comments

Comments
 (0)