MCP transport does not implement OAuth step-up authentication for 403 insufficient_scope
What version of Codex CLI is running?
0.125.0
What subscription do you have?
Enterprise
Which model were you using?
gpt-5.5
What platform is your computer?
Darwin 25.4.0 arm64 arm
What terminal emulator and version are you using (if applicable)?
_No response_
What issue are you seeing?
When an MCP server returns 403 Forbidden with insufficient_scope, Codex surfaces it as an error and stops. The MCP authorization spec describes a step-up authentication flow for this case: the client should parse the WWW-Authenticate header, determine the required scopes, and re-initiate the OAuth flow to obtain a token with sufficient permissions.
The WWW-Authenticate header in the 403 response already contains everything needed:
WWW-Authenticate: Bearer error="insufficient_scope",
error_description="Insufficient scope",
resource_metadata="https://mcp.example.com/.well-known/oauth-protected-resource",
scope="data.records:read data.records:write schema.bases:read ..."
What steps can reproduce the bug?
- Configure an MCP server protected by OAuth where the current token has limited scope.
- Invoke a tool that requires a scope the token does not have.
- Observe that Codex errors out rather than prompting the user to re-authenticate with the required scopes.
What is the expected behavior?
On 403 Forbidden with error="insufficient_scope", Codex should:
- Parse the
WWW-Authenticateheader to extract the requiredscopeandresource_metadata. - Re-initiate the OAuth authorization flow requesting the additional scopes.
- Retry the tool call with the new token.
This is analogous to the existing 401 handling in http_client_adapter.rs, which already returns Err(AuthRequired(...)) when a WWW-Authenticate header is present. 403 with insufficient_scope should follow the same pattern.
Additional information
The fix would be in codex-rs/rmcp-client/src/http_client_adapter.rs. The 401 case already does:
A 403 handler should be added alongside it:
StatusCode::FORBIDDEN => {
if let Some(www_auth) = response.headers.get("WWW-Authenticate") {
// Parse scope from www_auth and trigger step-up auth.
return Err(StreamableHttpError::InsufficientScope(...));
}
}This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗