Skip to content

feat(mcp): OAuth 2.1 and static Bearer header auth for remote MCP servers#1937

Merged
bug-ops merged 1 commit intomainfrom
mcp-oauth-support
Mar 17, 2026
Merged

feat(mcp): OAuth 2.1 and static Bearer header auth for remote MCP servers#1937
bug-ops merged 1 commit intomainfrom
mcp-oauth-support

Conversation

@bug-ops
Copy link
Copy Markdown
Owner

@bug-ops bug-ops commented Mar 17, 2026

Summary

Resolves #1930 — adds authentication support for remote MCP HTTP servers:

  • Static headers: arbitrary request headers with vault reference resolution for Bearer ${TOKEN} patterns
  • OAuth 2.1 with PKCE: full authorization code flow with local callback server (127.0.0.1:18766), VaultCredentialStore backed by age vault for token persistence across sessions, and proactive token refresh

Key design decisions

  • Pre-bind TcpListener before register_client() to resolve redirect_uri port (avoids chicken-and-egg)
  • TCP callback reads buffered until \r\n\r\n to handle packet fragmentation
  • All discovered OAuth metadata endpoints validated via SSRF guard
  • Two-phase connect_all(): non-OAuth servers concurrent (phase 1), OAuth deferred (phase 2)
  • headers and oauth.enabled are mutually exclusive per server (validated at config load)
  • Vault key pattern: mcp.oauth.{server_id}.{access_token,refresh_token,expires_at}

New config

# Mode A: static Bearer token
[[mcp.servers]]
id = "todoist"
url = "https://api.todoist.com/mcp/v1"
[mcp.servers.headers]
Authorization = "Bearer ${TODOIST_API_TOKEN}"

# Mode B: OAuth 2.1
[[mcp.servers]]
id = "todoist-oauth"
url = "https://ai.todoist.net/mcp"
[mcp.servers.oauth]
enabled = true
token_storage = "vault"
callback_port = 18766

Known limitations

  • Telegram OAuth URL delivery goes to stderr (not a native bot message) — tracked as follow-up
  • StoredCredentials token zeroization is an upstream rmcp limitation (accepted risk, vault at-rest encrypted)

Test plan

  • 6117 tests pass (cargo nextest run --workspace --features full --lib --bins)
  • cargo +nightly fmt --check clean
  • cargo clippy --workspace --features full -- -D warnings clean
  • Unit tests: McpOAuthConfig deserialization, OAuthTokenStorage serde, Config::validate() mutual exclusion, vault key collision detection, validate_oauth_metadata_urls() SSRF fan-out

…vers

Adds two authentication modes for HTTP MCP servers:

- Static headers: arbitrary headers (e.g. Authorization: Bearer ${KEY})
  with vault reference resolution for embedded ${VAR} patterns
- OAuth 2.1 with PKCE: full authorization code flow with local callback
  server, VaultCredentialStore for token persistence across sessions,
  InMemoryStateStore for PKCE verifier/CSRF, and proactive token refresh

OAuth flow: pre-binds TcpListener before client registration to resolve
redirect_uri port, validates all discovered metadata endpoints via SSRF
guard, reads buffered HTTP until CRLF to handle TCP fragmentation.

Two-phase MCP startup: non-OAuth servers connect concurrently (phase 1),
OAuth servers connect after (phase 2) to avoid blocking startup.

Auth URL delivery: CLI via eprintln, TUI via status_tx spinner, Telegram
via stderr (follow-up: #TODO for native bot message).

New config fields on McpServerConfig: headers (HashMap<String, String>)
and oauth (McpOAuthConfig with enabled, token_storage, callback_port).
Config validation enforces mutual exclusion of headers + oauth.enabled.

Closes #1930
@github-actions github-actions bot added documentation Improvements or additions to documentation rust Rust code changes core zeph-core crate dependencies Dependency updates config Configuration file changes enhancement New feature or request size/XL Extra large PR (500+ lines) labels Mar 17, 2026
@bug-ops bug-ops enabled auto-merge (squash) March 17, 2026 01:18
@bug-ops bug-ops merged commit 3bb09bc into main Mar 17, 2026
20 checks passed
@bug-ops bug-ops deleted the mcp-oauth-support branch March 17, 2026 01:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

config Configuration file changes core zeph-core crate dependencies Dependency updates documentation Improvements or additions to documentation enhancement New feature or request rust Rust code changes size/XL Extra large PR (500+ lines)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(mcp): OAuth 2.1 support for remote MCP servers (e.g. Todoist)

1 participant