From b60fabf837b02ac04a72f15a48949af8df80ab2c Mon Sep 17 00:00:00 2001 From: cliffhall Date: Tue, 6 Oct 2026 23:51:40 -0400 Subject: [PATCH 1/2] chore(core): promote the CLI surface mcpdo shares into core/cli (#2461) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit mcpdo compiled against clients/cli/src through a temporary build-time @inspector/cli alias. Move the shared node-only closure it reached for — error-handler, awaitable-log, style, cliOAuth, cli-oauth-navigation and the handlers run-method / method-types / output-file / format-output / collect-app-info / skills-verify / servers-list / connect-timeout — into core/cli/, keeping the same layout, and import it through the ordinary @inspector/core alias from both clients. - Drop the @inspector/cli alias from mcpdo's tsup, vitest and tsconfigs. - Keep core/cli under clients/cli's per-file coverage gate (its suites still exercise it); web's core/* whitelist deliberately omits it. - CliExitCodeError drops parameter properties: core/ is typechecked under web's erasableSyntaxOnly. - Update AGENTS.md, the project-structure skill, the mcpdo README and the specs that pointed at the old paths. Co-Authored-By: Claude Opus 5.5 Signed-off-by: cliffhall --- .claude/skills/project-structure/SKILL.md | 5 +++- AGENTS.md | 6 ++-- clients/cli/__tests__/README.md | 3 +- .../__tests__/cli-oauth-navigation.test.ts | 2 +- clients/cli/__tests__/cliOAuth.test.ts | 2 +- clients/cli/__tests__/completion.test.ts | 2 +- .../__tests__/consume-outcome-stream.test.ts | 2 +- clients/cli/__tests__/emit-result.test.ts | 2 +- clients/cli/__tests__/error-handler.test.ts | 2 +- clients/cli/__tests__/format-output.test.ts | 2 +- clients/cli/__tests__/helpers/cli-runner.ts | 4 +-- .../cli/__tests__/helpers/oauth-test-fakes.ts | 2 +- clients/cli/__tests__/method-types.test.ts | 2 +- .../cli/__tests__/oauth-interactive.test.ts | 2 +- clients/cli/__tests__/output-file.test.ts | 4 +-- .../cli/__tests__/run-method-mocks.test.ts | 2 +- .../cli/__tests__/run-method-skills.test.ts | 6 ++-- clients/cli/__tests__/run-method.test.ts | 2 +- .../cli/__tests__/schema-lint-report.test.ts | 5 +++- clients/cli/__tests__/servers-list.test.ts | 2 +- .../cli/__tests__/skills-verify-cli.test.ts | 2 +- clients/cli/__tests__/style.test.ts | 2 +- clients/cli/src/cli.ts | 30 +++++++++++-------- clients/cli/src/completion.ts | 4 +-- clients/cli/src/handlers/consume-outcome.ts | 15 ++++++++-- clients/cli/src/handlers/emit-result.ts | 18 ++++++++--- .../cli/src/handlers/schema-lint-report.ts | 4 +-- clients/cli/src/index.ts | 2 +- clients/cli/vitest.config.ts | 9 +++++- clients/mcpdo/README.md | 2 +- .../__tests__/auth-names-commands.test.ts | 20 ++++++++----- clients/mcpdo/__tests__/auth-names.test.ts | 2 +- clients/mcpdo/__tests__/authorize.test.ts | 4 +-- .../__tests__/connection-stored-auth.test.ts | 2 +- .../__tests__/daemon-connections.test.ts | 2 +- .../mcpdo/__tests__/daemon-coverage.test.ts | 2 +- .../__tests__/daemon-elicitation-park.test.ts | 2 +- clients/mcpdo/__tests__/daemon-paths.test.ts | 2 +- .../mcpdo/__tests__/daemon-private.test.ts | 2 +- .../mcpdo/__tests__/daemon-rpc-abort.test.ts | 2 +- clients/mcpdo/__tests__/daemon-stream.test.ts | 5 +++- .../__tests__/elicitation-prompt.test.ts | 2 +- clients/mcpdo/__tests__/ema-commands.test.ts | 2 +- clients/mcpdo/__tests__/ema.test.ts | 2 +- clients/mcpdo/__tests__/form-prompt.test.ts | 2 +- .../mcpdo/__tests__/format-connection.test.ts | 7 +++-- clients/mcpdo/__tests__/helpers/mcp-runner.ts | 2 +- .../mcpdo/__tests__/mcp-auth-coverage.test.ts | 5 +++- clients/mcpdo/src/connection/auth-helper.ts | 5 +++- clients/mcpdo/src/connection/auth-names.ts | 2 +- clients/mcpdo/src/connection/authorize.ts | 11 ++++--- clients/mcpdo/src/connection/dispatch.ts | 6 ++-- .../src/connection/elicitation-prompt.ts | 2 +- clients/mcpdo/src/connection/ema.ts | 7 +++-- clients/mcpdo/src/connection/form-prompt.ts | 2 +- .../mcpdo/src/connection/format-connection.ts | 13 ++++---- clients/mcpdo/src/connection/format-human.ts | 2 +- clients/mcpdo/src/connection/mcp.ts | 19 +++++++----- clients/mcpdo/src/connection/stored-auth.ts | 5 +++- clients/mcpdo/src/daemon/auth.ts | 5 +++- clients/mcpdo/src/daemon/client.ts | 5 +++- clients/mcpdo/src/daemon/connections.ts | 5 +++- clients/mcpdo/src/daemon/elicitation-park.ts | 5 +++- clients/mcpdo/src/daemon/ensure.ts | 5 +++- clients/mcpdo/src/daemon/protocol.ts | 2 +- clients/mcpdo/src/daemon/server.ts | 6 ++-- clients/mcpdo/src/daemon/stream-client.ts | 5 +++- clients/mcpdo/src/mcp-bin.ts | 2 +- clients/mcpdo/tsconfig.json | 12 ++++---- clients/mcpdo/tsconfig.test.json | 1 - clients/mcpdo/tsup.config.ts | 12 +++----- clients/mcpdo/vitest.config.ts | 10 +------ core/auth/node/runner-interactive-oauth.ts | 2 +- .../src => core/cli}/cli-oauth-navigation.ts | 4 +-- {clients/cli/src => core/cli}/cliOAuth.ts | 16 +++++----- .../cli/src => core/cli}/error-handler.ts | 20 +++++++------ .../cli}/handlers/collect-app-info.ts | 8 ++--- .../cli}/handlers/connect-timeout.ts | 2 +- .../cli}/handlers/format-output.ts | 0 .../src => core/cli}/handlers/method-types.ts | 6 ++-- .../src => core/cli}/handlers/output-file.ts | 2 +- .../src => core/cli}/handlers/run-method.ts | 8 ++--- .../src => core/cli}/handlers/servers-list.ts | 6 ++-- .../cli}/handlers/skills-verify.ts | 2 +- {clients/cli/src => core/cli}/style.ts | 0 .../src => core/cli}/utils/awaitable-log.ts | 0 core/mcp/uriTemplate.ts | 2 +- scripts/smoke-cli.mjs | 2 +- specification/v2_auth_mid_session.md | 4 +-- specification/v2_cli_v2.md | 8 ++--- 90 files changed, 268 insertions(+), 187 deletions(-) rename {clients/cli/src => core/cli}/cli-oauth-navigation.ts (97%) rename {clients/cli/src => core/cli}/cliOAuth.ts (96%) rename {clients/cli/src => core/cli}/error-handler.ts (96%) rename {clients/cli/src => core/cli}/handlers/collect-app-info.ts (78%) rename {clients/cli/src => core/cli}/handlers/connect-timeout.ts (96%) rename {clients/cli/src => core/cli}/handlers/format-output.ts (100%) rename {clients/cli/src => core/cli}/handlers/method-types.ts (97%) rename {clients/cli/src => core/cli}/handlers/output-file.ts (99%) rename {clients/cli/src => core/cli}/handlers/run-method.ts (98%) rename {clients/cli/src => core/cli}/handlers/servers-list.ts (98%) rename {clients/cli/src => core/cli}/handlers/skills-verify.ts (98%) rename {clients/cli/src => core/cli}/style.ts (100%) rename {clients/cli/src => core/cli}/utils/awaitable-log.ts (100%) diff --git a/.claude/skills/project-structure/SKILL.md b/.claude/skills/project-structure/SKILL.md index fedeec6a60..8928a71a65 100644 --- a/.claude/skills/project-structure/SKILL.md +++ b/.claude/skills/project-structure/SKILL.md @@ -46,6 +46,7 @@ an MCP server, the request/response lifecycle, and a set of state stores. | `core/auth/` | OAuth end to end — providers, discovery, storage, endpoint overrides, scopes, revocation, mid-session recovery — split into isomorphic logic plus `browser/`, `node/` and `remote/` backends | | `core/auth/node/` | Node OAuth storage + loopback callback server, **and** the `SecretStore` backends (keychain / file / memory) and their selection policy | | `core/extension//` | Host-side adapters for MCP extensions whose protocol an upstream SDK owns. `tasks/` wraps `@modelcontextprotocol/ext-tasks` with what that package leaves to its host: the raw `rawDispatch` channel, progress routing, error identity, and task-view conversions. Imports nothing from `InspectorClient`; host state arrives through narrow interfaces | +| `core/cli/` | Node-only surface the one-shot CLI and `mcpdo` both run on (#2461): `error-handler` (exit codes, `CliExitCodeError`, the error envelope), the method handlers and their option/format types (`handlers/`), the interactive OAuth connect flow (`cliOAuth`, `cli-oauth-navigation`) and output helpers (`style`, `utils/awaitable-log`). One-shot-only output (`emit-result`, `consume-outcome`, `servers-write`, …) stays in `clients/cli/src` | | `core/client/` | Install-level client config (`client.json`): browser-safe parse plus Node load/save, remote backend, secrets, runner | | `core/json/` | JSON + parameter/argument conversion; the schema normalizations all three form builders share (nullable unions, root composition) and the tool-schema portability lint | | `core/react/` | React hooks over the state stores — consumed by both the web and TUI React trees. Every subscription reads its snapshot **during render** via `useSyncExternalStore` (#1955); `useStoreSnapshot.ts` caches the fresh-value-per-read getters | @@ -55,7 +56,9 @@ an MCP server, the request/response lifecycle, and a set of state stores. `core/` is isomorphic (browser + Node) and has **no `package.json`** — it is not published on its own. Its tests live in `clients/web/src/test/core/`, and its -browser-consumed runtime is inside the web coverage gate. +browser-consumed runtime is inside the web coverage gate. **`core/cli/` is the +exception**: it is Node-only, its tests are `clients/cli/__tests__/`, and it is +gated by `clients/cli`'s coverage run instead. ## `clients/web/server/` — the Node backend diff --git a/AGENTS.md b/AGENTS.md index 6c0844e0f2..6ed5e9105d 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -49,6 +49,8 @@ inspector/ │ └── launcher/ The `mcp-inspector` bin; dispatches to web/cli/tui in-process ├── core/ Shared code, consumed via the `@inspector/core` alias (no package.json) │ ├── auth/ OAuth end to end + the per-server SecretStore backends +│ ├── cli/ Node-only surface the one-shot CLI and mcpdo share (method handlers, +│ │ exit codes, interactive OAuth flow, output styling); tested + gated by clients/cli │ ├── client/ Install-level client config (`client.json`) │ ├── extension/ Host-side adapters for MCP extensions whose protocol an upstream SDK owns │ │ └── tasks/ The Inspector's glue around `@modelcontextprotocol/ext-tasks` (raw dispatch, progress routing) @@ -410,11 +412,11 @@ When asked to respond to a code review of a PR: The _procedure_ — where a given test file goes, which command runs it, how to diagnose a failing gate — is the `testing` skill. These are the rules. -- **Ensure all code has corresponding tests.** New code must clear **≥ 90 on all four dimensions** — lines, statements, functions, and branches — per file. This gate is enforced by each client's `test:coverage` across `clients/web`, `clients/cli`, `clients/tui`, `clients/launcher`, and (experimentally) `clients/mcpdo`, and **CI enforces it**: a PR that drops any file below 90 on any dimension fails. **mcpdo** excludes only true bootstraps from the gate (`src/mcp-bin.ts`, `src/daemon/run.ts` — see `clients/mcpdo/vitest.config.ts`); its build-time `@inspector/cli` alias reaches into `clients/cli/src` for shared handlers/error-handler/OAuth helpers (temporary, not a published API — #2461 tracks promoting that surface to a shared area). +- **Ensure all code has corresponding tests.** New code must clear **≥ 90 on all four dimensions** — lines, statements, functions, and branches — per file. This gate is enforced by each client's `test:coverage` across `clients/web`, `clients/cli`, `clients/tui`, `clients/launcher`, and (experimentally) `clients/mcpdo`, and **CI enforces it**: a PR that drops any file below 90 on any dimension fails. **mcpdo** excludes only true bootstraps from the gate (`src/mcp-bin.ts`, `src/daemon/run.ts` — see `clients/mcpdo/vitest.config.ts`). The node-only surface mcpdo shares with the one-shot CLI (`core/cli/` — handlers, error-handler, OAuth helpers; #2461) is gated by `clients/cli`'s coverage run, whose suites exercise it; see `clients/cli/vitest.config.ts`. - **A genuinely-unreachable branch is annotated at the source, never waved through by lowering the gate.** Use a justified `/* v8 ignore … -- */`. Acceptable reasons: happy-dom-inherent paths (Mantine portal mount points, `useMediaQuery` fallbacks, `typeof window` SSR guards); React StrictMode effect-replay blocks; and provably-dead defensive guards (a `?? fallback` for a value the types guarantee non-null, a `Select.onChange` receiving a value outside the allowed list). Reach for it only when the branch is genuinely impossible to exercise. - **In unit tests that expect error output, suppress it from the console.** - **Test placement — side-by-side by default, `src/test/` only for what can't be co-located, and the Node clients are different.** - - **`clients/web`**: `.test.tsx` **next to the source** — components, hooks, `lib/`, `utils/`. A web-owned test living under `src/test/` instead is a bug. `src/test/` is for the three things that cannot be co-located: tests of the repo-root **`core/`** package (`src/test/core/…`, mirroring the `core/` layout — it lives outside `clients/web/` and has no harness of its own); the **`integration`** project (`src/test/integration/…` — _placement is the manifest_, picked up by a folder glob, with no enumeration to keep in sync); and **shared test infrastructure** (`renderWithMantine.tsx`, `setup.ts`, `fixtures/`). + - **`clients/web`**: `.test.tsx` **next to the source** — components, hooks, `lib/`, `utils/`. A web-owned test living under `src/test/` instead is a bug. `src/test/` is for the three things that cannot be co-located: tests of the repo-root **`core/`** package (`src/test/core/…`, mirroring the `core/` layout — it lives outside `clients/web/` and has no harness of its own) — **except `core/cli/`**, the Node-only surface the CLI and mcpdo share, whose tests stay in `clients/cli/__tests__/` and whose coverage `clients/cli` gates (#2461); the **`integration`** project (`src/test/integration/…` — _placement is the manifest_, picked up by a folder glob, with no enumeration to keep in sync); and **shared test infrastructure** (`renderWithMantine.tsx`, `setup.ts`, `fixtures/`). - **`clients/cli`, `clients/mcpdo`, `clients/tui`, `clients/launcher`**: **all** tests in a top-level **`__tests__/`**, not beside their source. Their `tsconfig.json` excludes `**/*.test.*`, so a co-located test lands in **no** tsconfig project and fails `npm run verify:typecheck-coverage`. - **Root tooling**: a `scripts/*.mjs` helper with pure logic gets a sibling `*.test.mjs`. Keep that exact filename — `node --test` silently _skips_ a file its glob misses and still exits 0. - **Render Ink components through the TUI's own `render`** (`clients/tui/__tests__/helpers/renderTui.tsx`), never `ink-testing-library`'s directly. It is the same function with every frame ANSI-stripped, which is what keeps an assertion on styled text from depending on the ambient environment: Ink writes styling _inside_ the styled run, so `Info` reaches the frame buffer with escapes between `I` and `nfo` and `toContain("Info")` fails. It only bites where chalk emits color — a developer whose shell exports `FORCE_COLOR` — so CI is green on a suite that is broken for them (#2207). A test that genuinely needs the raw bytes reads `stdout.lastFrame()` off the returned instance. diff --git a/clients/cli/__tests__/README.md b/clients/cli/__tests__/README.md index dcaf3affcb..122aea452b 100644 --- a/clients/cli/__tests__/README.md +++ b/clients/cli/__tests__/README.md @@ -3,7 +3,8 @@ Tests live under `__tests__/` and run via Vitest. - Most tests import `runCli()` **in-process** (see `helpers/cli-runner.ts`) so - `clients/cli/src` is measured under the coverage gate. Suite-wide + `clients/cli/src` and the shared `core/cli` surface are measured under the + coverage gate. Suite-wide `helpers/mock-open-url.ts` (vitest `setupFiles`) mocks core's `openUrl` so an armed interactive OAuth path cannot launch a real browser. - `e2e.test.ts` (and root `scripts/smoke-cli.mjs`) spawn the built binary for diff --git a/clients/cli/__tests__/cli-oauth-navigation.test.ts b/clients/cli/__tests__/cli-oauth-navigation.test.ts index 4236e413c4..a9d0c9e49f 100644 --- a/clients/cli/__tests__/cli-oauth-navigation.test.ts +++ b/clients/cli/__tests__/cli-oauth-navigation.test.ts @@ -3,7 +3,7 @@ import { createCliOAuthNavigation, isCliAutoOpenForced, resolveCliAutoOpenEnabled, -} from "../src/cli-oauth-navigation.js"; +} from "@inspector/core/cli/cli-oauth-navigation.js"; import { openUrl } from "@inspector/core/node/openUrl.js"; // Local mock (in addition to suite-wide setupFiles) so this file owns a diff --git a/clients/cli/__tests__/cliOAuth.test.ts b/clients/cli/__tests__/cliOAuth.test.ts index 99f288a3c5..c7eb242657 100644 --- a/clients/cli/__tests__/cliOAuth.test.ts +++ b/clients/cli/__tests__/cliOAuth.test.ts @@ -10,7 +10,7 @@ import { assertInteractiveOAuthAllowed, withCliAuthRecoveryRetry, STEP_UP_PIPE_TIMEOUT_MS, -} from "../src/cliOAuth.js"; +} from "@inspector/core/cli/cliOAuth.js"; import type { MCPServerConfig } from "@inspector/core/mcp/types.js"; import { createInterface } from "node:readline/promises"; import { diff --git a/clients/cli/__tests__/completion.test.ts b/clients/cli/__tests__/completion.test.ts index ff765abb7d..66951750f2 100644 --- a/clients/cli/__tests__/completion.test.ts +++ b/clients/cli/__tests__/completion.test.ts @@ -23,7 +23,7 @@ import { renderCompletion, type CompletionShell, } from "../src/completion.js"; -import { ONE_SHOT_METHODS } from "../src/handlers/method-types.js"; +import { ONE_SHOT_METHODS } from "@inspector/core/cli/handlers/method-types.js"; async function script(shell: CompletionShell): Promise { const result = await runCli(["--completion", shell]); diff --git a/clients/cli/__tests__/consume-outcome-stream.test.ts b/clients/cli/__tests__/consume-outcome-stream.test.ts index f527c0e172..bbcb937bb3 100644 --- a/clients/cli/__tests__/consume-outcome-stream.test.ts +++ b/clients/cli/__tests__/consume-outcome-stream.test.ts @@ -1,6 +1,6 @@ import { describe, it, expect, afterEach, vi } from "vitest"; import { consumeMethodOutcome } from "../src/handlers/consume-outcome.js"; -import type { MethodOutcome } from "../src/handlers/method-types.js"; +import type { MethodOutcome } from "@inspector/core/cli/handlers/method-types.js"; /** * The long-lived stream path's stdout error handling (#2412). A reader that diff --git a/clients/cli/__tests__/emit-result.test.ts b/clients/cli/__tests__/emit-result.test.ts index dfa16b379d..1776e385ce 100644 --- a/clients/cli/__tests__/emit-result.test.ts +++ b/clients/cli/__tests__/emit-result.test.ts @@ -1,6 +1,6 @@ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; import { emitResult, collectAppInfo } from "../src/cli.js"; -import { CliExitCodeError } from "../src/error-handler.js"; +import { CliExitCodeError } from "@inspector/core/cli/error-handler.js"; import type { InspectorClient } from "@inspector/core/mcp/index.js"; /** diff --git a/clients/cli/__tests__/error-handler.test.ts b/clients/cli/__tests__/error-handler.test.ts index 342a7b4b95..b08a9d28d4 100644 --- a/clients/cli/__tests__/error-handler.test.ts +++ b/clients/cli/__tests__/error-handler.test.ts @@ -5,7 +5,7 @@ import { classifyError, formatErrorOutput, handleError, -} from "../src/error-handler.js"; +} from "@inspector/core/cli/error-handler.js"; import { UnauthorizedError } from "@modelcontextprotocol/client"; import { SecretFileLockHeldError, diff --git a/clients/cli/__tests__/format-output.test.ts b/clients/cli/__tests__/format-output.test.ts index 14d633207a..3764bce340 100644 --- a/clients/cli/__tests__/format-output.test.ts +++ b/clients/cli/__tests__/format-output.test.ts @@ -1,5 +1,5 @@ import { describe, it, expect, afterEach } from "vitest"; -import { writeFormattedResult } from "../src/handlers/format-output.js"; +import { writeFormattedResult } from "@inspector/core/cli/handlers/format-output.js"; describe("writeFormattedResult", () => { let originalWrite: typeof process.stdout.write; diff --git a/clients/cli/__tests__/helpers/cli-runner.ts b/clients/cli/__tests__/helpers/cli-runner.ts index d5c8abe66e..af0cdb1220 100644 --- a/clients/cli/__tests__/helpers/cli-runner.ts +++ b/clients/cli/__tests__/helpers/cli-runner.ts @@ -1,5 +1,5 @@ import { runCli as invokeCli } from "../../src/cli.js"; -import { formatErrorOutput } from "../../src/error-handler.js"; +import { formatErrorOutput } from "@inspector/core/cli/error-handler.js"; export interface CliResult { exitCode: number | null; @@ -45,7 +45,7 @@ function captureWrite(append: (text: string) => void) { /** * Run the CLI **in-process** by importing and invoking `runCli` directly, so - * its source (`clients/cli/src/**`) is measured under vitest's coverage + * its source (`clients/cli/src/**`, plus the shared `core/cli/**`) is measured under vitest's coverage * instrumentation. The previous implementation spawned `build/index.js` as a * subprocess, which left CLI source invisible to coverage (#1484). * diff --git a/clients/cli/__tests__/helpers/oauth-test-fakes.ts b/clients/cli/__tests__/helpers/oauth-test-fakes.ts index 8c5e91f9eb..46a8a00837 100644 --- a/clients/cli/__tests__/helpers/oauth-test-fakes.ts +++ b/clients/cli/__tests__/helpers/oauth-test-fakes.ts @@ -4,7 +4,7 @@ import { DEFAULT_TASK_TTL_MS, } from "@inspector/core/mcp/types.js"; import type { InspectorServerSettings } from "@inspector/core/mcp/types.js"; -import type { CliOAuthClient } from "../../src/cliOAuth.js"; +import type { CliOAuthClient } from "@inspector/core/cli/cliOAuth.js"; /** * Typed mock factories for the CLI OAuth tests. They exist so a test can supply diff --git a/clients/cli/__tests__/method-types.test.ts b/clients/cli/__tests__/method-types.test.ts index 76edddaf24..3b40bab0fb 100644 --- a/clients/cli/__tests__/method-types.test.ts +++ b/clients/cli/__tests__/method-types.test.ts @@ -3,7 +3,7 @@ import { isOneShotMethod, ONE_SHOT_METHODS, CONNECTION_RPC_METHODS, -} from "../src/handlers/method-types.js"; +} from "@inspector/core/cli/handlers/method-types.js"; describe("CONNECTION_RPC_METHODS", () => { it("lists the full RPC method set supported by runMethod", () => { diff --git a/clients/cli/__tests__/oauth-interactive.test.ts b/clients/cli/__tests__/oauth-interactive.test.ts index abc5198264..b73a725cef 100644 --- a/clients/cli/__tests__/oauth-interactive.test.ts +++ b/clients/cli/__tests__/oauth-interactive.test.ts @@ -28,7 +28,7 @@ import { NodeOAuthStorage } from "@inspector/core/auth/node/index.js"; import { connectInspectorWithOAuth, withCliAuthRecoveryRetry, -} from "../src/cliOAuth.js"; +} from "@inspector/core/cli/cliOAuth.js"; import type { MCPServerConfig } from "@inspector/core/mcp/types.js"; import { makeFakeServerSettings } from "./helpers/oauth-test-fakes.js"; diff --git a/clients/cli/__tests__/output-file.test.ts b/clients/cli/__tests__/output-file.test.ts index 7d9c341a67..d78d2a0b5c 100644 --- a/clients/cli/__tests__/output-file.test.ts +++ b/clients/cli/__tests__/output-file.test.ts @@ -11,8 +11,8 @@ import { renderResultForFile, validateOutputOptions, writeResultFile, -} from "../src/handlers/output-file.js"; -import { CliExitCodeError } from "../src/error-handler.js"; +} from "@inspector/core/cli/handlers/output-file.js"; +import { CliExitCodeError } from "@inspector/core/cli/error-handler.js"; import { emitResult } from "../src/handlers/emit-result.js"; /** diff --git a/clients/cli/__tests__/run-method-mocks.test.ts b/clients/cli/__tests__/run-method-mocks.test.ts index 437f9515a5..e5df08b2b5 100644 --- a/clients/cli/__tests__/run-method-mocks.test.ts +++ b/clients/cli/__tests__/run-method-mocks.test.ts @@ -1,5 +1,5 @@ import { describe, it, expect, vi } from "vitest"; -import { runMethod } from "../src/handlers/run-method.js"; +import { runMethod } from "@inspector/core/cli/handlers/run-method.js"; import type { InspectorClient } from "@inspector/core/mcp/index.js"; function mockClient(overrides: Partial = {}): InspectorClient { diff --git a/clients/cli/__tests__/run-method-skills.test.ts b/clients/cli/__tests__/run-method-skills.test.ts index 9931995db2..7b1d239848 100644 --- a/clients/cli/__tests__/run-method-skills.test.ts +++ b/clients/cli/__tests__/run-method-skills.test.ts @@ -1,10 +1,10 @@ import { describe, it, expect, vi } from "vitest"; -import { runMethod } from "../src/handlers/run-method.js"; +import { runMethod } from "@inspector/core/cli/handlers/run-method.js"; import { skillVerificationExitCode, summarizeSkillVerification, -} from "../src/handlers/skills-verify.js"; -import { EXIT_CODES } from "../src/error-handler.js"; +} from "@inspector/core/cli/handlers/skills-verify.js"; +import { EXIT_CODES } from "@inspector/core/cli/error-handler.js"; import type { InspectorClient } from "@inspector/core/mcp/index.js"; import type { SkillEntry } from "@inspector/core/mcp/skillsSchemas.js"; import { sha256Digest } from "@inspector/core/mcp/skills.js"; diff --git a/clients/cli/__tests__/run-method.test.ts b/clients/cli/__tests__/run-method.test.ts index d859922366..1c27fac5e3 100644 --- a/clients/cli/__tests__/run-method.test.ts +++ b/clients/cli/__tests__/run-method.test.ts @@ -2,7 +2,7 @@ import { describe, it, expect, afterEach, vi } from "vitest"; import { getTestMcpServerCommand } from "@modelcontextprotocol/inspector-test-server"; import { InspectorClient } from "@inspector/core/mcp/index.js"; import { createTransportNode } from "@inspector/core/mcp/node/index.js"; -import { runMethod } from "../src/handlers/run-method.js"; +import { runMethod } from "@inspector/core/cli/handlers/run-method.js"; import { consumeMethodOutcome } from "../src/handlers/consume-outcome.js"; describe("runMethod", () => { diff --git a/clients/cli/__tests__/schema-lint-report.test.ts b/clients/cli/__tests__/schema-lint-report.test.ts index ca0994b8fe..b063752339 100644 --- a/clients/cli/__tests__/schema-lint-report.test.ts +++ b/clients/cli/__tests__/schema-lint-report.test.ts @@ -1,6 +1,9 @@ import { describe, it, expect, beforeEach, afterEach } from "vitest"; import { emitResult, runCli } from "../src/cli.js"; -import { CliExitCodeError, EXIT_CODES } from "../src/error-handler.js"; +import { + CliExitCodeError, + EXIT_CODES, +} from "@inspector/core/cli/error-handler.js"; import { lintListResult, toolsFromResult, diff --git a/clients/cli/__tests__/servers-list.test.ts b/clients/cli/__tests__/servers-list.test.ts index b425536252..41d34447cd 100644 --- a/clients/cli/__tests__/servers-list.test.ts +++ b/clients/cli/__tests__/servers-list.test.ts @@ -14,7 +14,7 @@ import { sanitizeServerSettings, showServerEntry, summarizeServerConfig, -} from "../src/handlers/servers-list.js"; +} from "@inspector/core/cli/handlers/servers-list.js"; import type { InspectorServerSettings, MCPServerConfig, diff --git a/clients/cli/__tests__/skills-verify-cli.test.ts b/clients/cli/__tests__/skills-verify-cli.test.ts index fb03ebbc43..704154294e 100644 --- a/clients/cli/__tests__/skills-verify-cli.test.ts +++ b/clients/cli/__tests__/skills-verify-cli.test.ts @@ -6,7 +6,7 @@ import { import type { SkillVerifyReport } from "@inspector/core/mcp/skillsVerification.js"; import { runCli } from "../src/cli.js"; import { consumeMethodOutcome } from "../src/handlers/consume-outcome.js"; -import { EXIT_CODES } from "../src/error-handler.js"; +import { EXIT_CODES } from "@inspector/core/cli/error-handler.js"; import { runCli as runCliCaptured, type CliResult, diff --git a/clients/cli/__tests__/style.test.ts b/clients/cli/__tests__/style.test.ts index f9b8cfeffd..236875a8dd 100644 --- a/clients/cli/__tests__/style.test.ts +++ b/clients/cli/__tests__/style.test.ts @@ -4,7 +4,7 @@ import { resolveAnsiEnabled, styleFromOpts, PLAIN, -} from "../src/style.js"; +} from "@inspector/core/cli/style.js"; describe("resolveAnsiEnabled", () => { it("is off for --plain, json, NO_COLOR, and non-TTY", () => { diff --git a/clients/cli/src/cli.ts b/clients/cli/src/cli.ts index 15e3cb52a5..25f55d5378 100644 --- a/clients/cli/src/cli.ts +++ b/clients/cli/src/cli.ts @@ -4,7 +4,7 @@ import { registerCompletionOption, } from "./completion.js"; type McpResponse = Record; -import { awaitableLog } from "./utils/awaitable-log.js"; +import { awaitableLog } from "@inspector/core/cli/utils/awaitable-log.js"; import type { InspectorServerSettings, MCPServerConfig, @@ -15,19 +15,22 @@ import { eraToVersionNegotiation } from "@inspector/core/mcp/types.js"; import { DEFAULT_CONNECT_TIMEOUT_MS, withConnectTimeout, -} from "./handlers/connect-timeout.js"; -import { listServerEntries, showServerEntry } from "./handlers/servers-list.js"; +} from "@inspector/core/cli/handlers/connect-timeout.js"; +import { + listServerEntries, + showServerEntry, +} from "@inspector/core/cli/handlers/servers-list.js"; import { CATALOG_WRITE_METHODS, isCatalogWriteMethod, runCatalogWrite, } from "./handlers/servers-write.js"; -import { writeFormattedResult } from "./handlers/format-output.js"; +import { writeFormattedResult } from "@inspector/core/cli/handlers/format-output.js"; import { parseOutputFileFormat, validateOutputOptions, type OutputFileFormat, -} from "./handlers/output-file.js"; +} from "@inspector/core/cli/handlers/output-file.js"; import { clearStoredAuthForRelogin } from "./clear-stored-auth-for-relogin.js"; import { InspectorClient } from "@inspector/core/mcp/index.js"; import { cleanRoots } from "@inspector/core/mcp/serverList.js"; @@ -52,15 +55,15 @@ import { import { getStateFilePath } from "@inspector/core/auth/node/storage-node.js"; import { SecretFileLockHeldError } from "@inspector/core/auth/node/secret-store.js"; import { consumeMethodOutcome } from "./handlers/consume-outcome.js"; -import { runMethod } from "./handlers/run-method.js"; +import { runMethod } from "@inspector/core/cli/handlers/run-method.js"; import { isOneShotMethod, ONE_SHOT_METHODS, type MethodArgs, -} from "./handlers/method-types.js"; -export type { CliAppInfo } from "./handlers/method-types.js"; +} from "@inspector/core/cli/handlers/method-types.js"; +export type { CliAppInfo } from "@inspector/core/cli/handlers/method-types.js"; export { emitResult } from "./handlers/emit-result.js"; -export { collectAppInfo } from "./handlers/collect-app-info.js"; +export { collectAppInfo } from "@inspector/core/cli/handlers/collect-app-info.js"; import { type OAuthPersistSnapshot } from "@inspector/core/auth/oauth-persist.js"; import type { ServerOAuthState } from "@inspector/core/auth/store.js"; import { getOwnEntry } from "@inspector/core/storage/own-entry.js"; @@ -83,14 +86,17 @@ import type { OAuthClientInformation, OAuthTokens, } from "@modelcontextprotocol/client"; -import { CliExitCodeError, EXIT_CODES } from "./error-handler.js"; +import { + CliExitCodeError, + EXIT_CODES, +} from "@inspector/core/cli/error-handler.js"; import { MutableRedirectUrlProvider } from "@inspector/core/auth/index.js"; import { NodeOAuthStorage } from "@inspector/core/auth/node/index.js"; -import { createCliOAuthNavigation } from "./cli-oauth-navigation.js"; +import { createCliOAuthNavigation } from "@inspector/core/cli/cli-oauth-navigation.js"; import { connectInspectorWithOAuth, withCliAuthRecoveryRetry, -} from "./cliOAuth.js"; +} from "@inspector/core/cli/cliOAuth.js"; import { DEFAULT_RUNNER_OAUTH_CALLBACK_URL, formatRunnerOAuthRedirectUrl, diff --git a/clients/cli/src/completion.ts b/clients/cli/src/completion.ts index 2c71540b87..11d181623e 100644 --- a/clients/cli/src/completion.ts +++ b/clients/cli/src/completion.ts @@ -23,8 +23,8 @@ */ import type { Command, Option } from "commander"; import { LoggingLevelSchema } from "@modelcontextprotocol/core"; -import { ONE_SHOT_METHODS } from "./handlers/method-types.js"; -import { awaitableLog } from "./utils/awaitable-log.js"; +import { ONE_SHOT_METHODS } from "@inspector/core/cli/handlers/method-types.js"; +import { awaitableLog } from "@inspector/core/cli/utils/awaitable-log.js"; export const COMPLETION_SHELLS = ["bash", "zsh", "fish"] as const; export type CompletionShell = (typeof COMPLETION_SHELLS)[number]; diff --git a/clients/cli/src/handlers/consume-outcome.ts b/clients/cli/src/handlers/consume-outcome.ts index baa16f08fc..45583ae002 100644 --- a/clients/cli/src/handlers/consume-outcome.ts +++ b/clients/cli/src/handlers/consume-outcome.ts @@ -1,7 +1,16 @@ -import { awaitableError, awaitableLog } from "../utils/awaitable-log.js"; -import { CliExitCodeError, EXIT_CODES } from "../error-handler.js"; +import { + awaitableError, + awaitableLog, +} from "@inspector/core/cli/utils/awaitable-log.js"; +import { + CliExitCodeError, + EXIT_CODES, +} from "@inspector/core/cli/error-handler.js"; import { emitResult } from "./emit-result.js"; -import type { MethodArgs, MethodOutcome } from "./method-types.js"; +import type { + MethodArgs, + MethodOutcome, +} from "@inspector/core/cli/handlers/method-types.js"; /** * True for the error a write to a closed pipe raises — the reader went away diff --git a/clients/cli/src/handlers/emit-result.ts b/clients/cli/src/handlers/emit-result.ts index 324d45f142..e8488ad715 100644 --- a/clients/cli/src/handlers/emit-result.ts +++ b/clients/cli/src/handlers/emit-result.ts @@ -1,9 +1,19 @@ -import { CliExitCodeError, EXIT_CODES } from "../error-handler.js"; +import { + CliExitCodeError, + EXIT_CODES, +} from "@inspector/core/cli/error-handler.js"; import { lintListResult, writeSchemaLintReport } from "./schema-lint-report.js"; import { countFindings } from "@inspector/core/json/schemaLint.js"; -import type { CliAppInfo, McpResponse, MethodArgs } from "./method-types.js"; -import { writeResultFile } from "./output-file.js"; -import { awaitableError, awaitableLog } from "../utils/awaitable-log.js"; +import type { + CliAppInfo, + McpResponse, + MethodArgs, +} from "@inspector/core/cli/handlers/method-types.js"; +import { writeResultFile } from "@inspector/core/cli/handlers/output-file.js"; +import { + awaitableError, + awaitableLog, +} from "@inspector/core/cli/utils/awaitable-log.js"; /** * Write the method result (and any app-info) to stdout, honouring `--format` diff --git a/clients/cli/src/handlers/schema-lint-report.ts b/clients/cli/src/handlers/schema-lint-report.ts index e6efd152ca..2ec2d50998 100644 --- a/clients/cli/src/handlers/schema-lint-report.ts +++ b/clients/cli/src/handlers/schema-lint-report.ts @@ -5,8 +5,8 @@ import { summarizeFindings, type ToolSchemaFindings, } from "@inspector/core/json/schemaLint.js"; -import { awaitableError } from "../utils/awaitable-log.js"; -import type { McpResponse } from "./method-types.js"; +import { awaitableError } from "@inspector/core/cli/utils/awaitable-log.js"; +import type { McpResponse } from "@inspector/core/cli/handlers/method-types.js"; /** * Read the `tools` array out of a `tools/list` result. The result is typed as diff --git a/clients/cli/src/index.ts b/clients/cli/src/index.ts index a03023939b..50decb5e35 100644 --- a/clients/cli/src/index.ts +++ b/clients/cli/src/index.ts @@ -3,7 +3,7 @@ import { resolve } from "path"; import { fileURLToPath } from "url"; import { runCli, validLogLevels } from "./cli.js"; -import { handleError } from "./error-handler.js"; +import { handleError } from "@inspector/core/cli/error-handler.js"; // `handleError` is exported so the launcher (which imports `runCli` as a module // and owns the rejection) can route a `mcp-inspector --cli` failure through the diff --git a/clients/cli/vitest.config.ts b/clients/cli/vitest.config.ts index f77d61d588..a71af73522 100644 --- a/clients/cli/vitest.config.ts +++ b/clients/cli/vitest.config.ts @@ -9,6 +9,7 @@ import { const dirname = path.dirname(fileURLToPath(import.meta.url)); const { projectResolve } = vitestSharedPaths(dirname); +const repoRoot = path.resolve(dirname, "../.."); export default defineConfig({ resolve: projectResolve, @@ -35,7 +36,13 @@ export default defineConfig({ coverage: { provider: "v8", reporter: ["text", "html", "json-summary"], - include: ["src/**/*.ts"], + // `core/cli/` is the node-only surface this client shares with mcpdo + // (#2461). It moved out of `src/` but these suites are still what + // exercise it, so it stays gated here; web's `core/*` whitelist + // deliberately omits it, since no web test reaches it. It sits outside + // this project's root, hence `allowExternal`. + include: ["src/**/*.ts", path.join(repoRoot, "core/cli/**/*.ts")], + allowExternal: true, exclude: [ // Binary bootstrap: shebang + `isMain` guard + `runCli()`/`process.exit` // wiring that only runs when launched as the real binary. Exercised by diff --git a/clients/mcpdo/README.md b/clients/mcpdo/README.md index 0a462ebb6a..3b97479277 100644 --- a/clients/mcpdo/README.md +++ b/clients/mcpdo/README.md @@ -2,7 +2,7 @@ **Experimental** separate client — **bundled into the published `@modelcontextprotocol/inspector` package** as the `mcpdo` bin. Connect once, then run many MCP commands against a named connection via an implicit local daemon (ssh-agent style). -> **Layout note:** Source lives in `clients/mcpdo/`. At build time it bundles some modules from `clients/cli/src` (`handlers/`, `error-handler`, OAuth helpers) via the `@inspector/cli` alias. That reach-in is intentional and temporary — not a published library API — until a cleaner shared package exists (tracked by [#2461](https://github.com/modelcontextprotocol/inspector/issues/2461)). +> **Layout note:** Source lives in `clients/mcpdo/`. The method handlers, exit codes / `CliExitCodeError`, interactive OAuth flow and output styling it shares with the one-shot CLI live in [`core/cli/`](../../core/cli), consumed through the ordinary `@inspector/core` alias and bundled like the rest of `core/` (#2461). ## Install diff --git a/clients/mcpdo/__tests__/auth-names-commands.test.ts b/clients/mcpdo/__tests__/auth-names-commands.test.ts index 707309033c..50df39a47d 100644 --- a/clients/mcpdo/__tests__/auth-names-commands.test.ts +++ b/clients/mcpdo/__tests__/auth-names-commands.test.ts @@ -1,5 +1,8 @@ import { describe, it, expect, vi, beforeEach } from "vitest"; -import { CliExitCodeError, EXIT_CODES } from "@inspector/cli/error-handler.js"; +import { + CliExitCodeError, + EXIT_CODES, +} from "@inspector/core/cli/error-handler.js"; const callDaemon = vi.fn(); const listServerEntries = vi.fn(); @@ -14,12 +17,15 @@ vi.mock("../src/daemon/index.js", async (importOriginal) => ({ callDaemon: (...args: unknown[]) => callDaemon(...args), })); -vi.mock("@inspector/cli/handlers/servers-list.js", async (importOriginal) => ({ - ...(await importOriginal< - typeof import("@inspector/cli/handlers/servers-list.js") - >()), - listServerEntries: (...args: unknown[]) => listServerEntries(...args), -})); +vi.mock( + "@inspector/core/cli/handlers/servers-list.js", + async (importOriginal) => ({ + ...(await importOriginal< + typeof import("@inspector/core/cli/handlers/servers-list.js") + >()), + listServerEntries: (...args: unknown[]) => listServerEntries(...args), + }), +); vi.mock("../src/connection/stored-auth.js", async (importOriginal) => ({ ...(await importOriginal< diff --git a/clients/mcpdo/__tests__/auth-names.test.ts b/clients/mcpdo/__tests__/auth-names.test.ts index 05805b7360..514c558e2f 100644 --- a/clients/mcpdo/__tests__/auth-names.test.ts +++ b/clients/mcpdo/__tests__/auth-names.test.ts @@ -1,5 +1,5 @@ import { describe, it, expect } from "vitest"; -import type { ServerListEntry } from "@inspector/cli/handlers/servers-list.js"; +import type { ServerListEntry } from "@inspector/core/cli/handlers/servers-list.js"; import type { ConnectionInfo } from "../src/daemon/protocol.js"; import { buildAuthNameIndex, diff --git a/clients/mcpdo/__tests__/authorize.test.ts b/clients/mcpdo/__tests__/authorize.test.ts index 420ccf50a8..ea73822cb0 100644 --- a/clients/mcpdo/__tests__/authorize.test.ts +++ b/clients/mcpdo/__tests__/authorize.test.ts @@ -5,11 +5,11 @@ const connectSpy = vi.fn(); const disconnectSpy = vi.fn().mockResolvedValue(undefined); const navigationSpy = vi.fn(); -vi.mock("@inspector/cli/cliOAuth.js", () => ({ +vi.mock("@inspector/core/cli/cliOAuth.js", () => ({ connectInspectorWithOAuth: (...args: unknown[]) => connectSpy(...args), })); -vi.mock("@inspector/cli/cli-oauth-navigation.js", () => ({ +vi.mock("@inspector/core/cli/cli-oauth-navigation.js", () => ({ createCliOAuthNavigation: (...args: unknown[]) => { navigationSpy(...args); return { navigate: vi.fn() }; diff --git a/clients/mcpdo/__tests__/connection-stored-auth.test.ts b/clients/mcpdo/__tests__/connection-stored-auth.test.ts index 4855552f86..b6895ab050 100644 --- a/clients/mcpdo/__tests__/connection-stored-auth.test.ts +++ b/clients/mcpdo/__tests__/connection-stored-auth.test.ts @@ -11,7 +11,7 @@ import { listStoredAuth, resolveStoredAuthKey, } from "../src/connection/stored-auth.js"; -import { CliExitCodeError } from "@inspector/cli/error-handler.js"; +import { CliExitCodeError } from "@inspector/core/cli/error-handler.js"; import { defaultSecretStore } from "@inspector/core/auth/node/secret-store-selection.js"; import { oauthSecretServerId } from "@inspector/core/auth/node/oauth-secrets.js"; import { runMcp } from "./helpers/mcp-runner.js"; diff --git a/clients/mcpdo/__tests__/daemon-connections.test.ts b/clients/mcpdo/__tests__/daemon-connections.test.ts index 186aa0cf34..e21361412c 100644 --- a/clients/mcpdo/__tests__/daemon-connections.test.ts +++ b/clients/mcpdo/__tests__/daemon-connections.test.ts @@ -14,7 +14,7 @@ import { isConnectionAuthRequiredError, ConnectionRegistry, } from "../src/daemon/connections.js"; -import { CliExitCodeError } from "@inspector/cli/error-handler.js"; +import { CliExitCodeError } from "@inspector/core/cli/error-handler.js"; import { AuthRecoveryRequiredError } from "@inspector/core/auth/challenge.js"; describe("daemon framing", () => { diff --git a/clients/mcpdo/__tests__/daemon-coverage.test.ts b/clients/mcpdo/__tests__/daemon-coverage.test.ts index 61536630a3..6517986567 100644 --- a/clients/mcpdo/__tests__/daemon-coverage.test.ts +++ b/clients/mcpdo/__tests__/daemon-coverage.test.ts @@ -22,7 +22,7 @@ import { } from "../src/daemon/ensure.js"; import { spawn, spawnSync } from "node:child_process"; import { ConnectionRegistry } from "../src/daemon/connections.js"; -import { CliExitCodeError } from "@inspector/cli/error-handler.js"; +import { CliExitCodeError } from "@inspector/core/cli/error-handler.js"; import { runMcp } from "./helpers/mcp-runner.js"; import { createSampleTestConfig, diff --git a/clients/mcpdo/__tests__/daemon-elicitation-park.test.ts b/clients/mcpdo/__tests__/daemon-elicitation-park.test.ts index bc2cdc61bf..52add1bf37 100644 --- a/clients/mcpdo/__tests__/daemon-elicitation-park.test.ts +++ b/clients/mcpdo/__tests__/daemon-elicitation-park.test.ts @@ -25,7 +25,7 @@ import type { InspectorClient } from "@inspector/core/mcp/inspectorClient.js"; const runMethodMock = vi.hoisted(() => ({ impl: undefined as unknown as (...args: unknown[]) => Promise, })); -vi.mock("@inspector/cli/handlers/run-method.js", () => ({ +vi.mock("@inspector/core/cli/handlers/run-method.js", () => ({ runMethod: (...args: unknown[]) => runMethodMock.impl(...args), })); diff --git a/clients/mcpdo/__tests__/daemon-paths.test.ts b/clients/mcpdo/__tests__/daemon-paths.test.ts index 18b8c20cce..e8f51e85be 100644 --- a/clients/mcpdo/__tests__/daemon-paths.test.ts +++ b/clients/mcpdo/__tests__/daemon-paths.test.ts @@ -11,7 +11,7 @@ import { getDaemonLockPath, getDaemonSocketPath, } from "../src/daemon/paths.js"; -import { writeFormattedResult } from "@inspector/cli/handlers/format-output.js"; +import { writeFormattedResult } from "@inspector/core/cli/handlers/format-output.js"; describe("daemon paths", () => { const backup: Record = {}; diff --git a/clients/mcpdo/__tests__/daemon-private.test.ts b/clients/mcpdo/__tests__/daemon-private.test.ts index e785695a82..0c67b63cf7 100644 --- a/clients/mcpdo/__tests__/daemon-private.test.ts +++ b/clients/mcpdo/__tests__/daemon-private.test.ts @@ -14,7 +14,7 @@ import { getDaemonTokenPath, } from "../src/daemon/paths.js"; import { DaemonServer } from "../src/daemon/server.js"; -import { CliExitCodeError } from "@inspector/cli/error-handler.js"; +import { CliExitCodeError } from "@inspector/core/cli/error-handler.js"; import { runMcp } from "./helpers/mcp-runner.js"; import { expectCliSuccess, diff --git a/clients/mcpdo/__tests__/daemon-rpc-abort.test.ts b/clients/mcpdo/__tests__/daemon-rpc-abort.test.ts index de5302b374..70b77d9d29 100644 --- a/clients/mcpdo/__tests__/daemon-rpc-abort.test.ts +++ b/clients/mcpdo/__tests__/daemon-rpc-abort.test.ts @@ -15,7 +15,7 @@ import type { InspectorClient } from "@inspector/core/mcp/inspectorClient.js"; const runMethodMock = vi.hoisted(() => ({ impl: undefined as unknown as (...args: unknown[]) => Promise, })); -vi.mock("@inspector/cli/handlers/run-method.js", () => ({ +vi.mock("@inspector/core/cli/handlers/run-method.js", () => ({ runMethod: (...args: unknown[]) => runMethodMock.impl(...args), })); diff --git a/clients/mcpdo/__tests__/daemon-stream.test.ts b/clients/mcpdo/__tests__/daemon-stream.test.ts index 07f4959ca4..09988c1fd0 100644 --- a/clients/mcpdo/__tests__/daemon-stream.test.ts +++ b/clients/mcpdo/__tests__/daemon-stream.test.ts @@ -8,7 +8,10 @@ import { acceptDaemonConnection, removeStaleDaemonSocket, } from "../src/daemon/ipc-glue.js"; -import { CliExitCodeError, EXIT_CODES } from "@inspector/cli/error-handler.js"; +import { + CliExitCodeError, + EXIT_CODES, +} from "@inspector/core/cli/error-handler.js"; describe("streamDaemon + ipc-glue", () => { let dir: string | undefined; diff --git a/clients/mcpdo/__tests__/elicitation-prompt.test.ts b/clients/mcpdo/__tests__/elicitation-prompt.test.ts index bfb4d17ccc..25391e598c 100644 --- a/clients/mcpdo/__tests__/elicitation-prompt.test.ts +++ b/clients/mcpdo/__tests__/elicitation-prompt.test.ts @@ -1,5 +1,5 @@ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; -import { createStyle } from "@inspector/cli/style.js"; +import { createStyle } from "@inspector/core/cli/style.js"; import type { ElicitationRequestFrame } from "../src/daemon/protocol.js"; const question = vi.fn(); diff --git a/clients/mcpdo/__tests__/ema-commands.test.ts b/clients/mcpdo/__tests__/ema-commands.test.ts index 1d04c98310..078ec9b371 100644 --- a/clients/mcpdo/__tests__/ema-commands.test.ts +++ b/clients/mcpdo/__tests__/ema-commands.test.ts @@ -1,5 +1,5 @@ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; -import { PLAIN } from "@inspector/cli/style.js"; +import { PLAIN } from "@inspector/core/cli/style.js"; import { formatEmaStatusHuman } from "../src/connection/format-human.js"; const getEmaStatus = vi.fn(); diff --git a/clients/mcpdo/__tests__/ema.test.ts b/clients/mcpdo/__tests__/ema.test.ts index 2a01457431..6a68aa5641 100644 --- a/clients/mcpdo/__tests__/ema.test.ts +++ b/clients/mcpdo/__tests__/ema.test.ts @@ -2,7 +2,7 @@ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; import * as fs from "node:fs"; import * as os from "node:os"; import * as path from "node:path"; -import { CliExitCodeError } from "@inspector/cli/error-handler.js"; +import { CliExitCodeError } from "@inspector/core/cli/error-handler.js"; import { NodeOAuthStorage, resetNodeOAuthStorageCache, diff --git a/clients/mcpdo/__tests__/form-prompt.test.ts b/clients/mcpdo/__tests__/form-prompt.test.ts index 768f878395..db6197cab2 100644 --- a/clients/mcpdo/__tests__/form-prompt.test.ts +++ b/clients/mcpdo/__tests__/form-prompt.test.ts @@ -1,5 +1,5 @@ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; -import { createStyle } from "@inspector/cli/style.js"; +import { createStyle } from "@inspector/core/cli/style.js"; import { promptForm } from "../src/connection/form-prompt.js"; import type { FormField } from "../src/connection/form-schema.js"; diff --git a/clients/mcpdo/__tests__/format-connection.test.ts b/clients/mcpdo/__tests__/format-connection.test.ts index 11670b33fd..2ab5c713e9 100644 --- a/clients/mcpdo/__tests__/format-connection.test.ts +++ b/clients/mcpdo/__tests__/format-connection.test.ts @@ -27,8 +27,11 @@ import { formatElicitationPendingHuman, } from "../src/connection/format-human.js"; import { writeConnectionOutput } from "../src/connection/format-connection.js"; -import { CliExitCodeError, EXIT_CODES } from "@inspector/cli/error-handler.js"; -import { createStyle, PLAIN } from "@inspector/cli/style.js"; +import { + CliExitCodeError, + EXIT_CODES, +} from "@inspector/core/cli/error-handler.js"; +import { createStyle, PLAIN } from "@inspector/core/cli/style.js"; describe("format-human", () => { it("formats tools with schema variants and empty list", () => { diff --git a/clients/mcpdo/__tests__/helpers/mcp-runner.ts b/clients/mcpdo/__tests__/helpers/mcp-runner.ts index 3952aa6a1d..f8a43f4d34 100644 --- a/clients/mcpdo/__tests__/helpers/mcp-runner.ts +++ b/clients/mcpdo/__tests__/helpers/mcp-runner.ts @@ -1,5 +1,5 @@ import { runMcp as invokeMcp } from "../../src/connection/mcp.js"; -import { formatErrorOutput } from "@inspector/cli/error-handler.js"; +import { formatErrorOutput } from "@inspector/core/cli/error-handler.js"; export interface McpResult { exitCode: number | null; diff --git a/clients/mcpdo/__tests__/mcp-auth-coverage.test.ts b/clients/mcpdo/__tests__/mcp-auth-coverage.test.ts index 50cebbf3cd..3e0980c9d0 100644 --- a/clients/mcpdo/__tests__/mcp-auth-coverage.test.ts +++ b/clients/mcpdo/__tests__/mcp-auth-coverage.test.ts @@ -3,7 +3,10 @@ import { createSampleTestConfig, deleteConfigFile, } from "../../cli/__tests__/helpers/fixtures.js"; -import { CliExitCodeError, EXIT_CODES } from "@inspector/cli/error-handler.js"; +import { + CliExitCodeError, + EXIT_CODES, +} from "@inspector/core/cli/error-handler.js"; const callDaemon = vi.fn(); const ensureDaemon = vi.fn(); diff --git a/clients/mcpdo/src/connection/auth-helper.ts b/clients/mcpdo/src/connection/auth-helper.ts index fc833246fa..073779a867 100644 --- a/clients/mcpdo/src/connection/auth-helper.ts +++ b/clients/mcpdo/src/connection/auth-helper.ts @@ -7,7 +7,10 @@ import type { InspectorServerSettings, MCPServerConfig, } from "@inspector/core/mcp/types.js"; -import { CliExitCodeError, EXIT_CODES } from "@inspector/cli/error-handler.js"; +import { + CliExitCodeError, + EXIT_CODES, +} from "@inspector/core/cli/error-handler.js"; import { getDaemonDir } from "../daemon/paths.js"; import { authorizeInFrontend } from "./authorize.js"; import { sanitizeText } from "./sanitize.js"; diff --git a/clients/mcpdo/src/connection/auth-names.ts b/clients/mcpdo/src/connection/auth-names.ts index 58e3ed0eab..eaf5350816 100644 --- a/clients/mcpdo/src/connection/auth-names.ts +++ b/clients/mcpdo/src/connection/auth-names.ts @@ -15,7 +15,7 @@ * resolves keys with, so a catalog `https://api.example.com/mcp` and the store's * `new URL(...).href` key line up. */ -import type { ServerListEntry } from "@inspector/cli/handlers/servers-list.js"; +import type { ServerListEntry } from "@inspector/core/cli/handlers/servers-list.js"; import type { ConnectionInfo } from "../daemon/protocol.js"; import { normalizeServerUrl } from "./stored-auth.js"; diff --git a/clients/mcpdo/src/connection/authorize.ts b/clients/mcpdo/src/connection/authorize.ts index 260bab3153..6aff3f2a38 100644 --- a/clients/mcpdo/src/connection/authorize.ts +++ b/clients/mcpdo/src/connection/authorize.ts @@ -19,12 +19,15 @@ import { type MCPServerConfig, } from "@inspector/core/mcp/types.js"; import { readInspectorVersion } from "@inspector/core/node/version.js"; -import { createCliOAuthNavigation } from "@inspector/cli/cli-oauth-navigation.js"; -import { connectInspectorWithOAuth } from "@inspector/cli/cliOAuth.js"; -import { CliExitCodeError, EXIT_CODES } from "@inspector/cli/error-handler.js"; +import { createCliOAuthNavigation } from "@inspector/core/cli/cli-oauth-navigation.js"; +import { connectInspectorWithOAuth } from "@inspector/core/cli/cliOAuth.js"; +import { + CliExitCodeError, + EXIT_CODES, +} from "@inspector/core/cli/error-handler.js"; import { isEmaClientNotConfiguredError } from "@inspector/core/auth/ema/clientConfigError.js"; import type { CallbackNavigation } from "@inspector/core/auth/index.js"; -import type { CliOAuthAutoOpenControl } from "@inspector/cli/cli-oauth-navigation.js"; +import type { CliOAuthAutoOpenControl } from "@inspector/core/cli/cli-oauth-navigation.js"; import { mcpdoEmaGuidance } from "./ema.js"; /** diff --git a/clients/mcpdo/src/connection/dispatch.ts b/clients/mcpdo/src/connection/dispatch.ts index bbc43de504..1d4d92c021 100644 --- a/clients/mcpdo/src/connection/dispatch.ts +++ b/clients/mcpdo/src/connection/dispatch.ts @@ -3,10 +3,10 @@ import type { RpcParams, RpcResult } from "../daemon/protocol.js"; import type { CliAppInfo, MethodArgs, -} from "@inspector/cli/handlers/method-types.js"; -import type { OutputFormat } from "@inspector/cli/handlers/format-output.js"; +} from "@inspector/core/cli/handlers/method-types.js"; +import type { OutputFormat } from "@inspector/core/cli/handlers/format-output.js"; import { writeConnectionOutput } from "./format-connection.js"; -import { styleFromOpts, type Style } from "@inspector/cli/style.js"; +import { styleFromOpts, type Style } from "@inspector/core/cli/style.js"; import { promptElicitation } from "./elicitation-prompt.js"; const STREAM_METHODS = new Set(["logging/tail", "resources/subscribe"]); diff --git a/clients/mcpdo/src/connection/elicitation-prompt.ts b/clients/mcpdo/src/connection/elicitation-prompt.ts index 158220060a..c21d9d415e 100644 --- a/clients/mcpdo/src/connection/elicitation-prompt.ts +++ b/clients/mcpdo/src/connection/elicitation-prompt.ts @@ -13,7 +13,7 @@ * Schemas outside the spec's restricted primitive-field shape (should * never happen from a well-behaved server) fall back to a clear decline. */ -import type { Style } from "@inspector/cli/style.js"; +import type { Style } from "@inspector/core/cli/style.js"; import type { ElicitationRequestFrame, ElicitationResponseFrame, diff --git a/clients/mcpdo/src/connection/ema.ts b/clients/mcpdo/src/connection/ema.ts index 8854e960fe..89e11683d0 100644 --- a/clients/mcpdo/src/connection/ema.ts +++ b/clients/mcpdo/src/connection/ema.ts @@ -24,8 +24,11 @@ import { import { getClientConfigFilePath } from "@inspector/core/client/index.js"; import { loadRunnerClientConfig } from "@inspector/core/client/runner.js"; import type { EnterpriseManagedAuthIdpConfig } from "@inspector/core/client/types.js"; -import { createCliOAuthNavigation } from "@inspector/cli/cli-oauth-navigation.js"; -import { CliExitCodeError, EXIT_CODES } from "@inspector/cli/error-handler.js"; +import { createCliOAuthNavigation } from "@inspector/core/cli/cli-oauth-navigation.js"; +import { + CliExitCodeError, + EXIT_CODES, +} from "@inspector/core/cli/error-handler.js"; /** Where install-level EMA IdP config lives (honours MCP_CLIENT_CONFIG_PATH). */ function clientConfigPath(): string { diff --git a/clients/mcpdo/src/connection/form-prompt.ts b/clients/mcpdo/src/connection/form-prompt.ts index dc27365fec..1ece55eb80 100644 --- a/clients/mcpdo/src/connection/form-prompt.ts +++ b/clients/mcpdo/src/connection/form-prompt.ts @@ -6,7 +6,7 @@ * range), then shows a review step before submitting so the user can * re-edit any field or cancel outright. */ -import type { Style } from "@inspector/cli/style.js"; +import type { Style } from "@inspector/core/cli/style.js"; import type { PromptInput } from "./prompt-reader.js"; import type { FormField } from "./form-schema.js"; import { codePointLength } from "./form-schema.js"; diff --git a/clients/mcpdo/src/connection/format-connection.ts b/clients/mcpdo/src/connection/format-connection.ts index 52c2e83d88..ed5077d1ea 100644 --- a/clients/mcpdo/src/connection/format-connection.ts +++ b/clients/mcpdo/src/connection/format-connection.ts @@ -1,14 +1,17 @@ import { awaitableError, awaitableLog, -} from "@inspector/cli/utils/awaitable-log.js"; +} from "@inspector/core/cli/utils/awaitable-log.js"; import type { ConnectionInfo, ElicitationPendingInfo, } from "../daemon/protocol.js"; -import { CliExitCodeError, EXIT_CODES } from "@inspector/cli/error-handler.js"; -import type { OutputFormat } from "@inspector/cli/handlers/format-output.js"; -import type { CliAppInfo } from "@inspector/cli/handlers/method-types.js"; +import { + CliExitCodeError, + EXIT_CODES, +} from "@inspector/core/cli/error-handler.js"; +import type { OutputFormat } from "@inspector/core/cli/handlers/format-output.js"; +import type { CliAppInfo } from "@inspector/core/cli/handlers/method-types.js"; import { formatAppInfoHuman, formatAppInfoListHuman, @@ -24,7 +27,7 @@ import { formatStreamEventHuman, } from "./format-human.js"; import { isSafeLinkTarget, sanitizeDeep, sanitizeText } from "./sanitize.js"; -import { PLAIN, type Style } from "@inspector/cli/style.js"; +import { PLAIN, type Style } from "@inspector/core/cli/style.js"; type JsonObject = Record; diff --git a/clients/mcpdo/src/connection/format-human.ts b/clients/mcpdo/src/connection/format-human.ts index 11df3cf7c3..966c48b956 100644 --- a/clients/mcpdo/src/connection/format-human.ts +++ b/clients/mcpdo/src/connection/format-human.ts @@ -3,7 +3,7 @@ * Styling (color / bold / dim / OSC 8 links) is parameterized via {@link Style}. */ -import { PLAIN, type Style } from "@inspector/cli/style.js"; +import { PLAIN, type Style } from "@inspector/core/cli/style.js"; import { isSafeLinkTarget } from "./sanitize.js"; import { parseFormSchema } from "./form-schema.js"; import type { ElicitationPendingInfo } from "../daemon/protocol.js"; diff --git a/clients/mcpdo/src/connection/mcp.ts b/clients/mcpdo/src/connection/mcp.ts index 04f03fca40..131e374238 100644 --- a/clients/mcpdo/src/connection/mcp.ts +++ b/clients/mcpdo/src/connection/mcp.ts @@ -22,7 +22,10 @@ import { type LoggingLevel } from "@modelcontextprotocol/client"; import { getDefaultEnvironment } from "@modelcontextprotocol/client/stdio"; import type { MCPServerConfig } from "@inspector/core/mcp/types.js"; import { LoggingLevelSchema } from "@modelcontextprotocol/core"; -import { CliExitCodeError, EXIT_CODES } from "@inspector/cli/error-handler.js"; +import { + CliExitCodeError, + EXIT_CODES, +} from "@inspector/core/cli/error-handler.js"; import { readInspectorVersion } from "@inspector/core/node/version.js"; import { getSecretStorageInfo, @@ -42,23 +45,23 @@ import { type ServerListSource, showServerEntry, summarizeServerConfig, -} from "@inspector/cli/handlers/servers-list.js"; -import { type OutputFormat } from "@inspector/cli/handlers/format-output.js"; +} from "@inspector/core/cli/handlers/servers-list.js"; +import { type OutputFormat } from "@inspector/core/cli/handlers/format-output.js"; import { DEFAULT_CONNECT_TIMEOUT_MS, withConnectTimeout, -} from "@inspector/cli/handlers/connect-timeout.js"; +} from "@inspector/core/cli/handlers/connect-timeout.js"; import { CONNECTION_RPC_METHODS, type MethodArgs, -} from "@inspector/cli/handlers/method-types.js"; +} from "@inspector/core/cli/handlers/method-types.js"; import { authorizeInFrontend } from "./authorize.js"; import { AUTH_HELPER_COMMAND, obtainPendingAuthUrl, runAuthHelper, } from "./auth-helper.js"; -import { isCliAutoOpenForced } from "@inspector/cli/cli-oauth-navigation.js"; +import { isCliAutoOpenForced } from "@inspector/core/cli/cli-oauth-navigation.js"; import { emaLogin, emaLogout, getEmaStatus } from "./ema.js"; import { EMA_LOGIN_HELPER_COMMAND, @@ -99,8 +102,8 @@ import { idpOAuthStorageKey, parseIdpOAuthStorageKey, } from "@inspector/core/auth/ema/storage.js"; -import { styleFromOpts } from "@inspector/cli/style.js"; -import { awaitableLog } from "@inspector/cli/utils/awaitable-log.js"; +import { styleFromOpts } from "@inspector/core/cli/style.js"; +import { awaitableLog } from "@inspector/core/cli/utils/awaitable-log.js"; import { createInterface } from "node:readline/promises"; function isDaemonUnreachable(error: unknown): boolean { diff --git a/clients/mcpdo/src/connection/stored-auth.ts b/clients/mcpdo/src/connection/stored-auth.ts index 920822831f..f87934273c 100644 --- a/clients/mcpdo/src/connection/stored-auth.ts +++ b/clients/mcpdo/src/connection/stored-auth.ts @@ -5,7 +5,10 @@ import { resetNodeOAuthStorageCache, } from "@inspector/core/auth/node/storage-node.js"; import { readOAuthStore } from "@inspector/core/auth/node/oauth-persist-file.js"; -import { CliExitCodeError, EXIT_CODES } from "@inspector/cli/error-handler.js"; +import { + CliExitCodeError, + EXIT_CODES, +} from "@inspector/core/cli/error-handler.js"; /** Same canonicalisation as one-shot `normalizeServerUrl` (avoid importing cli.ts). */ export function normalizeServerUrl(serverUrl: string): string { diff --git a/clients/mcpdo/src/daemon/auth.ts b/clients/mcpdo/src/daemon/auth.ts index bed88c8aef..638dec52fa 100644 --- a/clients/mcpdo/src/daemon/auth.ts +++ b/clients/mcpdo/src/daemon/auth.ts @@ -1,6 +1,9 @@ import { randomBytes, timingSafeEqual } from "node:crypto"; import * as fs from "node:fs"; -import { CliExitCodeError, EXIT_CODES } from "@inspector/cli/error-handler.js"; +import { + CliExitCodeError, + EXIT_CODES, +} from "@inspector/core/cli/error-handler.js"; import { DAEMON_TOKEN_ENV, getDaemonTokenPath } from "./paths.js"; /** Fresh random IPC token for a daemon whose environment didn't supply one. */ diff --git a/clients/mcpdo/src/daemon/client.ts b/clients/mcpdo/src/daemon/client.ts index f8ff718825..8002b08a29 100644 --- a/clients/mcpdo/src/daemon/client.ts +++ b/clients/mcpdo/src/daemon/client.ts @@ -1,7 +1,10 @@ import { randomUUID } from "node:crypto"; import * as net from "node:net"; import * as path from "node:path"; -import { CliExitCodeError, EXIT_CODES } from "@inspector/cli/error-handler.js"; +import { + CliExitCodeError, + EXIT_CODES, +} from "@inspector/core/cli/error-handler.js"; import { getDaemonTokenFromEnv, readDaemonTokenFile } from "./auth.js"; import { encodeRequest } from "./framing.js"; import { getDaemonDir, getDaemonSocketPath } from "./paths.js"; diff --git a/clients/mcpdo/src/daemon/connections.ts b/clients/mcpdo/src/daemon/connections.ts index c2d92431eb..0f175fb8fe 100644 --- a/clients/mcpdo/src/daemon/connections.ts +++ b/clients/mcpdo/src/daemon/connections.ts @@ -39,7 +39,10 @@ import { } from "@inspector/core/auth/index.js"; import { isEmaClientNotConfiguredError } from "@inspector/core/auth/ema/clientConfigError.js"; import { readLivePendingAuthMarker } from "../connection/auth-helper.js"; -import { CliExitCodeError, EXIT_CODES } from "@inspector/cli/error-handler.js"; +import { + CliExitCodeError, + EXIT_CODES, +} from "@inspector/core/cli/error-handler.js"; import type { ConnectionAuthInfo, ConnectionInfo, diff --git a/clients/mcpdo/src/daemon/elicitation-park.ts b/clients/mcpdo/src/daemon/elicitation-park.ts index 6d96cce9d4..3c5ddf4d1e 100644 --- a/clients/mcpdo/src/daemon/elicitation-park.ts +++ b/clients/mcpdo/src/daemon/elicitation-park.ts @@ -9,7 +9,10 @@ * for both eras because the bridge funnels legacy server→client requests and * modern non-task MRTR rounds through the same `ElicitationChannel` seam. */ -import { CliExitCodeError, EXIT_CODES } from "@inspector/cli/error-handler.js"; +import { + CliExitCodeError, + EXIT_CODES, +} from "@inspector/core/cli/error-handler.js"; import type { InspectorClient } from "@inspector/core/mcp/index.js"; import type { ElicitationChannel } from "./ipc-glue.js"; import type { diff --git a/clients/mcpdo/src/daemon/ensure.ts b/clients/mcpdo/src/daemon/ensure.ts index 1efc95a1e3..71493b9545 100644 --- a/clients/mcpdo/src/daemon/ensure.ts +++ b/clients/mcpdo/src/daemon/ensure.ts @@ -3,7 +3,10 @@ import * as fs from "node:fs"; import * as net from "node:net"; import * as path from "node:path"; import { fileURLToPath } from "node:url"; -import { CliExitCodeError, EXIT_CODES } from "@inspector/cli/error-handler.js"; +import { + CliExitCodeError, + EXIT_CODES, +} from "@inspector/core/cli/error-handler.js"; import { generateDaemonToken, getDaemonTokenFromEnv, diff --git a/clients/mcpdo/src/daemon/protocol.ts b/clients/mcpdo/src/daemon/protocol.ts index 0dafc3cf47..f3f90d0228 100644 --- a/clients/mcpdo/src/daemon/protocol.ts +++ b/clients/mcpdo/src/daemon/protocol.ts @@ -6,7 +6,7 @@ import type { import type { CliAppInfo, MethodArgs, -} from "@inspector/cli/handlers/method-types.js"; +} from "@inspector/core/cli/handlers/method-types.js"; import type { Implementation, ProtocolEra, diff --git a/clients/mcpdo/src/daemon/server.ts b/clients/mcpdo/src/daemon/server.ts index 6d06dd2021..7b45e83709 100644 --- a/clients/mcpdo/src/daemon/server.ts +++ b/clients/mcpdo/src/daemon/server.ts @@ -4,9 +4,9 @@ import { classifyError, CliExitCodeError, EXIT_CODES, -} from "@inspector/cli/error-handler.js"; -import { runMethod } from "@inspector/cli/handlers/run-method.js"; -import type { MethodArgs } from "@inspector/cli/handlers/method-types.js"; +} from "@inspector/core/cli/error-handler.js"; +import { runMethod } from "@inspector/core/cli/handlers/run-method.js"; +import type { MethodArgs } from "@inspector/core/cli/handlers/method-types.js"; import { acceptDaemonConnection, removeStaleDaemonSocket, diff --git a/clients/mcpdo/src/daemon/stream-client.ts b/clients/mcpdo/src/daemon/stream-client.ts index a4da01d458..4526a03015 100644 --- a/clients/mcpdo/src/daemon/stream-client.ts +++ b/clients/mcpdo/src/daemon/stream-client.ts @@ -3,7 +3,10 @@ */ import { randomUUID } from "node:crypto"; import * as net from "node:net"; -import { CliExitCodeError, EXIT_CODES } from "@inspector/cli/error-handler.js"; +import { + CliExitCodeError, + EXIT_CODES, +} from "@inspector/core/cli/error-handler.js"; import { getDaemonTokenFromEnv, readDaemonTokenFile } from "./auth.js"; import { encodeRequest } from "./framing.js"; import { getDaemonSocketPath } from "./paths.js"; diff --git a/clients/mcpdo/src/mcp-bin.ts b/clients/mcpdo/src/mcp-bin.ts index 989a313c41..2109fbf4aa 100644 --- a/clients/mcpdo/src/mcp-bin.ts +++ b/clients/mcpdo/src/mcp-bin.ts @@ -3,7 +3,7 @@ import { realpathSync } from "fs"; import { resolve } from "path"; import { fileURLToPath } from "url"; -import { handleError } from "@inspector/cli/error-handler.js"; +import { handleError } from "@inspector/core/cli/error-handler.js"; import { disallowMemorySecretStoreFallback, setSecretStorageWarningsQuiet, diff --git a/clients/mcpdo/tsconfig.json b/clients/mcpdo/tsconfig.json index f76c9ca96c..b11826efd5 100644 --- a/clients/mcpdo/tsconfig.json +++ b/clients/mcpdo/tsconfig.json @@ -3,10 +3,11 @@ "compilerOptions": { "noEmit": true, // Match clients/cli/tsconfig.json's module/lib *resolution* options (mcpdo - // reaches into @inspector/cli/* and @inspector/core/* the same way cli - // does) so core/ and cli/ are validated the same way their own gates - // validate them, rather than under base's stricter - // noUncheckedIndexedAccess, which core/cli were never written against. + // reaches into @inspector/core/* — including the core/cli/ surface it + // shares with the one-shot CLI — the same way cli does) so core/ is + // validated the same way its own gates validate it, rather than under + // base's stricter noUncheckedIndexedAccess, which core/ was never written + // against. "lib": ["ES2023", "DOM", "DOM.Iterable"], "types": ["node"], "moduleResolution": "bundler", @@ -14,8 +15,7 @@ "module": "ESNext", "noUncheckedIndexedAccess": false, "paths": { - "@inspector/core/*": ["../../core/*"], - "@inspector/cli/*": ["../cli/src/*"] + "@inspector/core/*": ["../../core/*"] } }, "include": ["src/**/*", "vitest.config.ts", "tsup.config.ts"], diff --git a/clients/mcpdo/tsconfig.test.json b/clients/mcpdo/tsconfig.test.json index 823eed9662..5c694c0343 100644 --- a/clients/mcpdo/tsconfig.test.json +++ b/clients/mcpdo/tsconfig.test.json @@ -9,7 +9,6 @@ "types": ["node", "express"], "paths": { "@inspector/core/*": ["../../core/*"], - "@inspector/cli/*": ["../cli/src/*"], "@modelcontextprotocol/inspector-test-server": [ "../../test-servers/src/index.ts" ], diff --git a/clients/mcpdo/tsup.config.ts b/clients/mcpdo/tsup.config.ts index 769422856e..67091dcaf2 100644 --- a/clients/mcpdo/tsup.config.ts +++ b/clients/mcpdo/tsup.config.ts @@ -4,7 +4,6 @@ import { fileURLToPath } from "node:url"; const dirname = path.dirname(fileURLToPath(import.meta.url)); const repoRoot = path.resolve(dirname, "../.."); -const cliSrc = path.resolve(dirname, "../cli/src"); export default defineConfig({ entry: { @@ -19,15 +18,13 @@ export default defineConfig({ sourcemap: false, target: "node22", platform: "node", - // Bundle core + one-shot CLI internals (handlers, error-handler, OAuth helpers). - // Temporary reach-in until a dedicated shared package exists — tracked by - // https://github.com/modelcontextprotocol/inspector/issues/2461 (see README). - noExternal: [/^@inspector\/core/, /^@inspector\/cli/], + // Bundle core, including the CLI-client surface it shares with the one-shot + // CLI (`core/cli/` — handlers, error-handler, OAuth helpers; #2461). + noExternal: [/^@inspector\/core/], // Mirrors clients/cli/tsup.config.ts (which documents each entry's story): // this client declares NO runtime dependencies (AGENTS.md dependency- // placement rule), so tsup's nearest-manifest auto-externalization sees - // nothing — every root-declared runtime package `core/` (or the bundled - // one-shot CLI source) imports must be named here or esbuild inlines it, + // nothing — every root-declared runtime package `core/` imports must be named here or esbuild inlines it, // and inlining a CJS module into this ESM bundle leaves esbuild's // `Dynamic require of "..." is not supported` shim (#2067). // `npm run verify:bundle-externals` enforces this against the built output. @@ -53,7 +50,6 @@ export default defineConfig({ esbuildOptions(options) { options.alias = { "@inspector/core": path.join(repoRoot, "core"), - "@inspector/cli": cliSrc, }; }, }); diff --git a/clients/mcpdo/vitest.config.ts b/clients/mcpdo/vitest.config.ts index 6d6486495e..dede3f7a13 100644 --- a/clients/mcpdo/vitest.config.ts +++ b/clients/mcpdo/vitest.config.ts @@ -9,17 +9,9 @@ import { const dirname = path.dirname(fileURLToPath(import.meta.url)); const { projectResolve } = vitestSharedPaths(dirname); -const cliSrc = path.resolve(dirname, "../cli/src"); - -const baseAliases = Array.isArray(projectResolve.alias) - ? projectResolve.alias - : []; export default defineConfig({ - resolve: { - ...projectResolve, - alias: [...baseAliases, { find: "@inspector/cli", replacement: cliSrc }], - }, + resolve: projectResolve, test: { globals: false, environment: "node", diff --git a/core/auth/node/runner-interactive-oauth.ts b/core/auth/node/runner-interactive-oauth.ts index e83626018e..6e084d85f8 100644 --- a/core/auth/node/runner-interactive-oauth.ts +++ b/core/auth/node/runner-interactive-oauth.ts @@ -98,7 +98,7 @@ export async function runRunnerInteractiveOAuth( // SIGINT specifically, absent any handler) hit Node's default abrupt exit // with no cleanup. Reject cleanly instead so the server is stopped and the // caller gets a normal, classifiable error ("OAuth" in the message maps to - // AUTH_REQUIRED — see clients/cli/src/error-handler.ts) rather than a raw + // AUTH_REQUIRED — see core/cli/error-handler.ts) rather than a raw // process death. Opt-in (see handleSignals) — never installed under the // TUI, which owns Ctrl-C through Ink. // diff --git a/clients/cli/src/cli-oauth-navigation.ts b/core/cli/cli-oauth-navigation.ts similarity index 97% rename from clients/cli/src/cli-oauth-navigation.ts rename to core/cli/cli-oauth-navigation.ts index 84c3964076..3e83e7edfb 100644 --- a/clients/cli/src/cli-oauth-navigation.ts +++ b/core/cli/cli-oauth-navigation.ts @@ -1,5 +1,5 @@ -import { CallbackNavigation } from "@inspector/core/auth/index.js"; -import { openUrl } from "@inspector/core/node/openUrl.js"; +import { CallbackNavigation } from "../auth/index.js"; +import { openUrl } from "../node/openUrl.js"; import { createStyle, resolveAnsiEnabled } from "./style.js"; /** diff --git a/clients/cli/src/cliOAuth.ts b/core/cli/cliOAuth.ts similarity index 96% rename from clients/cli/src/cliOAuth.ts rename to core/cli/cliOAuth.ts index 62fd718202..88265e44ed 100644 --- a/clients/cli/src/cliOAuth.ts +++ b/core/cli/cliOAuth.ts @@ -1,4 +1,4 @@ -import type { AuthChallenge } from "@inspector/core/auth/challenge.js"; +import type { AuthChallenge } from "../auth/challenge.js"; import { AuthRecoveryRequiredError, isStandardOAuthStepUp as isCoreStandardOAuthStepUp, @@ -6,16 +6,16 @@ import { stepUpConfirmMessage, stepUpInsufficientScopeMessage, MutableRedirectUrlProvider, -} from "@inspector/core/auth/index.js"; +} from "../auth/index.js"; import { createOAuthCallbackServer, runRunnerInteractiveOAuth, -} from "@inspector/core/auth/node/index.js"; -import type { RunnerInteractiveOAuthClient } from "@inspector/core/auth/node/runner-interactive-oauth.js"; -import type { RunnerOAuthCallbackConfig } from "@inspector/core/auth/node/runner-oauth-callback.js"; -import type { InspectorServerSettings } from "@inspector/core/mcp/types.js"; -import { isOAuthCapableServerConfig } from "@inspector/core/client/runner.js"; -import type { MCPServerConfig } from "@inspector/core/mcp/types.js"; +} from "../auth/node/index.js"; +import type { RunnerInteractiveOAuthClient } from "../auth/node/runner-interactive-oauth.js"; +import type { RunnerOAuthCallbackConfig } from "../auth/node/runner-oauth-callback.js"; +import type { InspectorServerSettings } from "../mcp/types.js"; +import { isOAuthCapableServerConfig } from "../client/runner.js"; +import type { MCPServerConfig } from "../mcp/types.js"; import { createInterface } from "node:readline/promises"; import { CliExitCodeError, EXIT_CODES } from "./error-handler.js"; import { diff --git a/clients/cli/src/error-handler.ts b/core/cli/error-handler.ts similarity index 96% rename from clients/cli/src/error-handler.ts rename to core/cli/error-handler.ts index 02857c889a..3bd6a318c5 100644 --- a/clients/cli/src/error-handler.ts +++ b/core/cli/error-handler.ts @@ -1,11 +1,8 @@ -import { - redactUrlQuery, - redactUrlsInText, -} from "@inspector/core/mcp/fetchTracking.js"; +import { redactUrlQuery, redactUrlsInText } from "../mcp/fetchTracking.js"; import { awaitableError } from "./utils/awaitable-log.js"; -import { isUnauthorizedError } from "@inspector/core/auth/index.js"; -import { SecretStoreUnavailableError } from "@inspector/core/auth/node/secret-store.js"; -import { OAuthStateFileUnrecognizedError } from "@inspector/core/auth/node/oauth-persist-file.js"; +import { isUnauthorizedError } from "../auth/index.js"; +import { SecretStoreUnavailableError } from "../auth/node/secret-store.js"; +import { OAuthStateFileUnrecognizedError } from "../auth/node/oauth-persist-file.js"; /** * Exit-code map. Non-zero codes let an automated caller (CI, an agent) branch @@ -94,13 +91,18 @@ export interface ErrorEnvelope { * the real code and stderr. */ export class CliExitCodeError extends Error { + readonly exitCode: number; + readonly envelope?: Partial; + constructor( - public readonly exitCode: number, + exitCode: number, message: string, - public readonly envelope?: Partial, + envelope?: Partial, ) { super(message); this.name = "CliExitCodeError"; + this.exitCode = exitCode; + this.envelope = envelope; } } diff --git a/clients/cli/src/handlers/collect-app-info.ts b/core/cli/handlers/collect-app-info.ts similarity index 78% rename from clients/cli/src/handlers/collect-app-info.ts rename to core/cli/handlers/collect-app-info.ts index d46e37544c..af488dcf2c 100644 --- a/clients/cli/src/handlers/collect-app-info.ts +++ b/core/cli/handlers/collect-app-info.ts @@ -1,8 +1,8 @@ -import { InspectorClient } from "@inspector/core/mcp/index.js"; -import { extractAppInfo } from "@inspector/core/mcp/apps.js"; -import type { AppInfo } from "@inspector/core/mcp/apps.js"; +import { InspectorClient } from "../../mcp/index.js"; +import { extractAppInfo } from "../../mcp/apps.js"; +import type { AppInfo } from "../../mcp/apps.js"; import type { CliAppInfo } from "./method-types.js"; -import type { RequestMetadata } from "@inspector/core/mcp/types.js"; +import type { RequestMetadata } from "../../mcp/types.js"; /** * Build the CLI's app-info for a tool. Never throws — failures fold into diff --git a/clients/cli/src/handlers/connect-timeout.ts b/core/cli/handlers/connect-timeout.ts similarity index 96% rename from clients/cli/src/handlers/connect-timeout.ts rename to core/cli/handlers/connect-timeout.ts index 8bd8cf655c..90d81e0115 100644 --- a/clients/cli/src/handlers/connect-timeout.ts +++ b/core/cli/handlers/connect-timeout.ts @@ -2,7 +2,7 @@ import { DEFAULT_MAX_FETCH_REQUESTS, DEFAULT_TASK_TTL_MS, type InspectorServerSettings, -} from "@inspector/core/mcp/types.js"; +} from "../../mcp/types.js"; /** * Default connect timeout (ms) for ad-hoc server invocations. Without this an diff --git a/clients/cli/src/handlers/format-output.ts b/core/cli/handlers/format-output.ts similarity index 100% rename from clients/cli/src/handlers/format-output.ts rename to core/cli/handlers/format-output.ts diff --git a/clients/cli/src/handlers/method-types.ts b/core/cli/handlers/method-types.ts similarity index 97% rename from clients/cli/src/handlers/method-types.ts rename to core/cli/handlers/method-types.ts index 94ffabe3d8..20e11773cc 100644 --- a/clients/cli/src/handlers/method-types.ts +++ b/core/cli/handlers/method-types.ts @@ -1,6 +1,6 @@ -import type { JsonValue } from "@inspector/core/mcp/index.js"; -import type { RequestMetadata } from "@inspector/core/mcp/types.js"; -import type { AppInfo } from "@inspector/core/mcp/apps.js"; +import type { JsonValue } from "../../mcp/index.js"; +import type { RequestMetadata } from "../../mcp/types.js"; +import type { AppInfo } from "../../mcp/apps.js"; import type { LoggingLevel } from "@modelcontextprotocol/client"; import type { OutputFormat } from "./format-output.js"; import type { OutputFileFormat } from "./output-file.js"; diff --git a/clients/cli/src/handlers/output-file.ts b/core/cli/handlers/output-file.ts similarity index 99% rename from clients/cli/src/handlers/output-file.ts rename to core/cli/handlers/output-file.ts index 8a7bd63bbf..ee46c1719b 100644 --- a/clients/cli/src/handlers/output-file.ts +++ b/core/cli/handlers/output-file.ts @@ -21,7 +21,7 @@ * with a file encoding would make `--format json --output x` ambiguous. */ import { writeFile } from "node:fs/promises"; -import { base64ToBytes } from "@inspector/core/mcp/skills.js"; +import { base64ToBytes } from "../../mcp/skills.js"; import { CliExitCodeError, EXIT_CODES } from "../error-handler.js"; import type { McpResponse } from "./method-types.js"; diff --git a/clients/cli/src/handlers/run-method.ts b/core/cli/handlers/run-method.ts similarity index 98% rename from clients/cli/src/handlers/run-method.ts rename to core/cli/handlers/run-method.ts index 212a31dc7b..e8ae570db9 100644 --- a/clients/cli/src/handlers/run-method.ts +++ b/core/cli/handlers/run-method.ts @@ -1,4 +1,4 @@ -import type { InspectorClient } from "@inspector/core/mcp/index.js"; +import type { InspectorClient } from "../../mcp/index.js"; import type { Root } from "@modelcontextprotocol/client"; import { ManagedToolsState, @@ -8,15 +8,15 @@ import { ManagedRequestorTasksState, ManagedSkillsState, MessageLogState, -} from "@inspector/core/mcp/state/index.js"; -import { SKILLS_EXTENSION_KEY } from "@inspector/core/mcp/skillsSchemas.js"; +} from "../../mcp/state/index.js"; +import { SKILLS_EXTENSION_KEY } from "../../mcp/skillsSchemas.js"; import { CliExitCodeError, EXIT_CODES } from "../error-handler.js"; import { collectAppInfo } from "./collect-app-info.js"; import { skillVerificationExitCode, summarizeSkillVerification, } from "./skills-verify.js"; -import { verifySkills } from "@inspector/core/mcp/skillsVerification.js"; +import { verifySkills } from "../../mcp/skillsVerification.js"; import type { CliAppInfo, McpResponse, diff --git a/clients/cli/src/handlers/servers-list.ts b/core/cli/handlers/servers-list.ts similarity index 98% rename from clients/cli/src/handlers/servers-list.ts rename to core/cli/handlers/servers-list.ts index 76a02603c5..b09c88a264 100644 --- a/clients/cli/src/handlers/servers-list.ts +++ b/core/cli/handlers/servers-list.ts @@ -1,15 +1,15 @@ import type { InspectorServerSettings, MCPServerConfig, -} from "@inspector/core/mcp/types.js"; -import { InMemorySecretStore } from "@inspector/core/auth/node/secret-store.js"; +} from "../../mcp/types.js"; +import { InMemorySecretStore } from "../../auth/node/secret-store.js"; import { loadServerEntries, resolveServerSource, selectServerEntry, withDefaultCatalogPath, type ServerLoadOptions, -} from "@inspector/core/mcp/node/index.js"; +} from "../../mcp/node/index.js"; /** One catalog/config entry as returned by `servers/list`. */ export type ServerListEntry = { diff --git a/clients/cli/src/handlers/skills-verify.ts b/core/cli/handlers/skills-verify.ts similarity index 98% rename from clients/cli/src/handlers/skills-verify.ts rename to core/cli/handlers/skills-verify.ts index fe33940e1e..dc8148af8a 100644 --- a/clients/cli/src/handlers/skills-verify.ts +++ b/core/cli/handlers/skills-verify.ts @@ -17,7 +17,7 @@ import { anySkillFailed, anySkillUnverifiable, type SkillVerifyReport, -} from "@inspector/core/mcp/skillsVerification.js"; +} from "../../mcp/skillsVerification.js"; import { EXIT_CODES } from "../error-handler.js"; /** diff --git a/clients/cli/src/style.ts b/core/cli/style.ts similarity index 100% rename from clients/cli/src/style.ts rename to core/cli/style.ts diff --git a/clients/cli/src/utils/awaitable-log.ts b/core/cli/utils/awaitable-log.ts similarity index 100% rename from clients/cli/src/utils/awaitable-log.ts rename to core/cli/utils/awaitable-log.ts diff --git a/core/mcp/uriTemplate.ts b/core/mcp/uriTemplate.ts index a536dde397..b61dbfeb84 100644 --- a/core/mcp/uriTemplate.ts +++ b/core/mcp/uriTemplate.ts @@ -10,7 +10,7 @@ * * The **CLI is deliberately not a consumer**: it has no template form, and its * `resources/read` passes the already-expanded `--uri` straight to - * `readResource` (see `clients/cli/src/handlers/run-method.ts`). Nothing here + * `readResource` (see `core/cli/handlers/run-method.ts`). Nothing here * runs for it. * * ## Why this is not simply `new UriTemplate(t).expand(v)` diff --git a/scripts/smoke-cli.mjs b/scripts/smoke-cli.mjs index c1c6e69b29..b88e837095 100644 --- a/scripts/smoke-cli.mjs +++ b/scripts/smoke-cli.mjs @@ -439,7 +439,7 @@ try { } let envelope; try { - // The documented envelope contract (clients/cli/src/error-handler.ts) is + // The documented envelope contract (core/cli/error-handler.ts) is // that the envelope is the *last* stderr line — `2>&1 | tail -1 | jq // .error` — because stderr legitimately carries diagnostics first (the // secret-store fallback banner on a host without a usable keychain, e.g. diff --git a/specification/v2_auth_mid_session.md b/specification/v2_auth_mid_session.md index cf714040df..c022bedae6 100644 --- a/specification/v2_auth_mid_session.md +++ b/specification/v2_auth_mid_session.md @@ -141,7 +141,7 @@ RemoteClientTransport StreamableHTTPClientTransport | Web remote client | `core/mcp/remote/remoteClientTransport.ts`, `core/mcp/inspectorClient.ts` | | Web app | `clients/web/src/App.tsx`, `lib/oauthResume.ts`, `utils/pendingReauth.ts`, `lib/browserTabVisibility.ts`, `components/groups/StepUpAuthModal/` | | TUI | `clients/tui/src/App.tsx`, `utils/tuiOAuth.ts` | -| CLI | `clients/cli/src/cliOAuth.ts` | +| CLI | `core/cli/cliOAuth.ts` | | Runner OAuth (TUI/CLI) | `core/auth/node/runner-interactive-oauth.ts`, `oauth-callback-server.ts` | | Step-up test fixture | `test-servers/configs/oauth-step-up-demo.json`, `test-servers/src/test-server-oauth.ts` | @@ -393,7 +393,7 @@ CLI never spawns TUI/web for auth — completes locally or fails. | Reauth for affected server | `handleAuthRecoveryRequired()` switches server when needed | | Clear OAuth | Auth tab **S** | -### CLI UX (`clients/cli/src/cliOAuth.ts`) +### CLI UX (`core/cli/cliOAuth.ts`) | Situation | Behavior | | ------------------------------ | -------------------------------------------------------------------------------- | diff --git a/specification/v2_cli_v2.md b/specification/v2_cli_v2.md index 247b142bea..78e4ddcee6 100644 --- a/specification/v2_cli_v2.md +++ b/specification/v2_cli_v2.md @@ -19,7 +19,7 @@ Documentation of the **experimental** connection-oriented Inspector CLI (`mcpdo` | Process | In-process only | Short-lived front-end + implicit connection daemon (IPC) | | Package | `clients/cli` (ships with `@modelcontextprotocol/inspector`) | `clients/mcpdo` (experimental; ships the `mcpdo` bin with `@modelcontextprotocol/inspector`) | -Both use `@inspector/core` `InspectorClient` and shared `clients/cli/src/handlers/run-method.ts` (mcpdo reaches in via a temporary `@inspector/cli` build alias). One-shot never starts the daemon. `mcpdo` does not accept `--method`. +Both use `@inspector/core` `InspectorClient` and shared `core/cli/handlers/run-method.ts` (#2461). One-shot never starts the daemon. `mcpdo` does not accept `--method`. ```bash mcpdo servers/list --config mcp.json @@ -47,10 +47,10 @@ mcpdo tools/list | Piece | Location | | -------------------- | ------------------------------------------------------------------------------------------------------------------------------ | -| One-shot | `clients/cli/src/cli.ts`, `cliOAuth.ts`, `index.ts` | +| One-shot | `clients/cli/src/cli.ts`, `index.ts`; interactive OAuth in `core/cli/cliOAuth.ts` (shared with mcpdo) | | Connection front-end | `clients/mcpdo/src/connection/` (`mcp.ts`, `dispatch.ts`, `authorize.ts`, `format-*.ts`, `private-env.ts`) + `mcp-bin.ts` | | Daemon | `clients/mcpdo/src/daemon/` → `clients/mcpdo/build/mcpdod.js` | -| Shared handlers | `clients/cli/src/handlers/` (`run-method.ts`, `method-types.ts`, `servers-list.ts`, `emit-result.ts`, …) | +| Shared handlers | `core/cli/handlers/` (`run-method.ts`, `method-types.ts`, `servers-list.ts`, …); one-shot-only output in `clients/cli/src/handlers/` (`emit-result.ts`, …) | ``` mcp-inspector --cli … mcpdo … @@ -154,7 +154,7 @@ Anything else (e.g. `logging/tail`, `resources/subscribe`, `tasks/*`, `roots/*`) | Client | Runner | Coverage | | ------------------------------------- | ---------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------ | -| One-shot (`clients/cli`) | In-process `runCli()`; thin binary e2e | Per-file ≥90 on `clients/cli/src`. Exclusion: `src/index.ts`. | +| One-shot (`clients/cli`) | In-process `runCli()`; thin binary e2e | Per-file ≥90 on `clients/cli/src` + `core/cli`. Exclusion: `src/index.ts`. | | Connection CLI (`clients/mcpdo`) | In-process `runMcp()`; daemon IPC + stream + private-token tests | Per-file ≥90 on `clients/mcpdo/src`. Exclusions: `mcp-bin.ts`, `daemon/run.ts` (bootstraps only). | Both are wired into root `validate` / `coverage`. From 6197966dc65847b0739712c8f13c361fcd658afd Mon Sep 17 00:00:00 2001 From: cliffhall Date: Wed, 7 Oct 2026 00:08:44 -0400 Subject: [PATCH 2/2] docs(testing): record the core/cli test-placement and coverage exception (#2461) Co-Authored-By: Claude Opus 5.5 Signed-off-by: cliffhall --- .claude/skills/testing/SKILL.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/.claude/skills/testing/SKILL.md b/.claude/skills/testing/SKILL.md index f555956f87..519e64e2a4 100644 --- a/.claude/skills/testing/SKILL.md +++ b/.claude/skills/testing/SKILL.md @@ -128,7 +128,9 @@ web-owned test living under `src/test/` instead is a bug. 1. **Tests of the repo-root `core/` package** → `src/test/core/…`, mirroring the `core/` folder layout. `core/` physically lives outside `clients/web/`, is consumed via the `@inspector/core` alias, and has no test harness of its own. - This includes `core/json/*` and `core/client/*`. + This includes `core/json/*` and `core/client/*`. **Except `core/cli/`** — + the Node-only surface the one-shot CLI and mcpdo share (#2461): its tests + live in `clients/cli/__tests__/` and `clients/cli`'s coverage run gates it. 2. **The `integration` project** → `src/test/integration/…`, mirroring the `core/` source layout (`mcp/`, `mcp/node/`, `mcp/remote/`, `auth/`, `auth/node/`, `storage/`). **Placement is the manifest** — any file under that @@ -218,8 +220,9 @@ Scope notes: (a composition root at ~42% branch coverage — gating it is a dedicated decomposition effort) and the `src/main.tsx` / `src/index.ts` bootstraps. - **CLI** tests run **in-process** by importing `runCli()` - (`__tests__/helpers/cli-runner.ts`) so `src` is measured; `src/index.ts` is the - only exclusion. `commander` uses `.exitOverride()` so a parse error throws + (`__tests__/helpers/cli-runner.ts`) so `src` **and the shared `core/cli/`** are + measured (the latter via `allowExternal`, since it sits outside the project + root); `src/index.ts` is the only exclusion. `commander` uses `.exitOverride()` so a parse error throws instead of tearing down the test worker. - **TUI** covers **all of `src/**`, React surface included**. Components mount through `**tests**/helpers/renderTui.tsx`—`ink-testing-library`'s `render`with every frame ANSI-stripped — alongside the passthrough doubles in the same