Authorization
docs/draft/tools/inspector/authorization
Nearest release: v2.1.246, published 8 hours before this site recorded the change. Shown because the two are within 24 hours of each other. Nothing here says the release caused the edit.
docs/draft/tools/inspector/authorization New page · 159 lines, new page
# Authorization ## The flow, end to end ## Callback URLs ## Where credentials live ## Mid-session re-authorization ## Non-interactive and CI runs ## Handing off from the web client to the CLI ## Inspecting auth state
A whole new page. There's nothing to diff it against, so here is what it says.
# Authorization
> How the MCP Inspector performs OAuth, re-authorizes mid-session, and shares tokens between its clients
Remote MCP servers usually require authorization. The Inspector implements the full [authorization](/specification/latest/basic/authorization) flow in all three clients, sharing the resulting tokens on disk so a login done once is usable everywhere.
## The flow, end to end
<Steps>
<Step title="Connect, and get refused">
The Inspector connects to the server URL. The server answers `401`. When the
response carries a `WWW-Authenticate` header, it points at the
protected-resource metadata URL (`resource_metadata`) and, optionally, the
scopes the request requires.
</Step>
<Step title="Discover the authorization server">
The Inspector fetches the server's [protected-resource and
authorization-server
metadata](/specification/latest/basic/authorization/authorization-server-discovery)
to learn the endpoints and the supported grants.
</Step>
<Step title="Register or identify the client">
The Inspector identifies itself to the authorization server through
whichever mechanism is configured: [dynamic client
registration](/specification/latest/basic/authorization/client-registration#dynamic-client-registration),
a pre-registered static client (`--client-id` / `--client-secret`), a
[Client ID Metadata
Document](/specification/latest/basic/authorization/client-registration#client-id-metadata-documents)
(`--client-metadata-url`), or an [enterprise-managed
IdP](/extensions/auth/enterprise-managed-authorization).
</Step>
<Step title="Authorize in the browser">
The Inspector opens the authorization URL. You sign in and consent.
</Step>
<Step title="Receive the callback">
The authorization server redirects to the Inspector's callback URL, carrying
the authorization code.
</Step>
<Step title="Exchange and retry">
The code is exchanged for tokens, the tokens are persisted, and the original
connect (or, for a [mid-session challenge](#mid-session-re-authorization),
the request that was refused) is retried automatically.
</Step>
</Steps>
<Frame caption="Connection Info after a completed OAuth flow: the authorization status, the dynamically registered client, and the granted scopes.">
<img src="https://mintcdn.com/mcp/gk28X8wi_tbRYzej/images/inspector/auth-connection-info.png?fit=max&auto=format&n=gk28X8wi_tbRYzej&q=85&s=01a8d11058c2d33e0069b8dec98591b5" width="3840" height="2160" data-path="images/inspector/auth-connection-info.png" />
</Frame>
## Callback URLs
The web app listens for the OAuth callback on its own URL, while the CLI and TUI deliberately share a second one:
| Surface | Default callback | Why |
| ------- | -------------------------------------- | ---------------------------------------------------------------------------------- |
| **Web** | `http://localhost:6274/oauth/callback` | The main app server already has an HTTP listener. |
| **CLI** | `http://127.0.0.1:6276/oauth/callback` | A dedicated loopback listener, so it doesn't collide with a running web Inspector. |
| **TUI** | `http://127.0.0.1:6276/oauth/callback` | The same listener as the CLI. |
**Register `http://127.0.0.1:6276/oauth/callback`** on any IdP that requires pre-registered redirect URIs before using the CLI or TUI. A predictable default is the point: you register once and reuse it.
Override with `--callback-url` or `MCP_OAUTH_CALLBACK_URL`.
<Warning>
The callback URL **must bind a loopback host**: `localhost`, `127.0.0.0/8`, or
`[::1]`. The listener receives the authorization code over plaintext `http`,
so a non-loopback host is rejected with an error and there is no flag to
override that. If your browser runs on a different machine, forward the
callback port to it; `--print-handoff` (below) prints a ready-made
`portForwardCmd`.
</Warning>
<Note>
Redirect URIs must match your registration **exactly**. `http://localhost:6276/...` and `http://127.0.0.1:6276/...` are different URIs to an authorization server, even though they reach the same listener.
Only one process can hold the default port at a time; a second concurrent flow fails with `EADDRINUSE`. Use a different fixed port per instance, or `http://127.0.0.1:0/oauth/callback` for an OS-assigned ephemeral port when your authorization server supports dynamic redirect-URI registration.
</Note>
## Where credentials live
| File | Contents |
| --------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
| `~/.mcp-inspector/storage/oauth.json` | Tokens and client information, keyed by canonicalized server URL. Written owner-only. |
| `~/.mcp-inspector/storage/client.json` | Install-level client settings (client metadata URL, enterprise IdP). The same file the web client's **Client Settings** dialog writes. |
| The server's `oauth` block in the [catalog file](/docs/draft/tools/inspector/configuration#catalog-file-format) | Per-server client id/secret, scopes, the enterprise-managed flag, and the [step-up](#mid-session-re-authorization) policy. |
The path to `oauth.json` is resolved in order: `MCP_INSPECTOR_OAUTH_STATE_PATH`, then `<MCP_STORAGE_DIR>/oauth.json` (see [Environment variables](/docs/draft/tools/inspector/configuration#environment-variables)), then the default above. All three clients resolve it the same way. Command-line `--client-id` / `--client-secret` / `--client-metadata-url` override `client.json`.
## Mid-session re-authorization
A server can refuse a *single* request mid-session with a `401` or a `403 insufficient_scope`, and the Inspector handles both without dropping the connection:
* **Re-authorization**: the token expired or was revoked. The Inspector parses the `WWW-Authenticate` challenge and re-runs the flow, then retries the failed request.
* **Step-up**: the request needs scopes the current token doesn't carry. The Inspector re-authorizes for the union of the held and required scopes, so the new token covers everything the old one did plus the newly required scopes.
In the **web** client this surfaces as a re-authorization banner. In the **CLI** it prompts on stderr:
```
Proceed with step-up authorization? [y/N]
```
Answer **y** to continue. Piped input works (`echo y | ...`), as long as it's newline-terminated or stdin closes. **N**, or EOF with no answer, declines. A non-TTY stdin that sends nothing within 5 seconds fails with `auth_required`, which is distinct from an explicit decline. Enterprise-managed step-up re-mints silently, with no prompt.
## Non-interactive and CI runs
Interactive OAuth requires a TTY on **stdin or stderr**, or [`MCP_AUTO_OPEN_ENABLED=true`](/docs/draft/tools/inspector/configuration#environment-variables). Redirecting stderr into a pipe, as in `2>&1 | tee`, still works because stdin stays a TTY. When neither is true, which is the normal CI shape, the CLI fails fast with `auth_required` rather than waiting up to fifteen minutes for a callback nobody will complete.
For CI, be explicit:
```bash theme={null}
mcp-inspector --cli "$URL" --transport http --stored-auth-only --method tools/list
```
`--stored-auth-only` never starts interactive OAuth or step-up, never opens a browser, uses the shared store if a token is there, and fails immediately otherwise.
## Handing off from the web client to the CLI
The common case: a human completed OAuth in the web Inspector on this machine, and now a script wants to use that token.
| Flag | Behavior |
| ----------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `--use-stored-auth` | Read the stored auth for `--server-url` and inject `Authorization: Bearer`. When a refresh token is stored, run the refresh grant first and inject the **fresh** token, persisting the rotation. Exits `3` (listing the stored server URLs) when nothing matches. |
| `--wait-for-auth <sec>` | Poll the state file until a token for `--server-url` appears, then inject it. Times out at `<sec>` with exit `3`. Use after handing a login off to a human. |
| `--list-stored-auth` | Print `{ oauthStatePath, storedServerUrls }` and exit without connecting. |
| `--print-handoff` | Print a JSON block (`deepLink`, `portForwardCmd`, `oauthStatePath`, `apiToken`) for `--server-url` and exit; this is everything a remote script needs to drive the browser side. |
| `--relogin` | Delete the stored OAuth for this server URL before connecting. HTTP/SSE only. |
A typical remote-VM sequence:
```bash theme={null}
# On the VM: print what the human needs in order to complete OAuth in their browser
mcp-inspector --cli --server-url https://api.example/mcp --print-handoff
# Then block until the token lands, and run the call with it
mcp-inspector --cli --transport http --server-url https://api.example/mcp \
--wait-for-auth 120 --method tools/list
```
The `deepLink` in the handoff block navigates a browser straight to a *connected* Inspector; see [Deep links](/docs/draft/tools/inspector/web#deep-links).
<Note>
Because the stored entry records no expiry, a stored refresh token is
exercised on **every** `--use-stored-auth` run. With rotating (single-use)
refresh tokens that opens two narrow failure windows: two concurrent
invocations against the same state file can race for the token, and a crash
between a successful refresh and the write-back leaves the rotated token
unsaved. Both are unlikely; re-authorize in the web client to recover.
</Note>
## Inspecting auth state
* **Web**: the Connection Info panel shows discovery results, the registered client, granted scopes, and token state, and offers **Clear OAuth state** for the active server.
* **TUI**: the **Auth** tab (`a`) shows the same fields and clears state the same way.
* **CLI**: `--list-stored-auth` shows what's on disk, and `--relogin` discards it and starts over.