Source Intelligence
Sweep 28 Aug 2026 ยท 00:00Z Build v2.1.250 478 read Stable v2.1.236 Latest v2.1.250 Next v2.1.250 Feeds RSS JSON llms.txt

DisclaimerUnofficial, and not affiliated with Anthropic. Nearly all of this is read straight out of what ships: npm bundles, captured prompts, published docs. Anthropic's own notes go in verbatim, marked as theirs. The rest is my reading, and every entry carries the strings behind it. If one looks wrong, vote it down and say why.

One change

Authorization

docs/draft/tools/inspector/authorization

The page's own history The capture it came from

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.