Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
75 commits
Select commit Hold shift + click to select a range
73d7439
docs: add CONTRIBUTORS.md issues-only policy (#1517)
cliffhall Jun 27, 2026
cc1e707
docs: add PR template redirecting external authors to issues-only pol…
cliffhall Jun 27, 2026
f7a746c
docs: drop version-board details from CONTRIBUTORS.md (#1517)
cliffhall Jun 27, 2026
a5d8d80
feat(cli): exit-code map + structured JSON error envelopes (closes #1…
cliffhall Jul 1, 2026
3810270
feat(core): redact sensitive headers in the fetch log (closes #1561)
cliffhall Jul 1, 2026
1a6e32b
feat(core): add AppInfo + extractAppInfo() to core/mcp/apps.ts (close…
cliffhall Jul 1, 2026
2647815
feat(web): generalize downloadFile.ts with downloadBlob, fileNameFrom…
cliffhall Jul 1, 2026
0d50b90
feat(test-servers): mcp_app_demo preset + _meta on tool/resource defs…
cliffhall Jul 1, 2026
5831b5c
feat(web): extract hostContext utilities for the Apps host (closes #1…
cliffhall Jul 1, 2026
77e57e0
feat(web): add sandbox CSP builder library (closes #1558)
cliffhall Jul 1, 2026
2b84281
feat(auth): harden OAuth state generation and callback validation (cl…
cliffhall Jul 1, 2026
d5be492
feat(core): honor HTTPS_PROXY/HTTP_PROXY/NO_PROXY in the Node transpo…
cliffhall Jul 1, 2026
c299b6e
feat(web): migrate OAuth store to shared /store API (RemoteOAuthStora…
cliffhall Jul 1, 2026
fa5514a
fix(cli): guard numeric .code to HTTP range; bound cause-chain depth
cliffhall Jul 1, 2026
e5dbdca
test(test-servers): export MCP_APP_DEMO_URI, dedupe in integration test
cliffhall Jul 1, 2026
d21da16
refactor(auth): address review — accurate toast copy, document state …
cliffhall Jul 1, 2026
ee4b232
refactor(core): narrow readUiMeta from unknown; document ui-meta prec…
cliffhall Jul 1, 2026
b152f74
fix(ci): run CI on Node 22.x and correct undici 8 Node floor (#1563)
cliffhall Jul 1, 2026
cb54de5
refactor(web): document + guard sandbox CSP builder per review
cliffhall Jul 1, 2026
39f9baa
test(test-servers): pass tool _meta through the task-tool registratio…
cliffhall Jul 1, 2026
8c10b8e
fix(auth): make clear* hydration-safe, migrate raw-key blobs, doc kee…
cliffhall Jul 1, 2026
7e931ec
fix(deps): sync root package-lock engines to Node >=22.19.0 (#1563)
cliffhall Jul 1, 2026
8059a3d
docs(test-servers): note custom resource handlers must re-add _meta
cliffhall Jul 1, 2026
4e14971
fix(auth): route clearClientInformation through clearAfterHydration too
cliffhall Jul 1, 2026
588fc3f
docs(auth): note getClientRegistrationKind's hydration invariant like…
cliffhall Jul 1, 2026
79ca8f0
docs: add version-board/label table to CONTRIBUTORS.md (closes #1595)
cliffhall Jul 2, 2026
3df4f0e
Merge remote-tracking branch 'origin/v2/main' into merge-1537
cliffhall Jul 2, 2026
4bad110
test(web): eliminate timeout flakiness in the web test suite (closes …
cliffhall Jul 2, 2026
dd78120
test(web): make the debounce-cancellation test load-bearing (review r…
cliffhall Jul 2, 2026
8bc32c8
merge: v2/1556-core-appinfo (#1584)
cliffhall Jul 2, 2026
2d1a99a
merge: v2/1561-redact-headers (#1585)
cliffhall Jul 2, 2026
4e3cd59
merge: v2/1563-node-proxy (#1590)
cliffhall Jul 2, 2026
4c5036e
merge: v2/1564-cli-exit-codes (#1583)
cliffhall Jul 2, 2026
9257c84
merge: v2/1557-mcp-app-demo (#1587)
cliffhall Jul 2, 2026
e55867c
merge: v2/1558-sandbox-csp (#1588)
cliffhall Jul 2, 2026
3f22299
merge: v2/1559-hostcontext-utils (#1591)
cliffhall Jul 2, 2026
52cd90b
merge: v2/1560-downloadfile (#1586)
cliffhall Jul 2, 2026
cc37a75
merge: v2/1562-oauth-state-hardening (#1589)
cliffhall Jul 2, 2026
e425602
merge: v2/1548-web-remote-oauth-storage (#1592)
cliffhall Jul 2, 2026
c6b5af3
merge: v2/1596-test-stability (#1597)
cliffhall Jul 2, 2026
b2ff34a
merge: chore/1517-contributors-md (#1537)
cliffhall Jul 2, 2026
524bf2d
feat(core): redact sensitive body + URL-query fields in fetch log (cl…
cliffhall Jul 2, 2026
1fd1313
test(web): gate src/lib/** under the coverage include globs (closes #…
cliffhall Jul 2, 2026
775b98a
docs(core): clarify redaction scope + add nested request-body test
cliffhall Jul 2, 2026
4b180d9
merge: #1598 redact bodies+URL query (#1593)
cliffhall Jul 2, 2026
e19d110
merge: #1599 gate src/lib coverage (#1594)
cliffhall Jul 2, 2026
61ca220
ci: enforce the ≥90% per-file coverage gate in CI (closes #1550)
cliffhall Jul 2, 2026
436257b
chore: retarget #1583 onto 1579-wave-1 rollup (empty commit enables b…
cliffhall Jul 2, 2026
0fe9c4e
Merge pull request #1583 from modelcontextprotocol/v2/1564-cli-exit-c…
cliffhall Jul 2, 2026
985ef32
chore: retarget #1584 onto 1579-wave-1 rollup (empty commit enables b…
cliffhall Jul 2, 2026
72ae119
Merge pull request #1584 from modelcontextprotocol/v2/1556-core-appinfo
cliffhall Jul 2, 2026
ff9269c
chore: retarget #1585 onto 1579-wave-1 rollup (empty commit enables b…
cliffhall Jul 2, 2026
98dff8b
Merge pull request #1585 from modelcontextprotocol/v2/1561-redact-hea…
cliffhall Jul 2, 2026
949bc3b
chore: retarget #1586 onto 1579-wave-1 rollup (empty commit enables b…
cliffhall Jul 2, 2026
53cca10
Merge pull request #1586 from modelcontextprotocol/v2/1560-downloadfile
cliffhall Jul 2, 2026
3b4c958
chore: retarget #1587 onto 1579-wave-1 rollup (empty commit enables b…
cliffhall Jul 2, 2026
b96fd6e
Merge pull request #1587 from modelcontextprotocol/v2/1557-mcp-app-demo
cliffhall Jul 2, 2026
060b64a
chore: retarget #1588 onto 1579-wave-1 rollup (empty commit enables b…
cliffhall Jul 2, 2026
3de93b9
Merge pull request #1588 from modelcontextprotocol/v2/1558-sandbox-csp
cliffhall Jul 2, 2026
56ca566
chore: retarget #1589 onto 1579-wave-1 rollup (empty commit enables b…
cliffhall Jul 2, 2026
d307639
Merge pull request #1589 from modelcontextprotocol/v2/1562-oauth-stat…
cliffhall Jul 2, 2026
30dc370
chore: retarget #1590 onto 1579-wave-1 rollup (empty commit enables b…
cliffhall Jul 2, 2026
42fb853
Merge pull request #1590 from modelcontextprotocol/v2/1563-node-proxy
cliffhall Jul 2, 2026
1fdf03e
chore: retarget #1591 onto 1579-wave-1 rollup (empty commit enables b…
cliffhall Jul 2, 2026
8b8e46f
Merge pull request #1591 from modelcontextprotocol/v2/1559-hostcontex…
cliffhall Jul 2, 2026
ca00935
chore: retarget #1592 onto 1579-wave-1 rollup (empty commit enables b…
cliffhall Jul 2, 2026
7e1496a
Merge pull request #1592 from modelcontextprotocol/v2/1548-web-remote…
cliffhall Jul 2, 2026
9f0e4d8
chore: retarget #1537 onto 1579-wave-1 rollup (empty commit enables b…
cliffhall Jul 2, 2026
7d4caf8
Merge pull request #1537 from modelcontextprotocol/chore/1517-contrib…
cliffhall Jul 2, 2026
fa274fa
chore: retarget #1597 onto 1579-wave-1 rollup (empty commit enables b…
cliffhall Jul 2, 2026
747148f
Merge pull request #1597 from modelcontextprotocol/v2/1596-test-stabi…
cliffhall Jul 2, 2026
c2bc6a6
Merge pull request #1603 from modelcontextprotocol/1550-ci-coverage-gate
cliffhall Jul 2, 2026
859911d
revert(rollup): remove CONTRIBUTORS docs (#1537/#1517) from Wave-1 ro…
cliffhall Jul 2, 2026
cfbdfdf
Merge remote-tracking branch 'origin/v2/main' into 1579-wave-1
cliffhall Jul 3, 2026
c026d4d
docs: address review nits on the v2/main merge
cliffhall Jul 4, 2026
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
31 changes: 20 additions & 11 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ jobs:
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: '20.x'
node-version: '22.x'
cache: 'npm'

- name: Install dependencies (root + all clients)
Expand All @@ -25,18 +25,27 @@ jobs:
- name: Validate (format, lint, build, fast tests)
# Each client self-validates: format:check + lint + build + test (no
# coverage instrumentation — fast). This also builds every client bundle
# (web dist, cli/tui/launcher) that the smokes below need. The per-file
# coverage gate (`npm run coverage`) is intentionally NOT run in CI — run
# it locally before pushing when you want the gate (#1484).
# (web dist, cli/tui/launcher) that the smokes below need. The heavier
# per-file coverage gate runs in its own step below. Unit tests run in
# both steps (fast here, instrumented under coverage); the double unit
# run is an accepted trade-off for keeping this fast build step intact.
# A future optimization could split coverage into per-client parallel
# jobs, but that's a larger restructure and deliberately out of scope.
run: npm run validate

- name: Run web integration tests
# `validate` runs the web UNIT project only (fast); the integration
# project (real stdio/HTTP servers, e2e OAuth, filesystem storage) is
# otherwise only reached via test:coverage. Run it here without coverage
# so CI still exercises those paths even though the gate is local-only.
working-directory: ./clients/web
run: npm run test:integration
- name: Enforce per-file coverage gate (≥90% on all four dimensions)
# CI-ENFORCED coverage gate (#1550): runs `npm run coverage`, which
# chains every client's `test:coverage` (v8-instrumented) and fails the
# job if ANY file drops below 90% on lines, statements, functions, or
# branches. This is the whole point of the step — a PR that regresses
# coverage below the threshold now blocks merge instead of relying on a
# contributor remembering to run the gate locally.
#
# This also covers the web integration project: web's `test:coverage`
# runs `--project=unit --project=integration --coverage`, so the former
# standalone `Run web integration tests` step was removed to avoid
# running the integration suite twice.
run: npm run coverage

- name: Run cross-client smokes
# End-to-end smokes through the built launcher (--help dispatch + prod
Expand Down
8 changes: 4 additions & 4 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -176,12 +176,12 @@ gh project item-edit --project-id PVT_kwDOCt2Azc4BJVxt --id "$ITEM_ID" --field-i
- Run CLI tests with `npm run test` from `clients/cli/` (builds test-servers + CLI bin first via `pretest`)
- Run TUI tests with `npm run test` from `clients/tui/`
- The repo root has no aggregate `test` script — each client self-validates, so run `npm run validate` from the root (all clients, fast) or `cd clients/<name> && npm run validate` (one client). Each client still exposes its own `test` / `test:coverage` for quick iteration.
- **`validate` is fast: it runs `test`, not `test:coverage`.** The coverage gate (slower — adds v8 instrumentation, and for web the integration project) is a **separate** top-level `npm run coverage` (and per-client `coverage:web` / `coverage:cli` / `coverage:tui` / `coverage:launcher`, each delegating to that client's `test:coverage`). Run `npm run coverage` when you want the gate. **CI does NOT run `coverage`** the gate is local-only; CI runs `validate` (fast) plus the web integration suite (`clients/web` `test:integration`, no coverage) so the integration paths are still exercised.
- **`validate` is fast: it runs `test`, not `test:coverage`.** The coverage gate (slower — adds v8 instrumentation, and for web the integration project) is a **separate** top-level `npm run coverage` (and per-client `coverage:web` / `coverage:cli` / `coverage:tui` / `coverage:launcher`, each delegating to that client's `test:coverage`). Run `npm run coverage` when you want to reproduce the gate locally before pushing. **CI runs `coverage`** on every push (#1550): the per-file ≥90 gate is CI-enforced, so a PR that drops any file below 90 on lines/statements/functions/branches fails the job. CI runs `validate` (fast) for format/lint/build/unit tests, then `coverage` for the instrumented gate. Because web's `test:coverage` already runs the integration project, CI has no separate `test:integration` step — the integration paths are exercised inside the coverage gate.
- Each client's `test:coverage` enforces a **uniform per-file gate of ≥ 90 on all four dimensions** — lines, statements, functions, and branches — across `clients/web`, `clients/cli`, `clients/tui`, and `clients/launcher` (CI enforces this gate). This is the result of a codebase-wide audit: the branch floor was first lifted 50 → 70 for web (#1271), then the whole gate raised to 90 with real tests added for every outlier. Genuinely-unreachable branches are **not** waved through by lowering the gate — they are annotated at the source with a justified `/* v8 ignore … -- <reason> */` comment. Acceptable reasons are happy-dom-inherent paths (Mantine portal mount points, `useMediaQuery` fallbacks, `typeof window` SSR guards), React StrictMode effect-replay blocks, and provably-dead defensive guards (e.g. a `?? fallback` for a value the types guarantee non-null, or a `Select.onChange` receiving a value outside the allowed list). New code must clear 90 on every dimension; reach for a justified `v8 ignore` only when a branch is genuinely impossible to exercise.
- The **same per-file gate** is enforced for the CLI and TUI (#1484), not just web:
- **CLI** (`clients/cli`): tests run **in-process** by importing `runCli()` (see `__tests__/helpers/cli-runner.ts`) so `clients/cli/src` is measured under v8 instrumentation. A thin out-of-process layer (`__tests__/e2e.test.ts` + `scripts/smoke-cli.mjs`) still spawns the built binary for the shebang/`process.exit` paths; `src/index.ts` (binary bootstrap) is the only coverage exclusion. `commander` uses `.exitOverride()` so a parse error throws instead of tearing down the test worker.
- **TUI** (`clients/tui`): the gate covers the **non-React logic** only — `logger.ts`, `components/tabsConfig.ts`, and `utils/*` (server resolution lives in `core/` and is measured by the web suite). The Ink components, `App.tsx`, and `hooks/` are an **interim exclusion** in `clients/tui/vitest.config.ts` pending the renderer-based follow-up (#1501). When adding new **non-React** logic under `clients/tui/src`, it falls under the gate automatically — add tests for it.
- Run `npm run test:integration` (also from `clients/web/`) for the InspectorClient + transport + auth integration suite. It runs under a separate `integration` vitest project in node env (no happy-dom) with 30s timeouts. The script builds `test-servers/` first via `tsc -p ../../test-servers --noCheck` so the stdio MCP test server can be spawned as a real subprocess. CI runs it as its own step after unit tests.
- Run `npm run test:integration` (also from `clients/web/`) for the InspectorClient + transport + auth integration suite. It runs under a separate `integration` vitest project in node env (no happy-dom) with 30s timeouts. The script builds `test-servers/` first via `tsc -p ../../test-servers --noCheck` so the stdio MCP test server can be spawned as a real subprocess. CI does not run `test:integration` as its own step — the integration project is covered by the CI `coverage` gate, whose web `test:coverage` runs `--project=unit --project=integration --coverage`.
- Test files live alongside the source as `<Name>.test.tsx` (or `.test.ts` for non-React modules). Integration tests live under `clients/web/src/test/integration/`, mirroring the `core/` source layout (`mcp/`, `mcp/node/`, `mcp/remote/`, `auth/`, `auth/node/`, `storage/`). Any test file under that folder is automatically picked up by the `integration` vitest project (node env, 30s timeouts) via the folder glob in `vite.config.ts` — placement is the manifest, there is no enumeration to keep in sync. Tests outside the folder run in the `unit` project (happy-dom). When adding a new test for, e.g., `core/mcp/remote/foo.ts`, put it at `src/test/integration/mcp/remote/foo.test.ts`.
- Use `renderWithMantine` from `src/test/renderWithMantine.tsx` to render components — it wraps in `MantineProvider` with the project theme

Expand All @@ -196,8 +196,8 @@ gh project item-edit --project-id PVT_kwDOCt2Azc4BJVxt --id "$ITEM_ID" --field-i
- **`npm run ci` before pushing** — runs the same steps as `.github/workflows/main.yml` (minus `npm install`): `validate` → web `test:integration` → `smoke` → Storybook play-function tests (installs Playwright chromium if needed). Use this when you want CI parity; expect several minutes. **`npm run validate`** remains the fast loop during development (unit tests only — no web integration, smoke, or Storybook).
- ALWAYS do `npm run format` before committing, then **`npm run ci`** (or at minimum `npm run validate`) before pushing. From the repo root, `validate` chains the four per-client validations (`validate:web` → `validate:cli` → `validate:tui` → `validate:launcher`); each delegates to that client's own `npm run validate` = `format:check` + `lint` + `build` + `test` in its own folder (no coverage — fast). Every client is self-validating and the top level just chains them, building each client's bundle along the way (no cross-client build dependencies).
- The one CLI nuance: `clients/cli`'s out-of-process `e2e.test.ts` spawns the built binary, so its `test` **builds first** via `pretest` (`test-servers:build && build`). To avoid building it twice, `clients/cli`'s `validate` folds that in — it is `format:check && lint && test` with **no** separate `build` step (the other clients, whose tests don't spawn their bundle, keep an explicit `build`). `validate:web`/`validate:tui`/`validate:launcher` are the uniform `format:check && lint && build && test`.
- Optionally also run **`npm run coverage`** when you want the local-only per-file ≥90 gate — CI does **not** run `coverage`; it relies on `validate` + web `test:integration` instead.
- **`smoke` is NOT part of `validate`** it is included in `npm run ci`. It runs `smoke:launcher` (`--help` dispatch) plus the prod `smoke:cli` / `smoke:tui` / `smoke:web`, and contains **no build commands** — it assumes the cli/tui/launcher bundles already exist (a full `validate` builds them; `smoke:web` builds `clients/web/dist` on demand).
- Before pushing, also run **`npm run coverage`** — `validate` is fast and does NOT enforce the per-file gate (or, for web, run the integration project); `coverage` does both. **CI runs `coverage`** too (#1550), so the gate blocks PRs that regress below 90 — running it locally first just catches failures before CI does. CI has no separate `test:integration` step: web's `test:coverage` already runs the integration project.
- **`smoke` is a separate top-level target, NOT part of `validate`.** Run it (or the individual `smoke:*`) after a build/validate: `npm run validate && npm run smoke`. It runs `smoke:launcher` (`--help` dispatch) plus the prod `smoke:cli` / `smoke:tui` / `smoke:web`, and contains **no build commands** — it assumes the cli/tui/launcher bundles already exist (a full `validate` builds them; `smoke:web` builds `clients/web/dist` on demand). CI runs `validate`, then the `coverage` gate (which also covers the web integration project), then `smoke`. Storybook is the only CI step left out (see below).
- `smoke:launcher` (`scripts/smoke-launcher.mjs`) runs the built launcher with `--help`, `--cli --help`, and `--tui --help`, asserting each exits 0 and prints that mode's usage banner (which also proves the launcher resolved and loaded the right client build). It's the cheap dispatch check before the heavier prod smokes below.
- `smoke:web` (`scripts/smoke-web.mjs`) starts `mcp-inspector --web` (prod, no `--dev`) against the built `clients/web/dist` and asserts `GET /` serves the SPA (HTTP 200) with the injected `__INSPECTOR_API_TOKEN__`. Prod `--web` serves from `clients/web/dist`, which ships in the published package but is absent in a fresh checkout — the runner builds it on demand (`build:client` = `vite build`) on first launch, or exits with an actionable error if that build can't run (see `clients/web/server/ensure-web-build.ts` and the launcher README). `--dev` runs Vite directly and never needs `dist`.
- `smoke:cli` (`scripts/smoke-cli.mjs`) drives `mcp-inspector --cli` through the built launcher against the bundled stdio test server via a temp `--catalog`: it asserts `tools/list` returns the server's tools (real connect over stdio), the default writable catalog is seeded empty on first run, a missing read-only `--config` errors without seeding, and `--catalog` + `--config` is rejected. `smoke:tui` (`scripts/smoke-tui.mjs`) launches `mcp-inspector --tui --catalog <temp>` and asserts the Ink app renders its first frame (the "MCP Servers" panel) within a timeout, then SIGTERMs it — a shallow boot/render check, not full interaction. **`smoke:tui` is local-only: it self-skips when `process.env.CI` is set**, because the Ink TUI needs a real TTY (raw mode) that headless CI lacks — so run it (via `npm run smoke`) on your own machine before pushing. Both build `test-servers/build` on demand if it's missing.
Expand Down
48 changes: 40 additions & 8 deletions clients/cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,12 @@ npx @modelcontextprotocol/inspector --cli https://my-mcp-server.example.com --tr

When a server is loaded from a `--catalog`/`--config` file, its per-server settings (headers, connection/request timeouts, and OAuth) are applied to the connection — the same resolution the TUI uses. A `--header` flag overrides the file's headers for that run while leaving the file's timeouts and OAuth in place.

### HTTP proxy support

Connections to remote HTTP/SSE servers honor the conventional proxy environment variables: `HTTPS_PROXY` / `HTTP_PROXY` (and their lowercase forms) select the proxy, and `NO_PROXY` exempts hosts. This applies to the Node transport shared by the CLI and the web backend — no inspector-specific flag is needed. When a proxy variable is set, outbound requests are routed through undici's `EnvHttpProxyAgent`.

Proxy routing is powered by the [`undici`](https://www.npmjs.com/package/undici) package (`^8.5.0`, which requires Node `>= 22.19.0` — the inspector's supported floor). It is imported lazily only when a proxy variable is set, so runs without a proxy configured pay no cost.

## Options

### MCP server (which server to connect to)
Expand Down Expand Up @@ -131,13 +137,13 @@ Register `http://127.0.0.1:6276/oauth/callback` on static or enterprise IdPs tha

#### Flags

| Option | Env | Description |
| ------ | --- | ----------- |
| `--client-config <path>` | `MCP_CLIENT_CONFIG_PATH` | Install-level client config (default: `~/.mcp-inspector/storage/client.json`). |
| `--client-id <id>` | — | OAuth client ID (static client); overrides `client.json`. |
| `--client-secret <secret>` | — | OAuth client secret; overrides `client.json`. |
| `--client-metadata-url <url>` | — | CIMD metadata URL; overrides `client.json`. |
| `--callback-url <url>` | `MCP_OAUTH_CALLBACK_URL` | Redirect URI sent to the authorization server (default: `http://127.0.0.1:6276/oauth/callback`). |
| Option | Env | Description |
| ----------------------------- | ------------------------ | ------------------------------------------------------------------------------------------------ |
| `--client-config <path>` | `MCP_CLIENT_CONFIG_PATH` | Install-level client config (default: `~/.mcp-inspector/storage/client.json`). |
| `--client-id <id>` | — | OAuth client ID (static client); overrides `client.json`. |
| `--client-secret <secret>` | — | OAuth client secret; overrides `client.json`. |
| `--client-metadata-url <url>` | — | CIMD metadata URL; overrides `client.json`. |
| `--callback-url <url>` | `MCP_OAUTH_CALLBACK_URL` | Redirect URI sent to the authorization server (default: `http://127.0.0.1:6276/oauth/callback`). |

**Example** — list tools on an OAuth-protected server using stored tokens and CIMD from the command line:

Expand All @@ -147,7 +153,33 @@ npx @modelcontextprotocol/inspector --cli --catalog mcp.json --server my-http-se
--method tools/list
```

See [EMA / enterprise-managed auth](../../specification/v2_auth_ema.md) and [OAuth smoke testing](../../specification/v2_auth_smoke_testing.md) (§3 Stytch/CIMD; [§5 mid-session manual validation](v2_auth_smoke_testing.md#5-mid-session-auth--step-up--manual-validation) — CLI **C1–C2**).
See [EMA / enterprise-managed auth](../../specification/v2_auth_ema.md) and [OAuth smoke testing](../../specification/v2_auth_smoke_testing.md) (§3 Stytch/CIMD; [§5 mid-session manual validation](../../specification/v2_auth_smoke_testing.md#5-mid-session-auth--step-up--manual-validation) — CLI **C1–C2**).

## Exit codes & error envelopes

Every non-zero exit maps to a stable failure class, so a programmatic caller
(CI, a script, an agent) can branch on _why_ the CLI failed without scraping
prose from stderr:

| Code | Meaning |
| ---- | ------- |
| `0` | Success. |
| `1` | Usage / unexpected error (the catch-all). |
| `2` | No MCP App found on the tool (`--app-info` probe). |
| `3` | Server requires authentication (401/403, `WWW-Authenticate`, OAuth). |
| `4` | Server unreachable (DNS, connection refused, timeout, `fetch failed`). |
| `5` | Tool error (`tools/call` returned `isError:true`, or the tool was not found). |

On any non-zero exit the CLI also writes a single JSON line to **stderr** — the
`ErrorEnvelope`:

```json
{ "error": { "code": "auth_required", "message": "Unauthorized", "status": 401, "url": "https://api.example/mcp" } }
```

The `code` is a stable identifier for the failure class; `message` is the
human-readable error; `cause`, `status`, and `url` are included when known.
Because it is one line, a caller can parse it with `2>&1 | tail -1 | jq .error`.

## Why use the CLI?

Expand Down
Loading
Loading