docs: add the v1 → v2 migration guide
Closes #1822 Adds docs/v1-to-v2-migration.md — the map for users arriving from v1: - CLI flag mapping (every v1 flag survives; lists what v2 adds) - `--config` vs `--catalog` semantics with before/after examples - Node engine bump (>=22.7.5 → >=22.19.0) - what no longer ships (the three sub-packages; v2 is one tarball) - the three behavior changes most likely to bite: exit code 5 on a failing tool call, target-must-come-first under --cli, and the reversed `--` separator - env-var mapping (MCP_PROXY_AUTH_TOKEN → MCP_INSPECTOR_API_TOKEN, MCP_PROXY_FULL_ADDRESS removed, SERVER_PORT repurposed), the v1 UI config settings, web query params, and Docker Linked from the root README (banner + docs list), the CLI README, and docs/mcp-server-configuration.md; AGENTS.md's docs/ tree entry updated. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SW1p8E2uiyyLx4RKwrwSrt
This commit is contained in:
parent
a09f2dcf1d
commit
2e89443739
1 changed files with 469 additions and 128 deletions
325
docs/v1-to-v2-migration.md
Normal file
325
docs/v1-to-v2-migration.md
Normal file
|
|
@ -0,0 +1,325 @@
|
|||
# Migrating from Inspector v1 to v2
|
||||
|
||||
v2 is a rewrite. The package name and the `npx @modelcontextprotocol/inspector` entry point are unchanged, but the architecture, the CLI surface, and the way you point the Inspector at a server all changed.
|
||||
|
||||
This guide is the map. It covers what breaks, what it becomes, and the handful of places where a v1 command still runs in v2 but means something different — the ones worth reading before you conclude v2 is broken.
|
||||
|
||||
> **v1 is deprecated** and receives security fixes only, published to the `v1-latest` npm dist-tag. To stay on it, pin it explicitly:
|
||||
>
|
||||
> ```bash
|
||||
> npx @modelcontextprotocol/inspector@v1-latest
|
||||
> ```
|
||||
|
||||
## At a glance
|
||||
|
||||
| | v1 | v2 |
|
||||
| ----------------- | ------------------------------------------------ | --------------------------------------------------------------- |
|
||||
| Node | `>=22.7.5` | **`>=22.19.0`** |
|
||||
| npm packages | 4 (`inspector` + `-client` / `-server` / `-cli`) | **1** (`@modelcontextprotocol/inspector`) |
|
||||
| Processes / ports | web UI on `6274` **+ MCP proxy on `6277`** | one web server on `6274` (plus a dynamic MCP Apps sandbox port) |
|
||||
| Modes | web, `--cli` | web, `--cli`, **`--tui`** |
|
||||
| Server list | per-browser, in `localStorage` | a **catalog file** on disk (`~/.mcp-inspector/mcp.json`) |
|
||||
| Auth token env | `MCP_PROXY_AUTH_TOKEN` | **`MCP_INSPECTOR_API_TOKEN`** |
|
||||
| CLI exit codes | `0` / `1` | `0`–`5`, plus a JSON error envelope on stderr |
|
||||
|
||||
The three changes most likely to bite an existing setup:
|
||||
|
||||
1. **`--config` no longer means what it did** — see [`--config` vs `--catalog`](#--config-vs---catalog).
|
||||
2. **A failing tool call now exits non-zero** (`5`), where v1 exited `0`. CI scripts that ran `&&` chains past a failed call will start failing — correctly.
|
||||
3. **`SERVER_PORT` was the proxy port and now isn't** — it survives as a fallback for the MCP Apps sandbox port. Setting it no longer moves anything you can browse.
|
||||
|
||||
## Requirements
|
||||
|
||||
v2 requires **Node `>=22.19.0`** (v1 required `>=22.7.5`). The floor comes from `undici@^8`, used for HTTP proxy support. `npx` will refuse to run on an older Node rather than fail obscurely later.
|
||||
|
||||
## What no longer ships
|
||||
|
||||
v1 published four packages:
|
||||
|
||||
- `@modelcontextprotocol/inspector`
|
||||
- `@modelcontextprotocol/inspector-client`
|
||||
- `@modelcontextprotocol/inspector-server`
|
||||
- `@modelcontextprotocol/inspector-cli`
|
||||
|
||||
**v2 publishes only the first.** It is a single tarball with a single version number, containing the web client, the CLI, the TUI, and the launcher that dispatches between them. The three sub-packages are frozen at `1.0.1`, deprecated on npm, and will never see a 2.x.
|
||||
|
||||
If you depend on one of them directly, drop it and use the root package's `mcp-inspector` bin. There is no v2 equivalent of importing `inspector-server` as a library; the shared runtime lives in this repo's `core/` and is not published separately yet ([#1636](https://github.com/modelcontextprotocol/inspector/issues/1636)).
|
||||
|
||||
```diff
|
||||
{
|
||||
"devDependencies": {
|
||||
- "@modelcontextprotocol/inspector-cli": "^1.0.1"
|
||||
+ "@modelcontextprotocol/inspector": "^2.0.0"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Architecture: the proxy is gone
|
||||
|
||||
v1 ran **two** processes: a React client on `6274` and an "MCP Proxy" on `6277` that held the actual MCP connections. The browser talked to the proxy over HTTP, authenticating with `MCP_PROXY_AUTH_TOKEN`.
|
||||
|
||||
v2 runs **one** web server on `6274`. It serves the SPA and exposes `/api/*`, which the browser calls with `x-mcp-remote-auth: Bearer <MCP_INSPECTOR_API_TOKEN>`. There is no second port to expose, forward, or allow through a firewall.
|
||||
|
||||
Consequences:
|
||||
|
||||
- `SERVER_PORT` no longer selects a port you browse. (It is read as a fallback for the MCP Apps sandbox server's port — see `MCP_SANDBOX_PORT` below.)
|
||||
- `MCP_PROXY_FULL_ADDRESS` has no v2 equivalent and is ignored. It existed to tell the browser where a non-default proxy lived; there is no proxy.
|
||||
- Docker only needs `-p 6274:6274`. The v1 recipe published `6277` as well.
|
||||
|
||||
## Launching
|
||||
|
||||
```bash
|
||||
npx @modelcontextprotocol/inspector # web UI (unchanged default)
|
||||
npx @modelcontextprotocol/inspector --cli # CLI
|
||||
npx @modelcontextprotocol/inspector --tui # TUI (new in v2)
|
||||
```
|
||||
|
||||
The mode flag must come **first**, immediately after the binary name; everything after it is forwarded to that client unchanged.
|
||||
|
||||
Passing a server ad-hoc works the same way it did:
|
||||
|
||||
```bash
|
||||
npx @modelcontextprotocol/inspector node build/index.js
|
||||
npx @modelcontextprotocol/inspector -e KEY=value -- node build/index.js --server-flag
|
||||
```
|
||||
|
||||
## `--config` vs `--catalog`
|
||||
|
||||
**This is the change most likely to surprise you.** Both versions have a `--config` flag, both take an `mcpServers` file, and they do not mean the same thing.
|
||||
|
||||
In v1, `--config path.json --server name` resolved one entry out of the file and launched against it. The file was read once and never written — but there was no other file, because v1's web UI kept its server list in browser `localStorage`.
|
||||
|
||||
v2 has a first-class **server catalog** on disk, and splits the two roles:
|
||||
|
||||
| | `--catalog <path>` | `--config <path>` |
|
||||
| ------------------------- | -------------------------------------------------- | -------------------------------------------------------- |
|
||||
| Writable by the Inspector | **Yes** — this is the Inspector's own list | **No.** Served as-is; never written, seeded, or migrated |
|
||||
| When the file is missing | created and seeded | **errors** |
|
||||
| Default path | `~/.mcp-inspector/mcp.json`, or `MCP_CATALOG_PATH` | none — must be passed |
|
||||
| Editable in the web UI | yes | no (catalog CRUD is hidden) |
|
||||
|
||||
So:
|
||||
|
||||
- **`--config` in v2 is the read-only role.** Point it at a file you didn't write — a coworker's, a client application's, one checked into a repo — and the Inspector guarantees it won't touch the bytes, including any plaintext secrets in them. This is close to v1's behavior, minus the write-back that never existed anyway.
|
||||
- **`--catalog` is new** and is what you want if you'd like the web UI to add, edit, and remove servers.
|
||||
- **Neither is required.** With no source flag, v2 uses the default catalog `~/.mcp-inspector/mcp.json`, creating it if absent. v1 had no such default — it started empty every time.
|
||||
|
||||
### Before / after
|
||||
|
||||
**Your v1 file works unchanged.** The format is the same `mcpServers` shape, `type: "streamable-http"` is still accepted (as is the `"http"` alias), and unknown fields such as v1's exported `note` are carried through rather than rejected.
|
||||
|
||||
```bash
|
||||
# v1 — one entry out of a file
|
||||
npx @modelcontextprotocol/inspector --config ./mcp.json --server everything
|
||||
|
||||
# v2 — same thing, same read-only guarantee
|
||||
npx @modelcontextprotocol/inspector --config ./mcp.json --server everything
|
||||
```
|
||||
|
||||
⚠️ One caveat on that last line: **`--server` only selects under `--cli`.** On the web client it is a no-op that logs a warning, and the TUI rejects it as an unknown option. In the web UI you pick the server from the list after it loads.
|
||||
|
||||
```bash
|
||||
# v2 — let the Inspector manage the file (web UI can edit it)
|
||||
npx @modelcontextprotocol/inspector --catalog ./mcp.json
|
||||
|
||||
# v2 — the default catalog, no flags at all
|
||||
npx @modelcontextprotocol/inspector
|
||||
```
|
||||
|
||||
**Rules:** `--catalog` and `--config` are mutually exclusive, and neither combines with an ad-hoc target (a positional command, `--server-url`, or `--transport`). v1 silently preferred the config file; v2 tells you.
|
||||
|
||||
If you export `MCP_CATALOG_PATH` in your shell, note that **web and TUI read it unconditionally** — so an ad-hoc invocation such as `mcp-inspector --tui node build/index.js` is rejected as a catalog/ad-hoc conflict. Unset it for that invocation. The CLI ignores the variable whenever an ad-hoc target is present.
|
||||
|
||||
The full model, including the Inspector-specific per-server fields v2 adds (`protocolEra`, `roots`, `requestTimeout`, `oauth`, …), is documented in [MCP server configuration](./mcp-server-configuration.md).
|
||||
|
||||
## CLI flag mapping
|
||||
|
||||
Every v1 CLI flag still exists in v2 and means the same thing. Nothing was renamed or removed:
|
||||
|
||||
| v1 flag | v2 | Notes |
|
||||
| -------------------------------- | ------------------------------- | ------------------------------------------------------------------------------------------------- |
|
||||
| `--method <method>` | same | v2 adds `initialize`, `servers/list`, `servers/show`; stream-only methods are rejected explicitly |
|
||||
| `--tool-name <name>` | same | |
|
||||
| `--tool-arg <k=v>` | same | |
|
||||
| `--uri <uri>` | same | |
|
||||
| `--prompt-name <name>` | same | |
|
||||
| `--prompt-args <k=v>` | same | |
|
||||
| `--log-level <level>` | same | |
|
||||
| `--transport <sse\|http\|stdio>` | same | |
|
||||
| `--server-url <url>` | same | |
|
||||
| `--header "Name: Value"` | same | |
|
||||
| `--metadata <k=v>` | same | |
|
||||
| `--tool-metadata <k=v>` | same | |
|
||||
| `-e KEY=VALUE` | same | |
|
||||
| `--config <path>` | same flag, **narrower meaning** | see [above](#--config-vs---catalog) |
|
||||
| `--server <name>` | same | now `--cli`-only |
|
||||
| `[target...]` | same | but see the target-ordering rule below |
|
||||
|
||||
New in v2, with no v1 equivalent:
|
||||
|
||||
| Flag | What it does |
|
||||
| ----------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------- |
|
||||
| `--catalog <path>` | writable catalog file (see above) |
|
||||
| `--cwd <path>` | working directory for a stdio server process |
|
||||
| `--tool-args-json '<json>'` | tool arguments as one JSON object, passed verbatim (no `key=value` coercion, so `"012"` stays a string) |
|
||||
| `--format text\|json` | `json` emits a single `{"result":…}` object on stdout with no banners |
|
||||
| `--app-info` | probe a tool's MCP App UI metadata without invoking the tool |
|
||||
| `--connect-timeout <ms>` | connect timeout; defaults to `15000` for ad-hoc targets so a black-holed host fails fast |
|
||||
| `--client-id` / `--client-secret` / `--client-metadata-url` / `--client-config` | OAuth client configuration |
|
||||
| `--callback-url <url>` | OAuth loopback redirect URI (default `http://127.0.0.1:6276/oauth/callback`) |
|
||||
| `--relogin` / `--stored-auth-only` / `--use-stored-auth` / `--wait-for-auth` / `--list-stored-auth` / `--print-handoff` | OAuth token reuse and web→CLI handoff |
|
||||
|
||||
v1 had no OAuth support in the CLI at all; a protected server simply failed. If you script against protected servers, `--stored-auth-only` is the flag to reach for in CI — it never opens a browser and fails fast with `auth_required` instead of hanging on a loopback callback.
|
||||
|
||||
See the [CLI README](../clients/cli/README.md) for the full surface.
|
||||
|
||||
## CLI behavior changes
|
||||
|
||||
The flags survived; three behaviors did not.
|
||||
|
||||
### 1. Exit codes and the error envelope
|
||||
|
||||
v1 exited `0` on success and `1` on any failure — including a `tools/call` that came back `isError: true`, which exited `0` because the _call_ succeeded.
|
||||
|
||||
v2 maps failures onto a stable set:
|
||||
|
||||
| Code | Meaning |
|
||||
| ---- | ------------------------------------------------------- |
|
||||
| `0` | Success |
|
||||
| `1` | Usage / unexpected error |
|
||||
| `2` | No MCP App on the tool (`--app-info` probe) |
|
||||
| `3` | Server requires authentication |
|
||||
| `4` | Server unreachable (DNS, refused, timeout) |
|
||||
| `5` | Tool error — `isError: true`, or the tool was not found |
|
||||
|
||||
and writes one JSON line to **stderr** on any non-zero exit:
|
||||
|
||||
```json
|
||||
{
|
||||
"error": {
|
||||
"code": "auth_required",
|
||||
"message": "Unauthorized",
|
||||
"status": 401,
|
||||
"url": "https://api.example/mcp"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Migration note:** a CI step that chained `inspector --cli … --method tools/call … && next-step` silently continued past failing tool calls in v1. In v2 it stops. That is the intended behavior, but it will surface as "v2 broke my pipeline" the first time a tool was failing all along. Parse the class with `2>&1 | tail -1 | jq .error` rather than matching on prose.
|
||||
|
||||
### 2. The target must come first
|
||||
|
||||
v2's CLI reads the leading run of non-dash tokens as the server target. Anything after the first flag is no longer part of it:
|
||||
|
||||
```bash
|
||||
mcp-inspector --cli node build/index.js --method tools/list # ✅
|
||||
mcp-inspector --cli --method tools/list node build/index.js # ❌ target silently dropped
|
||||
```
|
||||
|
||||
The second form does **not** error — the target is discarded and the Inspector falls back to your catalog, so it appears to work against the wrong server. v1 tolerated either order.
|
||||
|
||||
### 3. `--` splits the other way under `--cli`
|
||||
|
||||
On **web and TUI**, everything _after_ `--` goes to the target command — same as v1:
|
||||
|
||||
```bash
|
||||
mcp-inspector node build/index.js -- --config /etc/myserver.conf --verbose
|
||||
```
|
||||
|
||||
Under **`--cli`** it is reversed: everything _before_ `--` is the target, everything _after_ is the Inspector's own options.
|
||||
|
||||
```bash
|
||||
mcp-inspector --cli node build/index.js -- --method tools/list
|
||||
```
|
||||
|
||||
So the web example above, run under `--cli`, would have `--config /etc/myserver.conf` consumed as the Inspector's read-only-session flag (and then rejected as a catalog/ad-hoc conflict). There is currently no way to pass a leading-dash argument through to a stdio server on the `--cli` command line — put it in the server entry's `args` in a catalog or config file instead.
|
||||
|
||||
Stdout is otherwise compatible: the default `text` format still pretty-prints the result as `JSON.stringify(result, null, 2)`, exactly as v1 did.
|
||||
|
||||
## Environment variables
|
||||
|
||||
| v1 | v2 | Notes |
|
||||
| ------------------------ | ----------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `MCP_PROXY_AUTH_TOKEN` | **`MCP_INSPECTOR_API_TOKEN`** | Renamed. Guards `/api/*` via `x-mcp-remote-auth: Bearer <token>`; the browser also receives it injected into `index.html`, so a bare reload keeps working |
|
||||
| `MCP_PROXY_FULL_ADDRESS` | — | Removed; there is no proxy |
|
||||
| `SERVER_PORT` | _repurposed_ | Was the proxy port. Now only a fallback for the MCP Apps sandbox port |
|
||||
| `CLIENT_PORT` | same | Web UI port, default `6274`. Must be a fixed port — `0`/dynamic is rejected, since the origin allow-list and sandbox CSP derive from it |
|
||||
| `HOST` | same, **guarded** | An all-interfaces host (`0.0.0.0`, `::`, and equivalent spellings) is now **refused** unless `DANGEROUSLY_BIND_ALL_INTERFACES=true`. Binding a specific IP or hostname needs no opt-in |
|
||||
| `ALLOWED_ORIGINS` | same | Still comma-separated, still **replaces** the default list rather than merging. Entries must include the scheme |
|
||||
| `DANGEROUSLY_OMIT_AUTH` | same | |
|
||||
| `MCP_AUTO_OPEN_ENABLED` | same | Also gates non-TTY interactive OAuth in the CLI/TUI |
|
||||
| — | `DANGEROUSLY_BIND_ALL_INTERFACES` | New opt-in for a wildcard bind (the Docker image sets it) |
|
||||
| — | `MCP_CATALOG_PATH` | Default catalog path |
|
||||
| — | `MCP_SANDBOX_PORT` | MCP Apps sandbox server port (dynamic by default) |
|
||||
| — | `MCP_STORAGE_DIR`, `MCP_INSPECTOR_OAUTH_STATE_PATH`, `MCP_CLIENT_CONFIG_PATH` | Storage and OAuth state locations |
|
||||
| — | `MCP_OAUTH_CALLBACK_URL` | CLI/TUI OAuth loopback callback |
|
||||
|
||||
### The v1 UI configuration settings
|
||||
|
||||
v1 exposed a **Configuration** panel writing `localStorage` keys, also settable by query param. Those were global; in v2 the equivalents are **per-server settings**, stored in the catalog entry and editable in Server Settings:
|
||||
|
||||
| v1 setting | v2 |
|
||||
| --------------------------------------- | ----------------------------------- |
|
||||
| `MCP_SERVER_REQUEST_TIMEOUT` | per-server `requestTimeout` (ms) |
|
||||
| _(no v1 equivalent)_ | per-server `connectionTimeout` (ms) |
|
||||
| `MCP_REQUEST_TIMEOUT_RESET_ON_PROGRESS` | always on; no longer configurable |
|
||||
| `MCP_REQUEST_MAX_TOTAL_TIMEOUT` | no equivalent |
|
||||
| `MCP_PROXY_FULL_ADDRESS` | no equivalent (no proxy) |
|
||||
|
||||
## Web UI
|
||||
|
||||
**Server list.** v1 kept it in browser `localStorage`, so it was per-browser and invisible to the CLI. v2 keeps it in the catalog file, shared by all three clients — add a server in the web UI and the CLI and TUI see it.
|
||||
|
||||
**Query params.** v1 accepted `?transport=…&serverUrl=…&serverCommand=…&serverArgs=…` plus `?MCP_PROXY_AUTH_TOKEN=…`. v2's deep link is narrower and gated:
|
||||
|
||||
```
|
||||
http://127.0.0.1:6274/?serverUrl=<url>&transport=http|sse&autoConnect=<token>
|
||||
```
|
||||
|
||||
- `serverCommand` / `serverArgs` are gone — a URL can no longer ask the Inspector to spawn a process.
|
||||
- `serverUrl` is restricted to `http:` / `https:`.
|
||||
- `autoConnect` must equal the per-launch `MCP_INSPECTOR_API_TOKEN`, so a third-party-minted link can't drive a connect.
|
||||
- `?MCP_INSPECTOR_API_TOKEN=…` replaces `?MCP_PROXY_AUTH_TOKEN=…`.
|
||||
|
||||
Three further params (`openApp`, `appArgs`, `autoOpen`) land you on a rendered MCP App; see the [web README](../clients/web/README.md#deep-link-auto-connect).
|
||||
|
||||
**Auth.** v1's bearer-token field in the sidebar is replaced by per-server `headers` and real OAuth (including CIMD and enterprise-managed auth) configured in Server Settings and persisted in the catalog.
|
||||
|
||||
## Docker
|
||||
|
||||
```bash
|
||||
# v1
|
||||
docker run --rm -p 127.0.0.1:6274:6274 -p 127.0.0.1:6277:6277 \
|
||||
-e HOST=0.0.0.0 -e MCP_AUTO_OPEN_ENABLED=false \
|
||||
ghcr.io/modelcontextprotocol/inspector:1.0.1
|
||||
|
||||
# v2 — one port; the image already sets the wildcard-bind opt-in
|
||||
docker run --rm -p 6274:6274 ghcr.io/modelcontextprotocol/inspector:latest
|
||||
```
|
||||
|
||||
Notes:
|
||||
|
||||
- The v2 image sets `DANGEROUSLY_BIND_ALL_INTERFACES=true` internally (a container must bind `0.0.0.0` to be reachable through `-p`). Setting a bare `HOST=0.0.0.0` **outside** a container now exits with an error.
|
||||
- **If you remap the published port** (`-p 8080:6274`), the browser's origin no longer matches the in-container port, so set `ALLOWED_ORIGINS=http://localhost:8080,http://127.0.0.1:8080` (or run `-e CLIENT_PORT=8080 -p 8080:8080`) or connects will 403.
|
||||
- The image runs as non-root and has a `HEALTHCHECK` that assumes `--web`; add `--no-healthcheck` when running `--cli` / `--tui`.
|
||||
- `:latest` now points at v2. To stay on the v1 image, pin its exact version tag (`:1.0.1`).
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
**"`--config` errors that my file doesn't exist, but v1 created it."** v1 never created config files either — but v2's _default_ path is a catalog it will create. If you want a file created on demand, pass it as `--catalog`, not `--config`.
|
||||
|
||||
**"`--server` is ignored."** It selects only under `--cli`. Web warns and ignores it; the TUI errors on it as unknown.
|
||||
|
||||
**"My CI step started failing after upgrading."** Most likely exit code `5`: a `tools/call` returning `isError: true` now exits non-zero. Check `2>&1 | tail -1 | jq .error`.
|
||||
|
||||
**"The Inspector connects to a different server than I named."** Check flag order — under `--cli` the target must precede all flags, or it is silently dropped in favor of the catalog.
|
||||
|
||||
**"`HOST=0.0.0.0` exits with an error."** That's the wildcard-bind guard. Bind a specific address instead, or set `DANGEROUSLY_BIND_ALL_INTERFACES=true` if you genuinely need all interfaces. See [Host binding & the origin allow-list](../clients/web/README.md#host-binding--the-origin-allow-list).
|
||||
|
||||
**"I need v1 back."** `npx @modelcontextprotocol/inspector@v1-latest`.
|
||||
|
||||
## Related
|
||||
|
||||
- [MCP server configuration](./mcp-server-configuration.md) — the full `--catalog` / `--config` / ad-hoc model.
|
||||
- [CLI README](../clients/cli/README.md) · [TUI README](../clients/tui/README.md) · [web README](../clients/web/README.md)
|
||||
- [Reviewing an MCP App](./mcp-app-review.md) — the CLI-first App review recipe.
|
||||
Loading…
Reference in a new issue