Skip to content

feat(cli): session-oriented mcpi CLI with daemon (#1432) - #1727

Closed
BobDickinson wants to merge 12 commits into
v2/mainfrom
v2/cli-v2-sessions
Closed

feat(cli): session-oriented mcpi CLI with daemon (#1432)#1727
BobDickinson wants to merge 12 commits into
v2/mainfrom
v2/cli-v2-sessions

Conversation

@BobDickinson

@BobDickinson BobDickinson commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Closes #1432

Summary

Implements the session-oriented Inspector CLI from #1432, as documented in specification/v2_cli_v2.md:

  • New mcpi entrypoint (bin.mcpiclients/cli/build/mcp-bin.js) with an implicit local session daemon (IPC). Connect once, run many MCP subcommands against a named session, then disconnect — ssh-agent-style private binding via eval "$(mcpi private)".
  • Frozen one-shot path unchanged: mcp-inspector --cli still connects → one --method → disconnect, never starts the daemon. Both paths share handlers/run-method.ts + InspectorClient.
  • Session surface: catalog (servers/list|show), session lifecycle (connect / disconnect / sessions/*), auth store (auth/list, auth/clear), daemon control, and MCP methods including streams (logging/tail, resources/subscribe), tasks, and roots.
  • Human text (default) and pretty JSON (--format json, no { result } envelope); TTY styling / OSC 8; connect-time OAuth with --relogin / --stored-auth-only.

Known gaps stay in the spec To-do (mid-session auth over IPC, Windows named pipes, daemon lock hardening, etc.) — not claimed as done here.

Review follow-ups (Claude on this PR)

Addressed in 5fbd3b9e:

  1. Daemon idle leak (Medium) — Arm the idle timer at daemon start and after a failed connect that leaves the registry empty, so ensureDaemon from e.g. mcpi tools/list with no sessions still self-reaps (~60s). Covered by a session-less start test.
  2. stringifyMeta mangling (Low) — Non-string metadata values (objects/arrays from key={"a":1}) are now JSON.stringify'd instead of String(v)"[object Object]", in both session and one-shot paths.
  3. --relogin on stdio (Low/UX) — Documented, not rejected: the flag means “ignore stored OAuth for this connect; interactive login only if auth is required.” For stdio / no URL-keyed store entry it is a deliberate no-op (nothing to clear). Help text, README, and v2_cli_v2.md updated accordingly.
  4. Global bin name (Nit) — Renamed session bin from mcpmcpi (MCP Inspector) in root + clients/cli package.json, docs, examples, and Commander .name(), to avoid claiming a generic PATH name.

Deferred (already in spec To-do / out of scope for this base PR):

  • Completion requests #3 ensureDaemon unlink on failed ping vs auth mismatch — rare private-token edge case; leave for later hardening.
  • add readme #6 Coverage gate for ipc-glue.ts / stream-client.ts — already tracked in the spec To-do.

Test plan

  • npm run validate (web / cli / tui / launcher)
  • clients/cli unit suite + per-file ≥90 coverage gate (test:coverage), with the exclusions listed in the spec
  • Session smoke: mcpi connect <server> --config …tools/listtools/call …disconnectdaemon stop
  • OAuth: connect to an HTTP server that requires auth; TTY browser/OSC 8 UX and token reuse on reconnect
  • mcpi connect … --relogin, auth/list, auth/clear <url>, auth/clear --all --yes
  • eval "$(mcpi private)" isolates the daemon from the default socket
  • One-shot mcp-inspector --cli … still works for the same catalog entry
  • Re-validated after review fixes + mcpi rename

BobDickinson and others added 3 commits July 19, 2026 10:09
Land the living design doc under specification/ so we can iterate there;
cross-link from the as-built CLI/TUI launcher and catalog specs.

Co-authored-by: Cursor <cursoragent@cursor.com>
@BobDickinson BobDickinson added the v2 Issues and PRs for v2 label Jul 20, 2026
@BobDickinson

Copy link
Copy Markdown
Contributor Author

Note: This is a first pass. There are noted open / future issues in the spec. We're trying to land a base PR to build from.

@claude review

@claude

claude Bot commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Claude finished @BobDickinson's task in 3m 49s —— View job


Review: session-oriented mcp CLI with daemon (#1727)

Read the spec and the full daemon + session surface. This is a well-structured first pass — I've kept the review to things not already listed in the spec To-do, since the known gaps (mid-session auth, singleton lock, Windows pipes, per-session mutex, etc.) are explicitly out of scope for this base PR.

What's good: clean split of one-shot vs. session sharing handlers/run-method.ts + InspectorClient; constant-time IPC token compare; 0600 socket/lock; config (incl. secrets) sent over IPC after listen rather than on argv; token via env not argv; NDJSON framing is simple and readable; strong test coverage with sensible, documented gate carve-outs.


1. (Medium) Daemon leaks when a spawn never establishes a session

SessionRegistry.armIdleTimer() is only ever called from disconnect() when the session count hits 0 (sessions.ts:226). It is not armed at daemon startup. But ensureDaemon() auto-spawns a detached, unref'd daemon from four call sites — connect, disconnect, sessions/use, and every rpc/stream (dispatch.ts:35). Any of these that spawns the daemon but never records a successful connect leaves a daemon with sessions.size === 0 and no armed idle timer, so it runs forever.

Concrete repro (nothing connected yet):

mcp tools/list        # → error "No open sessions", but a daemon is now running
mcp sessions/use foo  # → "session not found", daemon lingers
mcp disconnect        # → "No open sessions", daemon lingers

The user sees an error and has no idea a background daemon is now pinned to the event loop indefinitely (only daemon stop, or a later connect+disconnect cycle, reaps it). On a shared/CI box these accumulate. The spec's "idle exit ~60s after last disconnect" implicitly assumes a connect happened first.

Suggested fix: arm the idle timer once at DaemonServer.start() (and/or after any request completes with sessions.size === 0) so a session-less daemon self-reaps. Fix this →


2. (Low) stringifyMeta mangles non-string metadata values

session/mcp.ts:799 maps every metadata/tool-metadata value through String(v). A --metadata k={"a":1} (parsed to an object by parseKeyValue) becomes the literal "[object Object]", and arrays get comma-joined — the structure is lost before it reaches the server. MethodArgs.metadata is typed Record<string,string>, so if only strings are ever intended, consider validating/erroring on non-strings; otherwise JSON.stringify non-primitives (or thread JsonValue through end-to-end).

3. (Low) ensureDaemon unlinks a reachable-but-unresponsive socket

ensure.ts:111-122: if the socket is reachable but ping throws, the code fs.unlinkSync(socketPath) and spawns a replacement. If the ping failed because the running daemon required a token the caller doesn't have (rather than being truly stale), this yanks the socket path from under a live daemon, orphaning it. Edge case (private dirs are unique per mcp private, so token drift on the same dir is unlikely), but the unlink is destructive — worth distinguishing an auth failure from a stale socket before removing.

4. (Low/UX) --relogin is a silent no-op for stdio servers

mcp.ts:318 only clears stored auth when "url" in serverConfig. Functionally correct (stdio has no OAuth), but --relogin on a stdio entry is silently ignored rather than rejected — mildly surprising.

5. (Nit) Global bin name mcp

package.json:22 registers a top-level mcp bin. That's a very generic name to claim on a user's PATH for a global install and is prone to collision with other tooling. The README's npx --package=… mcp form sidesteps it, but a global npm i -g grabs mcp outright — worth a conscious call-out (or a note in docs about the collision risk).

6. (Nit) Concurrency-critical code sits outside the coverage gate

ipc-glue.ts and stream-client.ts are excluded from the ≥90 per-file gate (documented, and it's in the To-do). Just flagging that these two files hold the trickiest logic in the PR (accept/half-close/abort races), so the exclusion is where I'd most want the gate — good to prioritize the "stable race matrix" follow-up.


Nothing here is a blocker for landing a base to build from. #1 is the one I'd fix before merge (or add to the To-do explicitly), since it's an easy-to-hit lingering-process leak rather than a documented gap.

  • Gather context (diff, spec, key files)
  • Review daemon IPC layer (protocol, framing, server, sessions)
  • Review session front-end (mcp.ts, dispatch, auth, formatting)
  • Review shared handlers + one-shot path
  • Review security considerations (socket perms, auth store)
  • Post consolidated review
    · branch v2/cli-v2-sessions

Arm the session daemon idle timer on start (and after failed connect) so
session-less spawns self-reap. JSON.stringify structured --metadata values.
Rename the session bin from mcp to mcpi to avoid PATH collisions, and clarify
--relogin as ignore-stored-OAuth (no-op for stdio).

Co-authored-by: Cursor <cursoragent@cursor.com>
@BobDickinson BobDickinson changed the title feat(cli): session-oriented mcp CLI with daemon (#1432) feat(cli): session-oriented mcpi CLI with daemon (#1432) Jul 20, 2026
@BobDickinson

BobDickinson commented Jul 20, 2026

Copy link
Copy Markdown
Contributor Author

OK, Cursor updated the PR description with responses to review comments, which isn't ideal. But I think generally we're good now.

@BobDickinson
BobDickinson requested a review from cliffhall July 20, 2026 02:42
Comment thread README.md Outdated
npx @modelcontextprotocol/inspector --cli # CLI
npx @modelcontextprotocol/inspector --cli # one-shot CLI
npx @modelcontextprotocol/inspector --tui # TUI
npx --package=@modelcontextprotocol/inspector mcpi servers/list # session CLI

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems like a toe-stubber (different command line for one client). Can the launcher not be the entry point and hand off to the mcpi?

@cliffhall cliffhall linked an issue Jul 23, 2026 that may be closed by this pull request
@BobDickinson

Copy link
Copy Markdown
Contributor Author

@cliffhall — following up on the concern about shipping a separate mcpi entrypoint alongside mcp-inspector.
Agreed that putting another bin on PATH / in the fat package was the wrong shape for an experimental session CLI. I've walked that back:

  • Not installed with the package — root bin is only mcp-inspector again; no mcpi shim from @modelcontextprotocol/inspector
  • Not in the fat tarballclients/mcpi/build is not on the root "files" allowlist (npm pack does not include it)
  • Not in user-facing docs — root README / CLI README no longer advertise mcpi; it's an experimental in-repo client (clients/mcpi, AGENTS.md / specs only)

mcpi stays buildable in the monorepo for evaluation (npm link from clients/mcpi if you want it locally). Whether it later becomes a launcher mode, a separate publish, or something else is still open — these changes just stop treating it as part of the shipped Inspector product.

Why merge this now

  1. One-shot CLI gains we want either way (shared with / motivated by the session work, but useful on mcp-inspector --cli):
    • --method servers/list / servers/show (catalog inspection without connecting)
    • --relogin (ignore stored OAuth for a URL-keyed connect)
    • --stored-auth-only (never start interactive OAuth)
    • Better connect-time OAuth UX (TTY OSC 8 link + browser open via cli-oauth-navigation / open)
    • Shared handlers/ extraction (run-method, etc.) so method execution isn't a 1k-line blob inside cli.ts
  2. Avoid bit-rot while we evaluate mcpi — landing the one-shot CLI changes (and keeping mcpi in-tree as an experimental sibling) means both surfaces stay building/tested against the same shared handlers instead of rotting on a side branch. Moving session/daemon code out of clients/cli makes that safer: the shipped one-shot package no longer carries the experimental session surface, while mcpi can still share handlers/ without living inside the CLI tree.

@BobDickinson

Copy link
Copy Markdown
Contributor Author

@claude review

@claude

claude Bot commented Jul 25, 2026

Copy link
Copy Markdown
Contributor

Claude finished @BobDickinson's task in 5m 32s —— View job


Review: mcpi extraction + one-shot CLI gains

Re-reviewed the whole branch against origin/v2/main (merge base b3b04c69), with focus on what changed since my last pass: the clients/mcpi extraction, the packaging walk-back, and the four addressed items. Static review only — node_modules isn't installed in this checkout, so I did not run validate/coverage.

Verified from the last round — all four hold: idle timer now armed at DaemonServer.start() (server.ts:98) and after a failed connect (sessions.ts:192); metaValueToString replaces String(v) in both the session (mcp.ts:809) and one-shot (cli.ts) paths; --relogin documented as a deliberate stdio no-op; bin renamed to mcpi.

Verified the walk-back claims — root bin is mcp-inspector only, root "files" has no clients/mcpi/build, and neither README advertises mcpi. The extraction is clean: clients/mcpi is a real sibling client with its own lint/test/coverage config wired into root validate/coverage.


1. (Medium) The "frozen" one-shot --method surface silently grew — including a method that hangs

Extracting runMethod into handlers/run-method.ts gave the one-shot CLI every method the session CLI needs, but cli.ts has no allowlist — options.method goes straight into runMethod (cli.ts:936). Two consequences:

  • mcp-inspector --cli --method logging/tail … now returns {kind: "stream"}, and consumeMethodOutcome (consume-outcome.ts:24-36) awaits a promise that only resolves on SIGINT. A one-shot invocation hangs forever; before this PR it exited with Unsupported method. Same for --method resources/subscribe. That's a live hazard for the CI/agent-loop use case the one-shot path exists for.
  • tasks/get|cancel|result, roots/set, and prompts/complete are now reachable but their required flags (--task-id, --roots-json, --complete-ref-type/--complete-ref/--complete-arg-name) are not defined on the one-shot parser — so they can only ever fail, while the new error text (run-method.ts:298) advertises tasks/* and roots/* as supported.

specification/v2_cli_v2.md still describes the one-shot surface as the core set + servers/list|show, so this is unintended reach rather than a designed expansion. Either gate one-shot to its documented methods (reject stream-kind outcomes with a "use mcpi" usage error), or add the flags and document them. Fix this →

2. (Medium) servers/show leaks pass-through config keys despite advertising "secrets redacted"

sanitizeServerConfig (handlers/servers-list.ts:124-132) redacts only stdio env. But mcpConfigToServerEntries (core/mcp/serverList.ts:419-427) routes every key that isn't in INSPECTOR_FIELD_KEYS onto config verbatim — so requestInit / eventSourceInit survive intact. A hand-written entry like

{ "type": "streamable-http", "url": "", "requestInit": { "headers": { "Authorization": "Bearer …" } } }

is printed in full by a command whose --help says "secrets redacted" — and this is the shape a user migrating from another client is most likely to hand-write. Suggest redacting requestInit.headers / eventSourceInit.headers (reusing isSensitiveHeader), or defaulting to redacting unknown pass-through keys rather than allowing them.

3. (Low) sanitizeServerSettings env redaction is conditional on the key

handlers/servers-list.ts:145-147:

env: settings.env.map((e) => ({ key: e.key, value: e.key ? REDACTED : e.value })),

e.key ? REDACTED : e.value emits the raw value whenever the key is empty. Looks like it should be an unconditional REDACTED (compare the headers line just above, which uses a real predicate).

4. (Low) One idle-timer path is still un-armed

SessionRegistry.connect() clears the idle timer at sessions.ts:172, but re-arms only inside the client.connect() catch. If createSessionClient() throws first (loadRunnerClientConfig / parseRunnerOAuthCallbackUrl on a bad MCP_OAUTH_CALLBACK_URL), or the reconnect await this.disconnect(...) at :176 throws, the timer is cleared and never re-armed — the same lingering-daemon class as the bug fixed in 5fbd3b9e, just via a narrower door. A try { … } finally { this.armIdleTimerIfEmpty() } around the body (or arming after every request in handleOutcome) makes it structural rather than per-path. Fix this →

5. (Low) callDaemon has no close handler → 60s stall on a daemon that dies mid-request

daemon/client.ts settles on data, error, or the timeout. A daemon that accepts the connection and then exits cleanly sends FIN with no error event, so the promise sits for the full timeoutMs (60s default) before reporting daemon_timeout. streamDaemon already has the socket.on("close", …) guard (stream-client.ts:138); mirroring it here turns a 60s stall into an immediate daemon_unreachable.

6. (Low/UX) Non-TTY session rule keys on stdout, so piping breaks MRU

requireExplicitSession() (dispatch.ts:95-98) returns true whenever !process.stdout.isTTY. That means the most ordinary CLI idiom —

mcpi connect myserver && mcpi tools/list | jq '.tools[].name'

— fails with "Explicit --session / @name is required in non-interactive mode" even though a human is at the keyboard. The spec does say "Non-TTY", but redirecting stdout isn't the same as being non-interactive; process.stdin.isTTY (or an explicit CI check) is a closer proxy and would keep pipelines working. Worth a conscious call rather than inheriting stdout's TTY-ness.


Docs & tooling

7. clients/cli/README.md has drifted from the code it documents (AGENTS.md "keep documentation files up to date"):

  • Line 148 still says the one-shot OAuth flow "Prints the authorization URL to the console (ConsoleNavigation)". cli.ts now uses createCliOAuthNavigation(), which prints an OSC 8 link and auto-opens the default browser when stderr is a TTY — a user-visible behavior change to the frozen path that deserves a line (including the "non-TTY never launches a browser" guarantee, which is the reassuring half).
  • The new one-shot flags --stored-auth-only and --relogin are in the spec and in --help, but not in the CLI README's flag documentation.
  • Line 272 still credits this client with runMcp(), which now lives in clients/mcpi.

8. Nothing typechecks clients/mcpi. clients/mcpi/tsconfig.json sets noEmit: true, but no script ever runs tscbuild is tsup/esbuild (strips types), and the eslint config is tseslint.configs.recommended without type-aware rules. Only clients/web runs tsc -b. This is pre-existing for cli/tui, but it bites harder here: mcpi reaches across packages via @inspector/cli/*../cli/src/*, so a signature change in clients/cli/src/handlers — the whole point of the shared layer — is caught only by mcpi's tests, never by a compiler. A "typecheck": "tsc -p . --noEmit" folded into validate would make the reach-in safe to refactor.

9. (Nit) prepacknpm run build now includes build:mcpi, which is correctly excluded from bin/files — so publish does a build whose output is thrown away. Harmless, just noting it since the goal was to decouple mcpi from the shipped product.


None of these block landing an experimental sibling client. #1 is the one I'd fix before merge — it's a regression in the shipped one-shot CLI (a hang where there used to be a clean error), not in the experimental surface. #2 is the other one worth closing since it's a redaction promise the command makes in its own help text.

On @cliffhall's launcher question: the walk-back (not on PATH, not in the tarball, not in user docs) addresses the toe-stubber directly, and specification/v2_cli_v2.md keeps "Launcher help polish" in the To-do for whenever mcpi earns a shipped entrypoint — that seems like the right place to leave it.

  • Gather context (diff vs origin/v2/main, spec, packaging)
  • Verify the four addressed review items
  • Review the clients/mcpi extraction + packaging claims
  • Review daemon (sessions, server, ensure, ipc, client, stream)
  • Review session front-end + shared clients/cli/handlers
  • Post consolidated review
    · branch v2/cli-v2-sessions

@BobDickinson

Copy link
Copy Markdown
Contributor Author

Addressed the latest Claude review items on this branch (now committed):

Fixed

  1. One-shot stream / session-only methodsONE_SHOT_METHODS allowlist in handlers/method-types.ts; cli.ts rejects logging/tail, resources/subscribe, tasks/*, etc. before connect so one-shot no longer hangs on SIGINT via consumeMethodOutcome.
  2. servers/show redactionsanitizeServerConfig redacts sensitive headers in requestInit / eventSourceInit; help-text promise holds for the common hand-written auth shape.
  3. Env redactionsanitizeServerSettings always redacts env values (including empty keys).
  4. Idle timer on early connect failureSessionRegistry.connect() re-arms idle on any failure after clearIdleTimer() (including createSessionClient / bad MCP_OAUTH_CALLBACK_URL), not only client.connect() failures.
  5. callDaemon close handler — peer clean-FIN without a response → immediate daemon_unreachable instead of waiting out the 60s timeout (mirrors streamDaemon).
  6. MRU + pipingrequireExplicitSession() keys off stdin.isTTY, so mcpi tools/list | jq still uses MRU when a human is at the keyboard.
  7. CLI README drift — OAuth UX (OSC 8 + browser open on TTY / no browser when non-TTY), --relogin / --stored-auth-only, method surface notes, dropped stale runMcp() credit.

Left as follow-ups (non-blocking)

  1. Add tsc --noEmit for clients/mcpi (and possibly align cli/tui later).
  2. Root prepack/build still builds mcpi then excludes it from the tarball — harmless; can drop from the publish build chain later if we want zero shipping coupling.

npm run ci is green locally after these changes.

@BobDickinson

Copy link
Copy Markdown
Contributor Author

@claude review

@claude

claude Bot commented Jul 25, 2026

Copy link
Copy Markdown
Contributor

Claude finished @BobDickinson's task in 4m 49s —— View job


Review: fixes round 3 (v2/cli-v2-sessions)

Re-reviewed against origin/v2/main (merge base b3b04c69), focused on a7b3cc38 plus a fresh pass over the daemon IPC edges. Static review only — node_modules isn't installed in this checkout, so I did not run validate/coverage.

All 7 claimed fixes verified, each with a test:

# Fix Where Test
1 One-shot allowlist handlers/method-types.ts:88 + cli.ts:884 method-types.test.ts, methods.test.ts
2 requestInit/eventSourceInit header redaction handlers/servers-list.ts:131 servers-list.test.ts
3 Unconditional env redaction servers-list.ts:150 ✓ (empty-key case)
4 Idle re-arm on any connect failure sessions.ts:220 daemon-coverage.test.ts (bad callback URL)
5 callDaemon close handler client.ts:103 daemon-coverage.test.ts (FIN-no-reply)
6 MRU keys off stdin.isTTY dispatch.ts:101 dispatch.test.ts
7 CLI README drift clients/cli/README.md

ONE_SHOT_METHODS is exactly the 9 methods the pre-PR cli.ts switch supported (checked against b3b04c69:clients/cli/src/cli.ts:343), so the allowlist restores the old surface rather than narrowing it — and servers/list/servers/show short-circuit above the check, so the error text stays truthful. Packaging walk-back re-verified: root bin is mcp-inspector only, "files" has no clients/mcpi/build, and neither README mentions mcpi.


1. (Low) streamDaemon still treats a pre-response FIN as success

The mirror of fix #5 wasn't applied on the stream side. stream-client.ts:138:

socket.on("close", () => {
  if (!settled) succeed();
});

The error handler two lines up correctly splits on streaming (:124-136) — soft-end after the ok frame, daemon_unreachable before it. The close handler doesn't. So a daemon that accepts the stream connection and then dies before writing the ok frame makes mcpi logging/tail exit 0 with no output, indistinguishable from a clean end. That's the exact failure callDaemon just stopped doing (there it was a 60s stall; here it's a silent success, which is worse for scripts). The spec To-do row "streamDaemon post-open errors" covers the streaming === true case only — this is the pre-open one. Fix this →

2. (Low) Header redaction misses the array form of HeadersInit

sanitizeInitRecord (servers-list.ts:176) only descends when isPlainObject(init.headers) — and isPlainObject explicitly rejects arrays (:168). RequestInit.headers is Headers | string[][] | Record<string,string>, so a hand-written entry using the pair form:

{ "type": "streamable-http", "url": "",
  "requestInit": { "headers": [["Authorization", "Bearer …"]] } }

still prints verbatim under a command whose help says "secrets redacted". Same class as the bug just fixed, one shape over. A few lines in sanitizeInitRecord to also map string[][] closes it.

3. (Nit) metaValueToString is duplicated verbatim across the two clients

clients/cli/src/cli.ts:459 and clients/mcpi/src/session/mcp.ts:809 are byte-identical, comment included. mcpi already reaches into @inspector/cli/handlers/*, so this belongs next to MethodArgs in handlers/method-types.ts — two copies of the metadata-encoding rule is precisely how the one-shot and session paths drift back apart on the thing fix #2-from-last-round was about.

4. (Nit) Method validation runs after the stored-auth work

cli.ts:884 (allowlist) and :855 (Method is required) both sit below the --use-stored-auth / --wait-for-auth block at :790, which can do a token refresh over the network. mcp-inspector --cli --method bogus --use-stored-auth … therefore burns a refresh before emitting a purely local usage error. Pre-existing ordering, cheap to hoist both checks above :790.

5. (Low) The daemon has no last-resort crash handler

daemon/run.ts installs SIGINT/SIGTERM but no unhandledRejection / uncaughtException. The accept path is a voided async IIFE (ipc-glue.ts:34); handleOutcome is total (catch-all at server.ts:135), so the realistic trigger is narrow — encodeResponse throwing on a non-serializable result — but if it ever fires, Node kills the daemon and daemon.sock / daemon.lock are orphaned (removeLockAndSocket only runs in stop()). Recovery does work on the next spawn via removeStaleDaemonSocket, but in between, mcpi daemon status reports a dead pid. Compounding it: ensure.ts:140 spawns with stdio: "ignore", so run.ts:27's process.stderr.write goes to /dev/null — a daemon that fails to start is completely silent, and the parent just sees the generic 10s daemon_start_timeout. A crash handler that calls stop(), and eventually a daemon log file, would make this debuggable.

6. (Doc) specification/v2_cli_v2.md testing section is stale after the extraction

Line 154 says "Per-file ≥90 coverage on measured clients/cli/src" and line 155 lists the exclusions as one set — but mcp-bin.ts / daemon/run.ts / ipc-glue.ts / stream-client.ts now live under clients/mcpi/src and are gated by clients/mcpi/vitest.config.ts, while index.ts is the cli one. Worth splitting into the two clients. While there: the one-shot --method table at :72-75 doesn't state that anything outside it is now a usage error before connect — that's the user-visible half of fix #1.


Notes, not asks:

  • Reconnect (mcpi connect <name> over a live session) leaves an idle timer armed: the inner disconnect arms it at sessions.ts:245 when the registry empties, and the successful re-connect doesn't clear it. It's harmless only because the timer callback re-checks sessions.size === 0 at :265 — please keep that guard.
  • --relogin's documented conflicts (--stored-auth-only / --use-stored-auth / --wait-for-auth) are actually enforced at cli.ts:727-734. ✓

Nothing here blocks the merge — #1 is the one I'd take, since it's a two-line symmetry fix to the same bug class you just closed on callDaemon, and a silent exit 0 is the hardest failure mode to notice in a script. #2 and #6 are quick follow-ups.

  • Gather context (diff vs origin/v2/main, latest commit)
  • Verify the 7 claimed fixes + their tests
  • Fresh pass over daemon IPC / packaging / docs
  • Post consolidated review
    · branch v2/cli-v2-sessions

@BobDickinson

Copy link
Copy Markdown
Contributor Author

Addressed the follow-up Claude review items (commit ab222089):

Fixed

  1. streamDaemon pre-response FINclose fails with daemon_unreachable if the ok frame never arrived (no more silent exit 0). Mid-stream close still soft-succeeds.
  2. Header pair-array redactionsanitizeInitRecord redacts [["Authorization","…"], …] as well as object-form headers.
  3. Shared metaValueToString — single copy in handlers/method-types.ts; one-shot + mcpi both import it.
  4. Method validation before stored-auth--method required + allowlist run before --use-stored-auth / --wait-for-auth, so bad methods fail locally without a refresh round-trip.
  5. Spec testing sectionv2_cli_v2.md split cli vs mcpi coverage; documents one-shot allowlist / reject-before-connect.

Still follow-up (non-blocking)

  1. Daemon last-resort crash handler / logging (unhandledRejection, spawn stdio, etc.).

npm run ci green locally after these changes.

BobDickinson and others added 2 commits July 25, 2026 13:44
Resolve conflicts keeping the mcpi client extraction while adopting
validate:core, cli/tui typecheck, and related AGENTS/package.json updates
from main. Fix servers/list config narrowing for the new CLI typecheck gate.

Co-authored-by: Cursor <cursoragent@cursor.com>
Narrow on sse/streamable-http first so optional stdio `type` does not
block access to `url` under the new cli typecheck gate from v2/main.

Co-authored-by: Cursor <cursoragent@cursor.com>
@BobDickinson

Copy link
Copy Markdown
Contributor Author

@cliffhall - I think this is mergeable now - it gets us some improved one-shot CLI functionality and gets the mcpi code into v2/main, but out of the package/bundle and hidden from end users (that don't build from source), per:
#1727 (comment)

@BobDickinson

Copy link
Copy Markdown
Contributor Author

Splitting this PR to address release concerns about landing mcpi this close to a cut:

  1. CLI improvements onlyCLI: catalog listing, auth flags, and OAuth browser UX #1782 (closes CLI: catalog listing, auth flags, and OAuth browser UX #1781) — catalog listing, auth flags, OAuth browser UX, handlers. No clients/mcpi.
  2. Experimental mcpi session clientfeat(mcpi): experimental session CLI client (#1432) #1783 (closes Inspector CLI v2 #1432) — stacked on CLI: catalog listing, auth flags, and OAuth browser UX #1782; not in the published tarball.

Closing this PR in favor of those two. The tip of v2/cli-v2-sessions remains as a reference.

@BobDickinson

Copy link
Copy Markdown
Contributor Author

Superseded by #1782 (CLI) + #1783 (mcpi).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

v2 Issues and PRs for v2

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Inspector CLI v2

2 participants