You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
CLI: OAuth denials and other non-usage failures exit 1 with code "error", the same as a usage error #2842
Operating system (and browser, for the web client)
Linux (Debian)
Transport
Streamable HTTP
MCP server under inspection
The repo's own OAuth test server: node test-servers/build/server-composable.js --config test-servers/configs/oauth-revocation-http.json (Streamable HTTP on :8083, DCR + refresh + revocation).
Decline at the authorization server, so the callback carries error=access_denied.
Expected behavior
A distinct, machine-readable outcome for an OAuth protocol error — e.g. code oauth_error with exit 3 (AUTH_REQUIRED), and error / error_description as separate envelope fields — so a script or CI job can tell "the authorization server said no" from "the command line is wrong".
Actual behavior
{"error":{"code":"error","message":"<error_description>"}} and exit 1, which EXIT_CODES defines as USAGE.
The catch-all at the end of classifyError (core/cli/error-handler.ts) returns EXIT_CODES.USAGE / "error" for anything not matched earlier, so OAuth callback errors, OAuth callback timeouts and fetch-guard refusals (FetchTargetError) all exit 1 with the same code as a mistyped flag.
Logs, errors, or screenshots
Suggested: classify OAuth callback / token-endpoint protocol errors as oauth_error (exit 3), FetchTargetError as fetch_refused, the callback timeout as its own code, and keep exit 1 for genuine usage errors only.
Already prototyped a fix?
No response
Before you submit
I searched existing issues and this is not a duplicate.
This is not a security vulnerability report (those go through the private advisory process).
Design notes for this one, following the repo's contribution flow (comment first, maintainers open the PR).
Problem. When an OAuth-protected server fails during discovery or token exchange, the client surfaces a generic connection failure. Three distinct failure classes are collapsed into one: (1) protocol errors returned by the authorization server (a structured OAuth error / error_description payload, for example invalid_client or invalid_grant), (2) transport failures reaching the target (FetchTargetError in the auth layer), and (3) timeouts waiting on the authorization server (OAuthRequestTimeoutError). Users cannot tell whether to fix their server configuration, their network, or simply retry.
Proposal.
Preserve the OAuth error payload end to end: when the authorization server returns a structured error, surface error and error_description in the connection error UI instead of replacing them with a generic message, and log the raw payload once at debug level.
Map the three classes to three distinct user-facing states: protocol error (show the server's description and point at server configuration), target unreachable (FetchTargetError: show the target URL and the underlying fetch failure), and timeout (OAuthRequestTimeoutError: show the elapsed limit and offer a retry action).
Keep the mapping in one place: a single error-normalization step in the auth flow that converts thrown errors into a typed connection error, so future error classes land in the same UI path instead of each call site formatting its own message.
What this deliberately does not do. No retry policy changes and no changes to the token flow itself; this is about faithful reporting of errors the code already detects.
Happy to refine the class boundaries against the current auth code if maintainers want a different split before a PR is opened.
Checked against the 2.14.0 release: still reproduces. classifyError in the CLI build still ends in the catch-all that returns EXIT_CODES.USAGE with code "error", and there is no oauth_error code anywhere in the package. An OAuth denial, an OAuth callback timeout and a FetchTargetError all still exit 1, the same as a mistyped flag.
Two notes:
Milestone. This is now on v2.11.0, which is closed and older than the 2.13.0 build I reported against. Looks like it was meant to stay on v2.14.0 (or the next open one) before that shipped. @cliffhall could you move it?
Scope. The design notes above are about the web client's connection-error UI. That is a fair improvement, but it is a different surface. This issue is only the CLI: the JSON envelope code and the process exit code that scripts and CI branch on. A mapping that would close it:
Failure
code
Exit
Authorization server returns error / error_description (callback or token endpoint)
oauth_error (with error and error_description as separate fields)
3 (AUTH_REQUIRED)
Timed out waiting for the OAuth callback
oauth_timeout
3 (AUTH_REQUIRED)
Fetch guard refused the target (FetchTargetError)
fetch_refused
not 1, since it is a policy refusal, not bad arguments
Genuine usage error
error / usage
1
I see fork commits referencing this issue but no PR yet. Once there is a branch, I'm happy to verify it against test-servers/configs/oauth-revocation-http.json with the same decline-at-consent repro.
Checked against the 2.14.0 release: still reproduces. classifyError in the CLI build still ends in the catch-all that returns EXIT_CODES.USAGE with code "error", and there is no oauth_error code anywhere in the package. An OAuth denial, an OAuth callback timeout and a FetchTargetError all still exit 1, the same as a mistyped flag.
Two notes:
Milestone. This is now on v2.11.0, which is closed and older than the 2.13.0 build I reported against. Looks like it was meant to stay on v2.14.0 (or the next open one) before that shipped. @cliffhall could you move it?
Scope. The design notes above are about the web client's connection-error UI. That is a fair improvement, but it is a different surface. This issue is only the CLI: the JSON envelope code and the process exit code that scripts and CI branch on. A mapping that would close it:
Failure
code
Exit
Authorization server returns error / error_description (callback or token endpoint)
oauth_error (with error and error_description as separate fields)
3 (AUTH_REQUIRED)
Timed out waiting for the OAuth callback
oauth_timeout
3 (AUTH_REQUIRED)
Fetch guard refused the target (FetchTargetError)
fetch_refused
not 1, since it is a policy refusal, not bad arguments
Genuine usage error
error / usage
1
I see fork commits referencing this issue but no PR yet. Once there is a branch, I'm happy to verify it against test-servers/configs/oauth-revocation-http.json with the same decline-at-consent repro.
Activity
cliffhall commentedon Oct 29, 2026
Triage: Priority Low (total 5)
buglabel (no milestone, assignee, epic, engagement or reporter-set priority)Board: #28, Status
Incoming(awaiting maintainer review — no milestone yet).Ben-Home commentedon Oct 30, 2026
fix(cli): classify OAuth authorization denials as oauth_error (modelc…
techaurah commentedon Nov 6, 2026
Design notes for this one, following the repo's contribution flow (comment first, maintainers open the PR).
Problem. When an OAuth-protected server fails during discovery or token exchange, the client surfaces a generic connection failure. Three distinct failure classes are collapsed into one: (1) protocol errors returned by the authorization server (a structured OAuth
error/error_descriptionpayload, for exampleinvalid_clientorinvalid_grant), (2) transport failures reaching the target (FetchTargetErrorin the auth layer), and (3) timeouts waiting on the authorization server (OAuthRequestTimeoutError). Users cannot tell whether to fix their server configuration, their network, or simply retry.Proposal.
erroranderror_descriptionin the connection error UI instead of replacing them with a generic message, and log the raw payload once at debug level.FetchTargetError: show the target URL and the underlying fetch failure), and timeout (OAuthRequestTimeoutError: show the elapsed limit and offer a retry action).What this deliberately does not do. No retry policy changes and no changes to the token flow itself; this is about faithful reporting of errors the code already detects.
Happy to refine the class boundaries against the current auth code if maintainers want a different split before a PR is opened.
fix(cli): classify OAuth protocol, timeout, and fetch target errors i…
fix(cli): classify OAuth protocol, timeout, and fetch target errors i…
fix(cli): classify OAuth protocol, timeout, and fetch target errors i…
fix(cli): classify OAuth protocol, timeout, and fetch target errors i…
fix(cli): classify OAuth protocol, timeout, and fetch target errors i…
fix(cli): classify OAuth protocol, timeout, and fetch target errors i…
CloneOfAlex commentedon Nov 9, 2026
Checked against the 2.14.0 release: still reproduces.
classifyErrorin the CLI build still ends in the catch-all that returnsEXIT_CODES.USAGEwith code"error", and there is nooauth_errorcode anywhere in the package. An OAuth denial, an OAuth callback timeout and aFetchTargetErrorall still exit 1, the same as a mistyped flag.Two notes:
Milestone. This is now on v2.11.0, which is closed and older than the 2.13.0 build I reported against. Looks like it was meant to stay on v2.14.0 (or the next open one) before that shipped. @cliffhall could you move it?
Scope. The design notes above are about the web client's connection-error UI. That is a fair improvement, but it is a different surface. This issue is only the CLI: the JSON envelope
codeand the process exit code that scripts and CI branch on. A mapping that would close it:codeerror/error_description(callback or token endpoint)oauth_error(witherroranderror_descriptionas separate fields)AUTH_REQUIRED)oauth_timeoutAUTH_REQUIRED)FetchTargetError)fetch_refusederror/usageI see fork commits referencing this issue but no PR yet. Once there is a branch, I'm happy to verify it against
test-servers/configs/oauth-revocation-http.jsonwith the same decline-at-consent repro.CloneOfAlex commentedon Nov 9, 2026
Checked against the 2.14.0 release: still reproduces.
classifyErrorin the CLI build still ends in the catch-all that returnsEXIT_CODES.USAGEwith code"error", and there is nooauth_errorcode anywhere in the package. An OAuth denial, an OAuth callback timeout and aFetchTargetErrorall still exit 1, the same as a mistyped flag.Two notes:
Milestone. This is now on v2.11.0, which is closed and older than the 2.13.0 build I reported against. Looks like it was meant to stay on v2.14.0 (or the next open one) before that shipped. @cliffhall could you move it?
Scope. The design notes above are about the web client's connection-error UI. That is a fair improvement, but it is a different surface. This issue is only the CLI: the JSON envelope
codeand the process exit code that scripts and CI branch on. A mapping that would close it:codeerror/error_description(callback or token endpoint)oauth_error(witherroranderror_descriptionas separate fields)AUTH_REQUIRED)oauth_timeoutAUTH_REQUIRED)FetchTargetError)fetch_refusederror/usageI see fork commits referencing this issue but no PR yet. Once there is a branch, I'm happy to verify it against
test-servers/configs/oauth-revocation-http.jsonwith the same decline-at-consent repro.fix(cli): classify OAuth protocol, timeout, and fetch target errors i…