Files
Mason Daugherty 6199c71d60 fix(code): token-safe MCP OAuth login error handling and loopback edge cases (#3492)
Tighten the MCP OAuth login flow surfaced by the recent in-TUI work:
keep tokens out of error logs and chat history, stop showing a "you're
signed in" page after the handshake actually failed, and skip the 300s
loopback wait when no browser is actually available. Also sweeps stale
`deepagents` CLI references to `dcode`, makes worker-side interrupt
cleanup user-visible, and replaces three structural `getattr` probes
with explicit `isinstance` checks.

## Changes
- **Token-safe error reporting**: new `format_login_failure` in
`mcp_auth` unwraps `ExceptionGroup`/cause chains, surfaces known-safe
message types (`MCPReauthRequiredError`, loopback-internal errors), and
degrades everything else to a class-name chain. Replaces three unsafe
`f"...{login_error}"` / `exc_info=login_error` sites in
`_run_mcp_login_worker` and the broad catch in `run_mcp_login`. Tokens
that could ride along in MCP-SDK exception `args`/`repr` no longer reach
logs or the chat surface.
- **Loopback duplicate request after failure no longer renders success
HTML**: `_LoopbackOAuthCallbackServer._handle_get` now branches on
`future.exception() is None` before serving the success page; failed
callbacks get a 400 + error page on retries (favicon, prefetch).
- **Reliable browser detection**: redirect handler probes
`webbrowser.get()` before `webbrowser.open()` so headless/SSH sessions
trip the paste-back fallback immediately instead of waiting out
`_LOOPBACK_CALLBACK_TIMEOUT`.
- **Better failure surfaces**: `mcp_commands` distinguishes
`PermissionError` on the tokens dir with an actionable remediation;
`MCPReauthRequiredError` and corrupt-token-file errors now include `/mcp
login <server>` (TUI) and `dcode mcp login <server>` (CLI) commands; the
interrupt-cleanup `except Exception` in `textual_adapter` now mounts a
chat-visible `AppMessage` instead of only file-logging.
- **Replace `getattr` probes with typed checks**: `find_reauth_required`
and `format_login_failure` use `isinstance(current, BaseExceptionGroup)`
instead of probing `.exceptions`; `_prompt_slack_team` checks
`isinstance(ui, _SupportsSlackTeamPrompt)` against a new
runtime-checkable capability protocol.
- **Encapsulation cleanups**: new `MCPLoginScreen.is_done`,
`MCPServerHeaderItem.server`, and `_LoopbackOAuthCallbackServer.port`
properties replace private-attribute reads from outside their owning
modules.
- **Stale CLI name sweep**: `deepagents` → `dcode` in the
remote-server-mode notify, the post-restart MCP-token notify, and the
untrusted-project-config hint.
- **Docs**: `build_connecting_footer` docstring no longer contradicts
itself on when `reconnecting` is honored; `OAuthInteraction.show_error`
renamed from "terminal" to "fatal" wording for a TUI codebase;
token-endpoint `ValueError` on malformed JSON now logs the cause before
coercing to `{}`.

## Testing
- New `TestFormatLoginFailure` covers reauth unwrap, token-redaction for
unknown exception types, known-loopback passthrough, and cause-chain
walking with payload omission.
- New loopback tests for the `_LoopbackCallbackTimeoutError` paste-back
fallback, the `webbrowser.get` failure path, and the
duplicate-request-after-error 400 page.
- New `_run_mcp_login_worker` failure-branch tests for config-resolve
error mounting and unknown-exception token redaction.
- `test_login_runtime_error_returns_exit_1` rewritten to assert the new
token-safe summary — generic `RuntimeError` messages must not bleed into
the CLI output.
2026-05-19 17:13:11 -05:00
..