| Filename | Latest commit message | Latest commit date |
|---|---|---|
* feat(cli): programmatic ergonomics — initialize, tool-args-json, connect-timeout, env-var fixes Adds the #1573 programmatic-ergonomics surface to the CLI, re-implemented informed by PR #1510 and adapted to the v2/main OAuth-wired cli.ts: - `--method initialize` — connect-only probe emitting `{serverInfo, protocolVersion, capabilities, instructions}`. - `--tool-args-json '{"k":"v"}'` — tool args passed verbatim as JSON (no key=value coercion); mutually exclusive with `--tool-arg`, with clear errors for combined use, malformed JSON, and non-object values. - `--connect-timeout <ms>` — default 15000 for ad-hoc `--server-url`/target runs, file-level timeout otherwise; `0` disables. Backed by the exported `withConnectTimeout()` helper. - MCP_CATALOG_PATH is ignored when an ad-hoc target is given, so a shell that exports it can still run one-off ad-hoc invocations. - MCP_STORAGE_DIR is honored by core `getStateFilePath` (`<dir>/oauth.json`), below the per-file MCP_INSPECTOR_OAUTH_STATE_PATH override. Tests: new programmatic-ergonomics.test.ts (CLI, in-process runCli + direct withConnectTimeout unit tests) and getStateFilePath resolution cases in the web storage integration suite. Per-file coverage gate stays ≥90. README: documents the new flags and env-var semantics. Closes #1573 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BrihGWcrM9JGRyu41nzZYw * feat(cli): --app-info probe + --format json envelope Adds the #1574 app-probing and machine-readable output surface, re-implemented informed by PR #1510 and adapted to the v2/main OAuth-wired cli.ts. - `--method tools/call --tool-name <t> --app-info` — prints one JSON line describing the tool's app posture (hasApp, resourceUri, csp, permissions, domain, …) WITHOUT invoking the tool; exit 0 when it has an app, 2 (no_app) when it doesn't. A missing tool exits 5 (tool_not_found) — distinct from the no-app exit so a typo isn't mistaken for "no app". - `--method tools/list --app-info` — one NDJSON line per tool over a single connection (`| jq -c 'select(.hasApp)'`). - `--format json` — a single stdout envelope (`{result}` plus `{appInfo}` as a sibling key for App tools), no banner, so any method pipes into jq. - A tools/call returning isError:true now exits 5 (tool_is_error) after printing its payload, so && chains stop on a failed call. Implementation: `collectAppInfo()` (core `extractAppInfo` + a tolerated resource read that reports `resourceError`), an NDJSON loop in tools/list, and a single `emitResult()` that owns format + exit-code handling. `runMethod` now returns a `MethodOutcome` discriminated union so the NDJSON path can signal "already emitted". Tests: app-info.test.ts (exit-0/2/5 + NDJSON + no-invoke), format-json.test.ts (envelope shapes + bad --format), emit-result.test.ts (direct unit coverage of the isError→exit-5 and resourceError branches via exported emitResult/ collectAppInfo). Updated the tools.test "nonexistent tool" case to the new exit-5 tool_not_found contract. Per-file coverage ≥90 on all four dimensions (cli.ts 100% stmts/funcs/lines). README documents the flags, NDJSON shape, and exit semantics. Closes #1574 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BrihGWcrM9JGRyu41nzZYw * feat(cli): stored-auth suite — --use-stored-auth, --wait-for-auth, --list-stored-auth, --print-handoff Adds the #1575 web→CLI OAuth handoff surface, re-implemented informed by PR #1510 and adapted to v2/main. - `--use-stored-auth` — read the shared oauth.json and inject `Authorization: Bearer <token>` for --server-url; exit 3 (no_stored_token), listing stored keys, when none matches. - `--wait-for-auth <sec>` — poll oauth.json (500ms) until a token for --server-url appears, then proceed; timeout → exit 3 (auth_wait_timeout). - `--list-stored-auth` — print `{oauthStatePath, storedServerUrls}` and exit (short-circuit, no connect). - `--print-handoff` — emit `{serverUrl, deepLink, portForwardCmd, oauthStatePath, apiToken, note}` for --server-url and exit. Adaptations vs. the reference: - State-path resolution reuses core `getStateFilePath()` (the MCP_INSPECTOR_OAUTH_STATE_PATH → <MCP_STORAGE_DIR>/oauth.json → default precedence added in #1573) instead of a CLI-local resolver. - The state file is read through the shared isomorphic `parseOAuthPersistBlob`, so both the plain `{servers,idpSessions}` layout the web backend writes and the legacy `{state,version}` envelope are accepted. - `parseArgs` now returns a ParseResult union so the short-circuit modes return without a server connection. Blind injection: the token is passed as-is; a stale one surfaces as HTTP 401 → exit 3. Documented in the README along with all four flags, the state-path precedence, and a follow-up note that --print-handoff's deepLink shape is interim pending #1576's canonical handoff format. Tests: stored-auth.test.ts drives injection, --header coexistence, missing --server-url, no-token exit-3, URL-normalisation matching, MCP_STORAGE_DIR resolution, listing, handoff (+note), wait-for-auth polling/timeout, and normalizeServerUrl units — all against a real in-process HTTP test server. Per-file coverage ≥90 on all four dimensions (cli.ts 100% stmts/funcs/lines). Closes #1575 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BrihGWcrM9JGRyu41nzZYw * docs(core): scope MCP_STORAGE_DIR comment in getStateFilePath to the OAuth backend Address review of PR #1651: the doc comment claimed MCP_STORAGE_DIR was honoured "across the app, so setting it once points every persist backend at the same directory", but the branch only relocates oauth.json. Narrow the comment to state it is OAuth-backend-only (client.json/mcp.json still resolve from getDefaultStorageDir → HOME) and record why the resolution lives here rather than in getDefaultStorageDir (the default-path constant is evaluated at module load, so a runtime-set MCP_STORAGE_DIR — as the CLI stored-auth / --wait-for-auth flows set it — must be read at call time). Comment-only; no behavior change. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BrihGWcrM9JGRyu41nzZYw * review(cli): per-tool-tolerant app-info listing + drop dead helper (PR #1653) Address review of PR #1653: - collectAppInfo now never throws: a malformed `_meta.ui.resourceUri` (extractAppInfo throws) folds into `{hasApp:false, resourceError}` just like a resources/read failure. This makes `tools/list --app-info` per-tool tolerant — one bad tool no longer aborts the whole NDJSON listing with a generic exit-1. Guarding inside collectAppInfo (vs. a loop-local try/catch) keeps the branch unit-testable; added an emit-result.test.ts case for the malformed-URI path. - Documented that `tools/list --app-info` always emits NDJSON regardless of `--format` (the per-tool list shape is fixed; --format json only reshapes the single-result paths) — in both the code comment and the README. - Removed the now-unused `expectJsonError` helper (its only caller moved to the exit-5 tool_not_found contract). Coverage stays ≥90 on all four dimensions (cli.ts 100% stmts/funcs/lines). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BrihGWcrM9JGRyu41nzZYw * review(cli): stored-auth follow-ups — TODO(#1576), wait-for-auth guard test, docs (PR #1655) Address review of PR #1655: - Added an explicit TODO(#1576) on buildHandoff's deepLink: `transport:"http"` is hardcoded even for SSE, and `autoConnect=<token>` is not one of the three token sources the web app reads (per CLAUDE.md) — both to be reconciled with #1576's canonical handoff format. - Added a test for `--wait-for-auth` without `--server-url`, covering the previously-untested `--wait-for-auth` arm of the shared missing-server-url guard's message ternary. - Documented that `--list-stored-auth` / `--print-handoff` are mutually exclusive short-circuit modes (list takes precedence) and that a stored refresh_token is not yet used (noted as a follow-up in code + README). No behavior change; coverage stays ≥90 on all four dimensions (cli.ts 100% stmts/funcs/lines). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BrihGWcrM9JGRyu41nzZYw --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
| .. | ||
| helpers | ||
| app-info.test.ts | ||
| cli.test.ts | ||
| cliOAuth.test.ts | ||
| e2e.test.ts | ||
| emit-result.test.ts | ||
| error-handler.test.ts | ||
| format-json.test.ts | ||
| headers.test.ts | ||
| metadata.test.ts | ||
| methods.test.ts | ||
| oauth-interactive.test.ts | ||
| oauth-runner.test.ts | ||
| programmatic-ergonomics.test.ts | ||
| README.md | ||
| stored-auth.test.ts | ||
| tools.test.ts | ||
CLI Tests
Running Tests
# Run all tests (pretest builds test-servers + the CLI binary)
npm test
# Run all tests under the per-file coverage gate
npm run test:coverage
# Run in watch mode (useful for test file changes)
npm run test:watch
# Run specific test file
npm run test:cli # cli.test.ts
npm run test:cli-tools # tools.test.ts
npm run test:cli-headers # headers.test.ts
npm run test:cli-metadata # metadata.test.ts
npx vitest run oauth-interactive.test.ts cliOAuth.test.ts # OAuth interactive smoke parity
How the CLI is exercised
Most tests run the CLI in-process: helpers/cli-runner.ts imports runCli()
from ../src/cli.ts and invokes it directly, capturing process.stdout/stderr
and mapping thrown errors to an exit code. This is what lets vitest's v8 coverage
instrument clients/cli/src (#1484). Because commander uses .exitOverride(),
a parse/usage error throws instead of calling process.exit and tearing down the
worker.
e2e.test.ts is the deliberately thin out-of-process layer: it spawns the
built build/index.js as a real subprocess to cover the shebang, the
index.ts isMain bootstrap, and the actual process.exit codes — the only
parts the in-process runner can't reach. (scripts/smoke-cli.mjs at the repo
root provides a further end-to-end check of the binary.)
Test Files
cli.test.ts- Basic CLI functionality: CLI mode, environment variables, config files, resources, prompts, logging, transport typestools.test.ts- Tool-related tests: Tool discovery, JSON argument parsing, error handling, promptsheaders.test.ts- Header parsing and validationmetadata.test.ts- Metadata functionality: General metadata, tool-specific metadata, parsing, merging, validationmethods.test.ts- Method/option-validation paths not covered elsewhere (resource templates, missing/invalid options, the--target separator)error-handler.test.ts- The binary'shandleErrorerror sink, exercised in-process withprocess.exitstubbedoauth-runner.test.ts- OAuth flag wiring (--client-config,--callback-url, CIMD overrides)cliOAuth.test.ts- Unit tests forcliOAuth.ts(step-up confirm, helper wiring, retry)oauth-interactive.test.ts- Integration smoke parity for CLI interactive OAuth: connect-time callback server + step-up y/N against composableTestServerHttp(auto-completes authorize URL programmatically; not a subprocess binary e2e)e2e.test.ts- Out-of-process spawn of the built binary (exit codes + boot; no OAuth)
Helpers
The helpers/ directory contains shared utilities:
cli-runner.ts- InvokesrunCli()in-process and captures stdout/stderr + exit codeassertions.ts- Custom assertion helpers for CLI output validationfixtures.ts- Test config/catalog file generators and temporary directory management
HTTP/SSE test servers (for transport tests) come from the
@modelcontextprotocol/inspector-test-server package (createTestServerHttp),
and the bundled stdio server is launched via getTestMcpServerCommand().
Notes
- Test files run in separate forked processes (
pool: 'forks'), so the in-process runner'sprocess.std*.writepatching never overlaps across files; tests within a file run sequentially - Config files use
crypto.randomUUID()for uniqueness - HTTP/SSE servers use dynamic port allocation to avoid conflicts
- Coverage is enforced per-file (lines ≥ 90, statements ≥ 85, functions ≥ 80,
branches ≥ 50).
src/index.ts(the binary bootstrap) is excluded because it only runs in the spawned binary, which coverage can't instrument - All tests use the built-in MCP test servers — no external/registry dependencies