Skip to content

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

@bug-ops

Description

@bug-ops

Problem

Remote MCP servers that require OAuth 2.1 authorization (e.g. https://ai.todoist.net/mcp) cannot be used with Zeph. The current McpClient::connect_url() in crates/zeph-mcp/src/client.rs uses StreamableHttpClientTransport::from_uri() without any auth headers or OAuth flow.

Library Support

rmcp already supports OAuth 2.1 via the auth feature flag (verified in rmcp v1.2.0 docs):

  • Full OAuth 2.1 with PKCE (S256)
  • Protected Resource Metadata discovery (RFC 9728)
  • Authorization Server Metadata discovery (RFC 8414)
  • Dynamic client registration (RFC 7591)
  • Automatic token refresh
  • OAuthState state machine + AuthClient transport

Currently Zeph uses rmcp with these features in crates/zeph-mcp/Cargo.toml:

rmcp = { workspace = true, features = ["client", "transport-child-process", "transport-streamable-http-client-reqwest"] }

Need to add auth feature and wire the OAuth flow.

Proposed Solution

1. Add auth feature to rmcp dependency

rmcp = { workspace = true, features = ["client", "transport-child-process", "transport-streamable-http-client-reqwest", "auth"] }

2. Extend config with two auth modes

Mode A — Static Bearer token (simple, for APIs with API keys like Todoist REST):

[[mcp.servers]]
id = "todoist"
url = "https://api.todoist.com/mcp/v1"
timeout = 30

[mcp.servers.headers]
Authorization = "Bearer ${TODOIST_API_TOKEN}"

Mode B — OAuth 2.1 (for servers like ai.todoist.net requiring browser auth):

[[mcp.servers]]
id = "todoist-oauth"
url = "https://ai.todoist.net/mcp"
timeout = 30

[mcp.servers.oauth]
enabled = true
token_storage = "vault"  # persist tokens in zeph vault across restarts

3. OAuth flow for non-interactive contexts (CLI/Telegram)

Since Zeph runs headless (CLI, Telegram bot), the OAuth flow needs to handle the browser redirect:

  • On first connect: print authorization URL to console / send via Telegram channel
  • Start local callback server on localhost:PORT to receive the code
  • Exchange code → tokens → store in vault
  • On subsequent starts: load tokens from vault, refresh automatically

4. Changes required

File Change
crates/zeph-mcp/Cargo.toml Add auth to rmcp features
crates/zeph-mcp/src/client.rs Add connect_url_with_headers() and connect_url_oauth()
crates/zeph-mcp/src/manager.rs Extend McpTransport::Http with optional headers and oauth fields
crates/zeph-core/src/config/ Add headers and oauth fields to McpServerConfig
crates/zeph-core/src/config/default.toml Document new fields
src/agent_setup.rs Handle OAuth token acquisition on startup

Acceptance Criteria

  • Static headers config works: token read from vault, passed as Bearer header
  • OAuth 2.1 flow works: authorization URL printed/sent, callback handled, tokens stored in vault
  • Token auto-refresh works across sessions
  • Todoist remote MCP (https://ai.todoist.net/mcp) connects successfully
  • CLI: authorization URL printed to stdout with instructions
  • Telegram: authorization URL sent as message to the user
  • TUI: authorization URL shown in status panel with spinner `Waiting for OAuth..."

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions