docs: document headless OAuth auth flow

This commit is contained in:
Sebastian Otaegui 2026-05-12 23:59:45 -03:00
parent b29854ebf2
commit 7ddb433479
No known key found for this signature in database
6 changed files with 41 additions and 7 deletions

View File

@ -164,7 +164,8 @@ Helpful flags:
- `--` (on `mcporter call`) -- stop flag parsing so the remaining tokens stay literal positional values, even when they start with `--`.
- `--json` (on `mcporter list`) -- emit JSON summaries/counts instead of text. Multi-server runs report per-server statuses, counts, and connection issues; single-server runs include the full tool metadata.
- `--output json/raw` (on `mcporter call`) -- when a connection fails, MCPorter prints the usual colorized hint and also emits a structured `{ server, tool, issue }` envelope so scripts can handle auth/offline/http errors programmatically.
- `--json` (on `mcporter auth`) -- emit the same structured connection envelope whenever OAuth/transport setup fails, instead of throwing an error.
- `--json` (on `mcporter auth`) -- emit the same structured connection envelope whenever OAuth/transport setup fails, instead of throwing an error. With `--no-browser`, it emits auth-start JSON containing `authorizationUrl` and `redirectUrl`.
- `--no-browser` / `--browser none` (on `mcporter auth` or `mcporter config login`) -- suppress browser launch and print the OAuth authorization URL for headless workflows; `MCPORTER_OAUTH_NO_BROWSER=1` / `true` / `yes` enables the same behavior.
- `--json` (on `mcporter emit-ts`) -- print a JSON summary describing the emitted files (mode + output paths) instead of text logs—handy when generating artifacts inside scripts.
- `--all-parameters` -- show every schema field when listing a server (default output shows at least five parameters plus a summary of the rest).
- `--http-url <https://…>` / `--stdio "command …"` -- describe an ad-hoc MCP server inline. STDIO transports now inherit your current shell environment automatically; add `--env KEY=value` only when you need to inject/override variables alongside `--cwd`, `--name`, or `--persist <config.json>`. These flags now work with `mcporter auth` too, so `mcporter auth https://mcp.example.com/mcp` just works.
@ -415,6 +416,8 @@ npx mcporter config add notion https://mcp.notion.com/mcp --auth oauth
npx mcporter auth notion
```
On headless hosts, use `npx mcporter auth notion --no-browser` to print the authorization URL instead of launching the platform browser. Treat the printed URL as sensitive operational output. If you open it on another machine, make sure the printed `redirectUrl` callback port is reachable through a loopback-only tunnel or a configured `oauthRedirectUrl`.
Providers that do not support dynamic client registration can use a pre-registered app:
```jsonc

View File

@ -53,7 +53,7 @@ This name becomes the cache key for OAuth tokens and log preferences, so repeate
Many hosted MCP servers (Supabase, Vercel, etc.) advertise OAuth capabilities but expect clients to discover this dynamically. When an ad-hoc HTTP server responds with `401/403` during the initial handshake, mcporter now:
1. **Promotes the definition to OAuth** and spins up the default browser flow—no need to edit config or supply `auth: "oauth"` manually.
1. **Promotes the definition to OAuth** and spins up the default browser flow—no need to edit config or supply `auth: "oauth"` manually. On headless hosts, pass `--no-browser` (or `--browser none`) to print the authorization URL instead of launching the platform browser.
2. **Persists the change** whenever you pass `--persist`, so future runs remember that the endpoint requires OAuth without repeating the detection step.
The CLI still avoids surprise prompts during `mcporter list`; the upgrade happens the first time you run `mcporter auth <url>` or any other command that allows OAuth (i.e., not in `--autoAuthorize=false` mode).
@ -62,6 +62,8 @@ The CLI still avoids surprise prompts during `mcporter list`; the upgrade happen
- OAuth flows are allowed; successful tokens store under the inferred name just like regular definitions.
- `mcporter auth` accepts the same `--http-url/--stdio` flags (and even bare URLs), so you can immediately re-run `mcporter auth https://…` after a 401 without touching a config file.
- Use `mcporter auth <url> --no-browser` for human-in-the-loop headless OAuth. Text mode writes only the authorization URL to stdout; `--json --no-browser` writes `authorizationUrl` plus `redirectUrl` as JSON. Keep that URL out of durable CI logs and support bundles.
- When opening the URL on a different machine, remember that loopback redirect URLs point at the browser machine unless an SSH tunnel forwards the callback port back to the mcporter process. Use `oauthRedirectUrl` when you need a predictable callback port.
- Nothing is written to disk unless you pass `--persist /path/to/config.json`. When set, we merge the generated definition into that file (creating it if necessary) so future runs can rely on the standard config pipeline. Ad-hoc HTTP headers are persisted with the entry, so placeholders such as `--header 'Authorization=$env:MY_TOKEN'` keep working through the normal config header resolver.
## Safety Nets

View File

@ -148,7 +148,8 @@ Use `--scope home|project` with `mcporter config add` to pick the write target e
### `mcporter config login <name|url>` / `logout`
- Mirrors `mcporter auth`. `login` completes OAuth (or token provisioning) for either a named server or an ad-hoc URL. When a hosted MCP returns 401/403, mcporter automatically promotes that target to OAuth and re-runs the flow, matching the behavior documented in `docs/adhoc.md`.
- `--browser none` suppresses automatic browser launch (useful for copying the URL into a remote browser).
- `--no-browser` suppresses automatic browser launch and prints the authorization URL to stdout so it can be copied from a headless host. `--browser none` is accepted as a compatibility alias, and `MCPORTER_OAUTH_NO_BROWSER=1` / `true` / `yes` enables the same behavior by environment.
- In `--json --no-browser` mode, stdout contains a JSON object with `authorizationUrl` and `redirectUrl`; diagnostics stay off stdout so scripts can parse the result. Treat emitted authorization URLs as sensitive operational output.
- `logout` wipes the shared vault entry, legacy `~/.mcporter/<name>/` caches, and the custom `tokenCacheDir` when present. Pass `--all` to clear everything.
### `mcporter config doctor`

View File

@ -74,6 +74,13 @@ Expectations:
- If a token cache exists, log should mention the cleared directory.
- Failed auths emit the unified message (`Failed to authorize 'SERVER': ...`).
For headless OAuth URL capture, run the same auth command with `--no-browser`:
- Text mode stdout should contain exactly one authorization URL line and no logger prefix.
- `--json --no-browser` stdout should parse as JSON with `authorizationUrl` and `redirectUrl`.
- If completing the flow from another machine over SSH, forward the printed callback port with a loopback-only tunnel; avoid exposing the callback listener publicly.
- Treat copied authorization URLs as sensitive and avoid storing them in long-lived logs.
## Tips
- To exercise error paths, point at a placeholder endpoint and use `--timeout 1000` (e.g., `https://example.com/mcp.listStuff`).

View File

@ -98,8 +98,21 @@ export const CONFIG_HELP_ENTRIES: Record<ConfigSubcommand, ConfigHelpEntry> = {
name: 'login <name|url> [options]',
summary: 'Run the OAuth/auth flow',
usage: 'mcporter config login <name|url> [options]',
description: 'Delegates to `mcporter auth`, so you can pass ephemeral flags like --http-url/--stdio/--reset.',
examples: ['pnpm mcporter config login linear', 'pnpm mcporter config login https://example.com/mcp --reset'],
description:
'Delegates to `mcporter auth`, so you can pass ephemeral flags like --http-url/--stdio/--reset and browser-suppression flags for headless OAuth.',
flags: [
{ flag: '--no-browser', description: 'Print the OAuth authorization URL without launching a browser.' },
{ flag: '--browser none', description: 'Alias for --no-browser.' },
{
flag: 'MCPORTER_OAUTH_NO_BROWSER=1|true|yes',
description: 'Environment default for browser-suppressed OAuth.',
},
],
examples: [
'pnpm mcporter config login linear',
'pnpm mcporter config login linear --no-browser',
'pnpm mcporter config login https://example.com/mcp --reset',
],
},
logout: {
name: 'logout <name>',

View File

@ -27,7 +27,11 @@ describe('mcporter auth help shortcut', () => {
await runCli(['auth', '--help']);
expect(errorSpy).toHaveBeenCalledWith(expect.stringContaining('Usage: mcporter auth'));
const output = errorSpy.mock.calls.map(([message]) => String(message)).join('\n');
expect(output).toContain('Usage: mcporter auth');
expect(output).toContain('--no-browser');
expect(output).toContain('--browser none');
expect(output).toContain('MCPORTER_OAUTH_NO_BROWSER');
expect(process.exitCode).toBe(0);
});
@ -37,7 +41,11 @@ describe('mcporter auth help shortcut', () => {
await runCli(['auth', 'help']);
expect(errorSpy).toHaveBeenCalledWith(expect.stringContaining('Usage: mcporter auth'));
const output = errorSpy.mock.calls.map(([message]) => String(message)).join('\n');
expect(output).toContain('Usage: mcporter auth');
expect(output).toContain('--no-browser');
expect(output).toContain('--browser none');
expect(output).toContain('MCPORTER_OAUTH_NO_BROWSER');
expect(process.exitCode).toBe(0);
});
});