mirror of
https://github.com/openai/codex.git
synced 2026-11-13 14:46:14 +00: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:
1 parent
b80fbb70cd
commit
a6d20ed297
27 files changed
+350
-55
No files matched your search
@@ -63,6 +63,7 @@ use rmcp::model::InitializeRequestParams;
|
||||
use rmcp::model::JsonObject;
|
||||
use rmcp::model::ProtocolVersion;
|
||||
use rmcp::model::Tool as RmcpTool;
|
||||
use rmcp::transport::auth::AuthError;
|
||||
use tokio_util::sync::CancellationToken;
|
||||
use tracing::Instrument;
|
||||
use tracing::instrument;
|
||||
@@ -304,13 +305,39 @@ pub(crate) enum StartupOutcomeError {
|
||||
// We can't store the original error here because anyhow::Error doesn't implement
|
||||
// `Clone`.
|
||||
#[error("MCP startup failed: {error}")]
|
||||
Failed { error: String },
|
||||
Failed {
|
||||
error: String,
|
||||
is_authentication_required: bool,
|
||||
},
|
||||
}
|
||||
|
||||
impl StartupOutcomeError {
|
||||
pub(crate) fn is_authentication_required(&self) -> bool {
|
||||
match self {
|
||||
Self::Cancelled => false,
|
||||
Self::Failed {
|
||||
is_authentication_required,
|
||||
..
|
||||
} => *is_authentication_required,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<anyhow::Error> for StartupOutcomeError {
|
||||
fn from(error: anyhow::Error) -> Self {
|
||||
let is_authentication_required = error.chain().any(|source| {
|
||||
source
|
||||
.downcast_ref::<AuthError>()
|
||||
.is_some_and(|auth_error| {
|
||||
matches!(
|
||||
auth_error,
|
||||
AuthError::AuthorizationRequired | AuthError::TokenExpired
|
||||
)
|
||||
})
|
||||
});
|
||||
Self::Failed {
|
||||
error: error.to_string(),
|
||||
is_authentication_required,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -765,6 +792,17 @@ mod tests {
|
||||
use pretty_assertions::assert_eq;
|
||||
use rmcp::model::JsonObject;
|
||||
use rmcp::model::Meta;
|
||||
use rmcp::transport::auth::AuthError;
|
||||
|
||||
#[test]
|
||||
fn startup_outcome_error_identifies_authentication_required() {
|
||||
let error = anyhow::Error::new(AuthError::AuthorizationRequired)
|
||||
.context("failed to initialize MCP server");
|
||||
|
||||
let error = StartupOutcomeError::from(error);
|
||||
|
||||
assert!(error.is_authentication_required());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mcp_initialize_advertises_openai_form_only_when_supported() {
|
||||
|
||||
Reference in new issue
Block a user