Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 67 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ opt-level = 3
[dependencies]
anyhow = "1.0.98"
async-trait = "0.1.89"
base64 = "0.22"
bytes = "1.10.1"
chrono = { version = "0.4.42", features = ["clock", "serde"] }
clap = { version = "4.5.38", features = ["derive", "string"] }
Expand All @@ -55,6 +56,7 @@ hyper-rustls = { version = "0.27", default-features = false, features = ["http1"
serde = { version = "1.0.228", features = ["derive"] }
serde_json = "1.0.145"
serde_yaml = "0.9.34"
sha2 = "0.10"
thiserror = "2.0.12"
tokio = { version = "1.49.0", features = ["full"] }
tracing = "0.1.41"
Expand All @@ -66,4 +68,4 @@ uuid = { version = "1.18.1", features = ["serde", "v4"] }
[dev-dependencies]
assert_cmd = "2.0"
predicates = "3.1"
tempfile = "3.20.0"
tempfile = "3.20.0"
34 changes: 25 additions & 9 deletions docs/features/authentication.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,13 @@ Manage server authentication with token persistence, interactive login, and auto
## Commands

```bash
# Login — reads the bearer token from stdin, then falls back to a prompt
# 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 (fails fast instead of prompting if no token is piped)
# Login non-interactively with an existing bearer token
echo "$TOKEN" | email auth login --non-interactive

# Login with a structured payload
Expand All @@ -23,13 +26,13 @@ email auth status
email auth logout
```

`auth login` obtains the bearer token from one of three sources, in order:
`auth login` resolves credentials in this order:

1. **Piped stdin** — `echo "$TOKEN" | email auth login`.
2. **`--input-json`** — a JSON object with the schema below.
3. **Interactive prompt** — used only when neither of the above provides a token.
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 skip the prompt and fail fast (useful in CI and scripts) when no token is supplied via stdin or `--input-json`.
Pass `--non-interactive` to fail fast when no token is supplied via stdin or `--input-json`.

### `--input-json` schema

Expand All @@ -53,12 +56,18 @@ Pass `--non-interactive` to skip the prompt and fail fast (useful in CI and scri
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->>User: Enter token:
User->>CLI: sk-abc123
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 ✓

Expand Down Expand Up @@ -149,15 +158,22 @@ server:

## Browser-Based OAuth

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

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

When the server sends an `elicitation/create` with a URL during auth, mcp2cli opens the browser automatically.
`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.

If you already have a bearer token, pipe it or pass `--input-json`; this bypasses
browser OAuth and stores the token directly.

---

Expand Down
2 changes: 1 addition & 1 deletion docs/protocol-coverage.md
Original file line number Diff line number Diff line change
Expand Up @@ -324,7 +324,7 @@ See [`docs/features/transports.md`](features/transports.md) and [`docs/features/
## Known gaps

- **Pagination cursors.** Spec-defined `nextCursor` on `*/list` responses is not yet consumed — mcp2cli issues a single `list` request per primitive and treats the first page as the full inventory. Will matter for servers with very large tool/resource catalogs.
- **Authorization (OAuth 2.1) flows.** `auth login` supports bearer-token capture and the stored-token lifecycle; end-to-end OAuth authorization-code with PKCE is partial — see [`docs/features/authentication.md`](features/authentication.md) for the current matrix.
- **Authorization (OAuth 2.1) flows.** `auth login` supports bearer-token capture plus authorization-code + PKCE with dynamic client registration for streamable-HTTP servers. Remaining gaps include client ID metadata documents, pre-registered client config, refresh-token rotation, and runtime step-up authorization — see [`docs/features/authentication.md`](features/authentication.md) for the current matrix.
- **Multi-root `notifications/roots/list_changed` debouncing.** Clients may spam the server if root config is hot-reloaded in a tight loop; there is no built-in debounce window.

Found a gap not listed here? File an issue — the intent is to track spec coverage accurately.
9 changes: 5 additions & 4 deletions docs/reference/cli-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -303,15 +303,16 @@ email get mail://thread/123
## Auth Commands

```bash
email auth login # Prompt for a bearer token (or read it from stdin)
email auth login # Start browser OAuth, or store a supplied bearer token
email auth logout # Clear stored credentials
email auth status # Show current auth state
```

`auth login` reads a bearer token interactively, from a piped stdin
(`echo "$TOKEN" | email auth login`), or from
For streamable-HTTP configs, `auth login` starts OAuth authorization-code + PKCE
when no token is supplied. Existing bearer-token workflows still work via piped
stdin (`echo "$TOKEN" | email auth login`) or
`--input-json '{"bearer_token": "<token>"}'`. With `--non-interactive` and no
token available it fails fast instead of prompting.
token available it fails fast.

---

Expand Down
9 changes: 6 additions & 3 deletions docs/usage-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -509,10 +509,13 @@ credentials.
email auth login
```

For **real servers** (non-demo), this prompts for a bearer token via stdin:
For streamable-HTTP servers that advertise MCP OAuth metadata, this starts a
browser authorization-code + PKCE flow and stores the returned bearer token.

```text
enter bearer token for email: <paste token>
If you already have a bearer token, pipe it instead:

```bash
echo "$TOKEN" | email auth login
```

The token is persisted in a file-backed token store at
Expand Down
Loading