mcp2cli

Authentication

Manage server authentication with token persistence, interactive login, and automatic header injection.


Commands

Terminal window
# Login — starts browser OAuth for HTTP servers that advertise it
email auth login
# Login with an existing bearer token
echo "$TOKEN" | email auth login
# Login non-interactively with an existing bearer token
echo "$TOKEN" | email auth login --non-interactive
# Login with a structured payload
email auth login --input-json '{"bearer_token": "sk-abc123", "account": "me@example.com"}'
# Check current state
email auth status
# Clear stored credentials
email auth logout

auth login resolves credentials in this order:

  1. Piped stdinecho "$TOKEN" | email auth login.
  2. --input-json — a JSON object with the schema below.
  3. Browser OAuth — for streamable-HTTP configs, mcp2cli discovers the server’s OAuth metadata, dynamically registers a loopback client, starts authorization-code + PKCE, and stores the returned access token.

Pass --non-interactive to fail fast when no token is supplied via stdin or --input-json.

--input-json schema

{
"bearer_token": "<token>",
"account": "<optional>"
}
FieldRequiredMeaning
bearer_tokenThe token sent as Authorization: Bearer <token>.
accountOptional account label stored alongside the token.

How It Works

sequenceDiagram
    participant User
    participant CLI as mcp2cli
    participant Auth as OAuth Server
    participant Store as Token Store
    participant Server as MCP Server

    User->>CLI: email auth login
    CLI->>Server: Discover protected resource metadata
    CLI->>Auth: Register loopback OAuth client
    CLI->>User: Open authorization URL
    User->>Auth: Approve login in browser
    Auth->>CLI: Redirect to loopback callback with code
    CLI->>Auth: Exchange code + PKCE verifier
    Auth-->>CLI: Bearer access token
    CLI->>Store: Store token for "email"
    CLI-->>User: Authenticated ✓

    Note over User: Later...

    User->>CLI: email search --query "from:boss"
    CLI->>Store: Load token for "email"
    Store-->>CLI: sk-abc123
    CLI->>Server: POST /mcp<br/>Authorization: Bearer sk-abc123
    Server-->>CLI: Result
    CLI-->>User: Output

Token Storage

Tokens are persisted per-config at:

~/.local/share/mcp2cli/instances/<name>/tokens.json

The file is written with 0600 permissions (owner read/write only) and contains:

{
"bearer_token": "sk-abc123"
}

Custom Token Path

Override the default location:

auth:
token_store_file: /secure/path/tokens.json

Auth States

StateMeaning
unauthenticatedNo token stored
activeToken stored and being sent with requests

Check the current state:

Terminal window
email auth status
# → Auth state: active
email --json auth status | jq '.data.auth_session.state'
# → "active"

Transport Behavior

TransportAuth SupportHow
Streamable HTTPAuthorization: Bearer <token> injected on every request from the stored token
StdioSubprocess inherits environment; set env vars in config
DemoNo auth needed

The streamable-HTTP transport loads the stored token and attaches an Authorization: Bearer <token> header to every request automatically — no per-call flags needed. Both http and https endpoints are supported; HTTPS connections use TLS via rustls with the webpki root certificates, so a production endpoint like https://mcp.example.com/email works out of the box.

For stdio servers that need authentication, pass credentials via environment:

server:
transport: stdio
stdio:
command: my-server
env:
API_KEY: sk-abc123

Browser-Based OAuth

For streamable-HTTP servers that implement MCP OAuth discovery and dynamic client registration:

auth:
browser_open_command: "xdg-open" # Linux
# browser_open_command: "open" # macOS

auth login discovers /.well-known/oauth-protected-resource, reads the authorization server metadata, dynamically registers a loopback redirect URI, opens the authorization URL, and stores the resulting bearer token. The authorization request includes the MCP resource parameter and uses PKCE S256. The loopback callback validates the CSRF state on every request, ignores requests to any path other than the registered redirect URI (a stray probe on the ephemeral port can’t derail an in-flight login), and — per the MCP 2026-07-28 authorization hardening (RFC 9207 / SEP-2468) — rejects a present iss parameter that doesn’t match the discovered issuer.

If you already have a bearer token, pipe it or pass --input-json; this bypasses browser OAuth and stores the token directly. auth login only reads stdin when you actually pipe something to it — running from a script, CI runner, or IDE terminal with nothing piped falls straight through to browser OAuth rather than blocking.


See Also