inspector/clients/tui
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Cliff Hall 23e0c39d11
chore(deps): untangle the vitest/Storybook exact-pin knot to clear GHSA-p63j-vcc4-9vmv (#1839) (#1899)
GHSA-p63j-vcc4-9vmv (**critical**) — `@vitest/browser` Browser Mode provider
commands accept a file path from the browser and act on it without checking the
`allowWrite` permission gate. Patched in 4.1.10; `clients/web` was pinned at
4.1.0.

Dev-scope only — `@vitest/*` is absent from the published `files` allowlist, and
Browser Mode here runs this project's own Storybook play functions, never a
third-party page. That is why #1837 deferred it rather than attempting a
toolchain upgrade immediately before an irreversible publish.

## The knot, and what actually unties it

`^4.1.0` already admits 4.1.10, so the declared ranges were never the problem.
The family is welded by *exact* peer pins — `vitest` pins
`@vitest/browser-playwright`, which pins `@vitest/browser` — so nothing moves
alone.

The issue predicted that an explicit coordinated `npm i -D` would fix it. It
does not. With `clients/web/package-lock.json` in place, npm ERESOLVEs even when
every member of the family plus the Storybook packages are named in one command
and `node_modules` is deleted first:

    Found: @storybook/addon-a11y@10.2.19
    Could not resolve dependency: dev @storybook/addon-a11y@"^10.5.5"
    Conflicting peer dependency: storybook@10.5.5

npm resolves against the existing lock tree and will not move the whole
constellation at once. **Regenerating `clients/web/package-lock.json` is the
only thing that unties it** — a clean resolve places all four at 4.1.10 with no
peer complaints at all.

## Versions

    vitest                       4.1.0    -> 4.1.10
    @vitest/browser              4.1.0    -> 4.1.10   (transitive)
    @vitest/browser-playwright   4.1.0    -> 4.1.10
    @vitest/coverage-v8          4.1.0    -> 4.1.10
    storybook + @storybook/*     10.2.19  -> 10.5.5
    eslint-plugin-storybook      10.2.19  -> 10.5.5
    @chromatic-com/storybook     5.0.1    -> 5.2.1

`clients/{cli,tui,launcher}` were already resolving 4.1.9 (no browser pins, so
they floated freely) and were never vulnerable. Their declared `^4.1.0` still
*permitted* 4.1.0 on a fresh resolve, so the floor is raised to `^4.1.10` in all
four manifests — same reasoning as the vite floor in #1841. Pinning only the
lockfile would have left a regression path open.

## The vite 8 peer override

Resolved, as a consequence rather than a workaround.
`@joshwooding/vite-plugin-react-docgen-typescript@0.6.4` declared
`peer vite@"^3 || ^4 || ^5 || ^6 || ^7"` against our vite 8. It is not a direct
dependency — it arrives through `@storybook/react-vite`, which at 10.5.5 depends
on `^0.7.0`, and 0.7.0 adds `^8.0.0` to that range. So taking Storybook 10.5.5
for the peer set drops the override too; nothing is forced.

## Two holds the regeneration forced, each with a tracked issue

Regenerating the lock floats every other in-range dependency to current. Most of
that is welcome — it is what takes `clients/web` from 19 advisories to 1 — but
two floats broke the gate and are unrelated to this security fix:

- **zod 4.3.6 -> 4.4.3** makes `clients/web`'s `tsc -b` die with
  `FATAL ERROR: Ineffective mark-compacts near heap limit` at the ~4GB default.
  Reverting zod alone makes it exit 0, so the attribution is unambiguous.
  Held at `~4.3.6`. See #1896.
- **eslint-plugin-react-hooks 7.0.1 -> 7.1.1** enables `set-state-in-effect`,
  which fails `lint` on 8 pre-existing violations across 7 components. Fixing
  them is a real refactor with interaction-behavior consequences, not a
  suppression. Held at `~7.0.1`. See #1897.

Both `~` pins are deliberate and temporary, and both issues say so — they are
constraints to remove, not preferences.

Storybook 10.5.5 also reports that `setProjectAnnotations` is applied
automatically since 10.3, making `.storybook/vitest.setup.ts` redundant. Left in
place: `./preview` carries the Mantine decorator and the a11y annotations drive
the play-function assertions, and if automatic provisioning missed either, the
462 stories would render unthemed with a11y checks inert and very likely still
pass. That silent-failure risk does not belong in a security bump. See #1898.

## Audit delta

`clients/web`, where the advisory lived:

    before   19 (4 critical, 6 high, 6 moderate, 3 low)
    after     1 (1 low — esbuild-in-tsup, Windows dev server only)

All four critical entries — `vitest`, `@vitest/browser`,
`@vitest/browser-playwright`, `@vitest/coverage-v8` — are gone. The other 14 are
transitive dev-tree advisories (hono, ws, js-yaml, flatted, brace-expansion, ...)
cleared as a side effect of the clean resolve. Elsewhere: cli 6 -> 5,
launcher 3 -> 2, tui 4 -> 4, root unchanged at 9 (its lock is untouched). Those
remainders are pre-existing and unrelated.

## Verification

`npm run ci` green end to end on the upgraded stack — the point of caring here,
since this *is* the test infrastructure:

- coverage gate (>=90 per file, all four dimensions) passes on all four clients;
  no file regressed, so no gate was lowered and no `v8 ignore` added
- 4802 web tests, 304 cli, 282 tui, 5 launcher
- 462 Storybook play functions across 109 files, on vitest 4.1.10 Browser Mode
- verify:build-gate, and all five smokes including `smoke:tui` (real TTY here)

Claude-Session: https://claude.ai/code/session_01YAt8rqxysNbhYWLhoRm3fU

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-01 19:50:57 -04:00
..
__tests__ test(cli,tui): typecheck the __tests__ dirs (#1791) (#1799) 2026-07-27 12:05:35 -04:00
src fix(core): register peer-request handlers before connect (#1797) (#1798) 2026-07-27 11:32:23 -04:00
dev.ts feat: port CLI, TUI, and launcher from v1.5 into v2 (#1452) 2026-06-14 20:27:49 -04:00
eslint.config.js chore: close a batch of quality-gate & tidy gaps (#1785 #1787 #1789 #1790 #1767) (#1792) 2026-07-26 10:43:11 -04:00
index.ts feat(web): enforce localhost-only binding; expand loopback origin allow-list (#1796) 2026-07-26 22:15:10 -04:00
package-lock.json chore(deps): untangle the vitest/Storybook exact-pin knot to clear GHSA-p63j-vcc4-9vmv (#1839) (#1899) 2026-08-01 19:50:57 -04:00
package.json chore(deps): untangle the vitest/Storybook exact-pin knot to clear GHSA-p63j-vcc4-9vmv (#1839) (#1899) 2026-08-01 19:50:57 -04:00
README.md docs: restore the missing docs/ targets and split the catalog-seeding wording (#1811) (#1812) 2026-07-27 20:37:14 -04:00
test-config.json feat: port CLI, TUI, and launcher from v1.5 into v2 (#1452) 2026-06-14 20:27:49 -04:00
tsconfig.json test(cli,tui): typecheck the __tests__ dirs (#1791) (#1799) 2026-07-27 12:05:35 -04:00
tsconfig.test.json test(cli,tui): typecheck the __tests__ dirs (#1791) (#1799) 2026-07-27 12:05:35 -04:00
tsup.config.ts chore: close a batch of quality-gate & tidy gaps (#1785 #1787 #1789 #1790 #1767) (#1792) 2026-07-26 10:43:11 -04:00
tui.tsx feat(web): enforce localhost-only binding; expand loopback origin allow-list (#1796) 2026-07-26 22:15:10 -04:00
vitest.config.ts test(tui): extend coverage gate to Ink components + App.tsx (closes #1501) (#1538) 2026-06-29 08:19:12 -04:00

MCP Inspector TUI Client

The Terminal User Interface (TUI) client brings the interactive exploration capabilities of the Web Client directly to your terminal. It is built using Ink to provide a rich, React-like component experience in a command-line environment.

MCP Inspector TUI Screenshot

Running the TUI

You can run the TUI client via npx:

npx @modelcontextprotocol/inspector --tui node build/index.js

With Configuration Files

The TUI can load all servers from an MCP catalog/config file. With no source flag it uses the default writable catalog ~/.mcp-inspector/mcp.json (seeded empty if missing):

npx @modelcontextprotocol/inspector --tui --catalog mcp.json   # writable catalog (seeded empty if missing)
npx @modelcontextprotocol/inspector --tui --config mcp.json    # read-only session (errors if absent)

(It does not use --server; all servers in the file are available in the TUI.)

Options

MCP server (which server(s) to connect to)

Options that specify the MCP server(s) (catalog/config file, ad-hoc command/URL, env vars, headers) are shared by the Web, CLI, and TUI and are documented in MCP server configuration: --catalog (writable catalog, seeded empty if missing; default ~/.mcp-inspector/mcp.json or MCP_CATALOG_PATH), --config (read-only session, errors if absent), -e, --cwd, --header, --transport, --server-url, and the positional [target...]. --catalog and --config are mutually exclusive, and neither combines with an ad-hoc target.

TUI-specific (OAuth for HTTP servers)

The TUI supports OAuth for SSE and Streamable HTTP servers. Per-server OAuth fields in mcp.json (static client id/secret, scopes, enterprise-managed flag) are applied automatically when loaded from --catalog or --config. Install-wide settings (CIMD, enterprise IdP) come from ~/.mcp-inspector/storage/client.json — the same file the web Client Settings dialog writes. You can point at a different file with --client-config or MCP_CLIENT_CONFIG_PATH.

OAuth callback URL

The TUI starts a small loopback HTTP server to receive the authorization redirect after you sign in in the browser. Defaults:

Surface Default callback
Web http://localhost:6274/oauth/callback (main app server)
TUI http://127.0.0.1:6276/oauth/callback (dedicated runner port; avoids colliding with web on 6274)

Why a fixed default port? Enterprise-managed auth (EMA), CIMD, and many static OAuth apps require pre-registered redirect URIs. A predictable default (http://127.0.0.1:6276/oauth/callback) lets you register once on the IdP and reuse it across TUI sessions. Dynamic registration (DCR) can use ephemeral ports instead — see below.

Trade-off: only one TUI OAuth flow at a time can listen on the default port. A second instance starting OAuth while another is in progress may fail with EADDRINUSE. Override the listener with --callback-url or MCP_OAUTH_CALLBACK_URL (e.g. a different fixed port per instance, or http://127.0.0.1:0/oauth/callback when the authorization server accepts dynamically registered redirect URIs).

OAuth redirect URIs must match exactly what you register on the authorization server — localhost and 127.0.0.1 are different URIs. Register the TUI default on your OAuth app / IdP when using pre-registered (static), CIMD, or enterprise-managed clients. Override the listener with --callback-url or MCP_OAUTH_CALLBACK_URL; use http://127.0.0.1:0/oauth/callback for an OS-assigned ephemeral port when the authorization server registers redirect URIs dynamically (DCR).

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 (confidential clients); overrides client.json.
--client-metadata-url <url> — Client ID Metadata Document URL (CIMD); overrides client.json.
--callback-url <url> MCP_OAUTH_CALLBACK_URL OAuth redirect/callback listener (default: http://127.0.0.1:6276/oauth/callback). Must bind a loopback host (localhost / 127.0.0.0/8 / [::1]); a non-loopback host hard-errors, since the listener receives the authorization code over plaintext http.

Authenticating in the TUI

  1. Select an HTTP/SSE server and press C to connect.
  2. If authorization is required, the TUI starts OAuth automatically (browser opens for sign-in).
  3. After the callback completes, connect finishes without a second C.
  4. Use the Auth tab to inspect OAuth state (same fields as web Connection Info) or Clear OAuth state (disconnects when connected).

See also EMA / enterprise-managed auth and OAuth smoke testing for staging servers and verification steps.

Features

The TUI provides terminal-native tabs and panes for interacting with your MCP server:

  • Resources: Browse and read resources exposed by the server.
  • Prompts: List and test prompts.
  • Tools: View available tools and execute them with form-like inputs.
  • Protocol: View JSON-RPC request/response/notification history (matches the web Protocol monitor).
  • Network: View HTTP fetch traffic for SSE / Streamable HTTP servers (matches the web Network monitor).
  • Console: View stdio stderr from the connected server process (matches the web Console monitor).

Navigation

  • Use the Arrow Keys (Left/Right) or Tab to switch between the main tabs (Resources, Tools, Prompts, etc.).
  • Use the Arrow Keys (Up/Down) to scroll through lists of items.
  • Press Enter to select an item, execute a tool, or fetch a resource.
  • Press Escape or Ctrl+C to exit the application.

Development

Like the other clients, the TUI self-validates from its own folder:

npm run validate       # format:check && lint && build && test:coverage
npm test               # run all tests
npm run test:coverage  # run tests under the per-file coverage gate

The repo-root validate:tui just delegates here. eslint.config.js registers react-hooks for the classic rules only (rules-of-hooks + exhaustive-deps); the stricter react-hooks@7 rules are not enforced on the interim component surface (#1501).

Tests live in __tests__/. The coverage gate currently covers the TUI's non-React logic (server resolution, logger, tab metadata, and the utils/ form/URL helpers); the Ink components and App.tsx are an interim exclusion in vitest.config.ts pending a renderer-based follow-up.