Skip to content

feat(mcp): OAuth 2.1 authorization for remote MCP servers#1161

Merged
diillson merged 1 commit into
mainfrom
feat/mcp-oauth
Jul 11, 2026
Merged

feat(mcp): OAuth 2.1 authorization for remote MCP servers#1161
diillson merged 1 commit into
mainfrom
feat/mcp-oauth

Conversation

@diillson

Copy link
Copy Markdown
Owner

Problem

Remote MCP servers gated behind OAuth 2.1 could not be connected. The MCP client only supported static bearer/basic/header auth and treated a 401 as a generic error, so servers such as the AWS MCP server never authorized (mirroring how /mcp re-authorizes in other clients was impossible).

What this adds

The full MCP authorization flow, reusing the existing auth/ primitives (PKCE, loopback callback, encrypted store, single-flight refresh, DoWithRefresh):

  • On a 401, parse the WWW-Authenticate challenge, discover protected-resource metadata (RFC 9728) and authorization-server metadata (RFC 8414), register a public client via Dynamic Client Registration (RFC 7591), run a PKCE authorization-code flow through a localhost callback with the resource indicator (RFC 8707), and persist tokens.
  • Tokens refresh transparently; a 401 surviving a refresh surfaces an actionable error so the agent can re-authorize mid-session.

New surfaces

  • @mcp-login built-in tool (login / status / logout) so the agent authorizes a server after a tool call reports it is unauthorized. Auto-registers into the completer, palette and tool catalog via GetPlugins.
  • /mcp login and /mcp logout commands, wired into the completer.
  • /mcp status shows an authorization-required state with a login hint.

Security

  • Access and refresh tokens are stored in the existing AES-256-GCM encrypted auth store (~/.chatcli/auth-profiles.json, 0600). Only non-secret metadata (endpoints, issuer, public client_id) lives in ~/.chatcli/mcp_oauth/<server>.json.

Compatibility

  • The Anthropic and OpenAI OAuth logins are unchanged: the token provider constructor only delegates to a shared implementation with the default refresh path. All existing auth/ tests pass.
  • Automatic 401 detection is scoped to the streamable HTTP transport (the AWS MCP case). SSE uses stored OAuth tokens and supports /mcp login via well-known discovery.

Tests

  • End-to-end: refresh-on-401 then retry succeeds through the real transport; a 401 returns a typed OAuthRequiredError with the parsed challenge.
  • Unit: challenge parsing, discovery chain, dynamic client registration, refresh closure, metadata store round-trip.
  • Full suite, go vet, and golangci-lint all green. i18n keys added for en, en-US and pt-BR.

@github-actions

github-actions Bot commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Quality Gate

Result: ✅ all floors passed

Floor Status Result Δ vs main Budget
1 · Build & Static go build / vet / fmt / lint
2 · Coverage 49.9% (bootstrap) 0 ≥ baseline
3 · Patch coverage ⚠️ 64.5% (req ≥ 60%) ≥ 60%
4 · AI smells diff scanned
5 · Scope budget ⚠️ 28 files / 3007 LOC (code 3007 + tooling 0) warn 800·25
6 · E2E go test -race ./e2e/... ≤ 15min
7 · Commit lint conventional commits
8 · Cyclo (new code) 13 file(s) under threshold ≤ 30
9 · Secrets scan gitleaks
10 · i18n parity missing 0, unknown 0
11 · CRD drift drifted: 0
12 · License headers 0 missing
13 · API breaking 0 incompatible
14 · Binary size chatcli 90.9MB · operator 52.3MB 100MB each
15 · Provider parity 15 providers · 0 violations

Config: .github/quality-gate.yml. Workflow: .github/workflows/quality-gate.yml.

@diillson diillson added the large-pr-approved Bypass Quality Gate LOC threshold (PR is intentionally large) label Jul 10, 2026
Remote MCP servers gated behind OAuth 2.1 could not be connected: the MCP
client only supported static bearer/basic/header auth and treated a 401 as a
generic error, so servers such as the AWS MCP server never authorized.

Add the full MCP authorization flow, reusing the existing auth package
primitives. On a 401 the client parses the WWW-Authenticate challenge,
discovers the protected-resource and authorization-server metadata, registers
a public client via dynamic client registration, runs a PKCE authorization
code flow through a localhost callback with the resource indicator, and stores
the resulting tokens in the encrypted auth store. Tokens refresh transparently
via the shared refreshable token provider, and a 401 that survives a refresh
surfaces an actionable error so the agent can re-authorize.

New surfaces:
- @mcp-login built-in tool with login, status and logout subcommands, so the
  agent can authorize a server after a tool call reports it is unauthorized.
- /mcp login and /mcp logout commands, both wired into the completer.
- /mcp status shows an authorization-required state with a login hint.

The SSE transport also uses stored OAuth tokens; automatic 401 detection is
scoped to the streamable HTTP transport. The Anthropic and OpenAI OAuth logins
are unchanged: the token provider constructor only delegates to a shared
implementation with the default refresh path.
@diillson diillson merged commit c41831f into main Jul 11, 2026
25 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

large-pr-approved Bypass Quality Gate LOC threshold (PR is intentionally large)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant