mcporter/docs/cli-reference.md
Sebastian B Otaegui 3e27b64021
Some checks failed
CI / build (${{ matrix.os }}) (ubuntu-latest) (push) Has been cancelled
CI / build (${{ matrix.os }}) (macos-15) (push) Has been cancelled
CI / build (${{ matrix.os }}) (windows-latest) (push) Has been cancelled
pages / Deploy docs (push) Has been cancelled
fix(runtime): preserve disableOAuth across headless paths (#198)
* feat(runtime): add `disableOAuth` connect option (cache-friendly OAuth suppression)

Closes #197.

Long-running headless callers (daemons, scheduled jobs, CI workers) need
to suppress the interactive OAuth flow without losing connection caching.
The only existing knob — `maxOAuthAttempts: 0` — couples those two concerns
because `useCache` is gated on `options.maxOAuthAttempts === undefined`.
Daemons that wrap `connect` to force `maxOAuthAttempts: 0` end up spawning
a fresh transport per `callTool`/`listTools` and `runtime.close()` cannot
reap any of them.

Add an additive `disableOAuth: boolean` option that suppresses OAuth at
the transport layer (short-circuits `shouldEstablishOAuth` and
`maybePromoteHttpDefinition`) but preserves caching. The cache entry
metadata gains a `disableOAuth` field so connections established with
the flag don't share a slot with connections that could refresh into an
OAuth flow — switching the flag between calls evicts and re-establishes,
mirroring the existing `allowCachedAuth` mismatch path.

Backward compatibility:

* `maxOAuthAttempts: 0` keeps its legacy escape-the-cache contract
  unchanged. Existing callers see no behavior change.
* `skipCache: true` keeps its behavior unchanged.
* `disableOAuth` defaults to undefined; only opt-in changes behavior.

Also export `ConnectOptions` from `runtime.ts` and add the parameter to
the `Runtime.connect` interface signature — the implementation already
accepted options at runtime but the interface only exposed
`connect(server)`, so callers couldn't pass options through the type
system. (Pre-existing gap surfaced by adding the new test coverage.)

Tests added to `tests/runtime-integration.test.ts`:

* `reuses cached connection when disableOAuth: true is passed` — two
  calls return the same ClientContext, `close()` reaps it.
* `maxOAuthAttempts: 0 still bypasses the cache (existing contract
  preserved)` — regression guard.
* `evicts and re-establishes the cached client when disableOAuth flag
  changes` — the core eviction semantic.

`pnpm test` (709 pass / 3 skip), `pnpm lint`, `pnpm typecheck` all
green.

* fix(runtime): preserve disableOAuth across helper calls

* fix(daemon): forward disableOAuth through keep-alive paths

* feat(cli): expose disableOAuth for headless commands

* fix(runtime): preserve cached slot across connect(disableOAuth) → callTool/listTools

Addresses PR #198 review comment r3366238654.

The documented headless setup is:

    await runtime.connect(server, { disableOAuth: true });
    await runtime.callTool(server, 'foo', { ... });

The first call stored the cache slot with `allowCachedAuth: undefined`,
but `callTool()` internally calls `this.connect(server, {
allowCachedAuth: true, disableOAuth: <effective>: true })` and the
cache-match check treated the two options shapes as structurally
different:

    existing.allowCachedAuth (undefined)
       !== options.allowCachedAuth (true)
       && options.allowCachedAuth !== undefined
    => MISMATCH => evict + reopen transport

Every first callTool / listTools after a pre-connect spawned a fresh
transport, defeating the pooling guarantee that motivated the
disableOAuth option in the first place. Same shape affected `listTools`
(which defaults `allowCachedAuth: options.allowCachedAuth ?? true`).

Fix: normalize at the connect() entrypoint. A `disableOAuth: true`
caller has no path to interactive OAuth, so cached-token application
is the only auth they can ever use — default `allowCachedAuth: true`
when the caller didn't pick a side. Explicit `false` is honored
(header-only / anonymous callers). The normalized value flows through
both the cache lookup and the cache write so subsequent internal
callers compose without eviction.

Two regression tests added to `tests/runtime-integration.test.ts`:

  - `preserves the cached client across connect(disableOAuth:true) →
    callTool() (no implicit eviction)`
  - `preserves the cached client across connect(disableOAuth:true) →
    listTools() (no implicit eviction)`

Both call `runtime.connect(disableOAuth:true)`, then invoke the
internal-cached path (callTool or listTools), then re-call
`runtime.connect(disableOAuth:true)` and assert the resulting
ClientContext is `=== ` the first one. Both tests fail without this
fix (the second connect returns a new ClientContext because the first
was evicted).

`pnpm test` 723 pass / 3 skip / 0 fail. `pnpm lint` + `pnpm
typecheck` clean. No push.

* docs(examples): add headless-pooling-demo for disableOAuth verification

Demonstrates the three patterns under the new `disableOAuth` option
against a local mock MCP server (no real auth). Reproducible artifact
for PR #198 review proof.

Patterns demonstrated:

* Legacy `maxOAuthAttempts: 0` (uncached): 5 connect() calls produce
  5 distinct ClientContexts. Existing contract preserved.
* `disableOAuth: true` on every connect: 5 calls produce 1
  ClientContext. Cache reuse under cache-friendly suppression.
* Documented headless setup — pre-connect(disableOAuth: true) +
  5 callTool() — proves the pre-connected slot survives the implicit
  internal connect path. Directly demonstrates the fix from b0e3e2e.

Run: `pnpm tsx examples/headless-pooling-demo.ts`

Sample output is intentionally redacted to no PII / no secrets: a local
http://127.0.0.1:<random-port>/mcp server with a public `add` tool.

* style(examples): oxfmt headless-pooling-demo (CI fix)

* fix(server-proxy): thread disableOAuth through schema-discovery listTools

Addresses PR #198 review comment r3366307210 (clawsweeper proxy gap).

The Proxy returned by `createServerProxy` calls `ensureMetadata()` on
every tool invocation, which fires `runtime.listTools(server, {
includeSchema: true })` for schema discovery. That call ran BEFORE the
proxy parsed the caller's options bag, so a `proxy.tool({ ... }, {
disableOAuth: true })` invocation on an OAuth server with no cached
schema could still trigger an interactive OAuth flow during metadata
fetch — defeating the no-browser guarantee the option was meant to
provide.

Fix:

* Pre-scan callArgs once for `disableOAuth: true` before invoking
  `ensureMetadata`. The scan is a single linear pass over the
  already-present argument list and short-circuits on the first match.
* Extend `ensureMetadata(toolName, { disableOAuth? })` and forward the
  flag to the underlying `runtime.listTools(serverName, { includeSchema:
  true, disableOAuth: true })` call.
* The schema-fetch path that was vulnerable now inherits the same
  no-OAuth posture as the eventual `runtime.callTool` invocation. End-
  to-end no-browser guarantee is preserved across the proxy interface.

Regression test in `tests/server-proxy.test.ts`:

  > threads disableOAuth through schema discovery so
  > proxy.tool({disableOAuth:true}) cannot trigger OAuth during
  > metadata fetch

Asserts BOTH:
- `runtime.listTools` called with `{ includeSchema: true, disableOAuth:
  true }`
- `runtime.callTool` called with the eventual tool args and
  `disableOAuth: true`

Locks the contract on both halves so a future refactor that re-introduces
the gap on either side will fail loudly.

Full suite: 724 pass / 3 skipped / 0 fail. `pnpm check` (format + lint
+ typecheck) clean.

* refactor(cli): drop --disable-oauth alias; keep only --no-oauth

The PR originally exposed two CLI names for the same intent:
--disable-oauth (mirroring the JS option `disableOAuth: true`) and
--no-oauth (the GNU-style boolean opt-out). Two names for one
behavior is noise — documentation has to mention both, users have to
learn both, and they invite drift.

--no-oauth is the right shape for a per-invocation boolean opt-out:
- Matches the dominant unix convention (git --no-verify, npm --no-save,
  bun --no-cache, curl --no-progress-meter).
- Shorter to type.
- Composes naturally with other flags in scripts.

The JS option name stays `disableOAuth: boolean` — that's the right
shape for a JS option (verb+noun, no Boolean-negation prefix
ambiguity), and the JS and CLI naming conventions are genuinely
different domains.

Removed CLI registrations + help text + internal forwarding for
--disable-oauth across:
- src/cli/call-arguments.ts (FLAG_HANDLERS registration)
- src/cli/call-command.ts (internal listArgs forwarding, 2 sites)
- src/cli/call-help.ts (help text)
- src/cli/list-command.ts (help text)
- src/cli/list-flags.ts (token check)
- src/cli/resource-command.ts (token check + help text)
- docs/cli-reference.md (3 references)

Renamed test cases that exclusively exercised --disable-oauth to
exercise --no-oauth instead, preserving regression coverage:
- tests/call-arguments.test.ts
- tests/cli-list-flags.test.ts
- tests/cli-resource-command.test.ts

The internal cache-key fragment `disable-oauth:` in
src/cli/tool-cache.ts is kept — it mirrors the JS option name (which
stays `disableOAuth`), not the CLI flag.

Tests: 724 passed, 3 skipped, 0 failed.
Lint: 0 warnings, 0 errors.
Typecheck: clean.

* fix(runtime): forward disableOAuth through callOnce

* chore: update dependencies

* fix(server-proxy): preserve schema-owned option fields

* fix(runtime): isolate OAuth cache variants safely

* fix(server-proxy): isolate schema discovery posture

* fix(server-proxy): preserve OAuth posture during discovery

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-06-08 16:11:23 -07:00

7.6 KiB
Raw Permalink Blame History

summary read_when
Quick reference for mcporter subcommands, their arguments, and shared global flags.
Need a refresher on available CLI commands

mcporter CLI Reference

A quick reference for the primary mcporter subcommands. Each command inherits --config <file> and --root <dir> to override where servers are loaded from.

mcporter list [server]

  • Without arguments, lists every configured server (with live discovery + brief status).
  • With a server name, prints TypeScript-style signatures for each tool, doc comments, optional summaries, and any server instructions returned during MCP initialization.
  • With server.tool, prints just that tool; combine with --schema for a single tool schema.
  • Add --brief or --signatures with a server or server.tool target to keep the server header/instructions and print compact signatures without doc comments, examples, or schemas.
  • Add --status with a server target to print only the concise status row instead of full tool docs.
  • Add --exit-code to make the command exit 1 when any checked server is unhealthy, or --quiet to suppress output and imply --exit-code.
  • Hidden alias: list-tools (kept for muscle memory; not advertised in help output).
  • Hidden ad-hoc flag aliases: --sse for --http-url, --insecure for --allow-http (for plain HTTP testing).
  • Flags:
    • --brief compact single-server output; cannot be combined with --json, --schema, --verbose, or --all-parameters.
    • --signatures alias for --brief.
    • --all-parameters include every optional parameter in the signature.
    • --schema pretty-print the JSON schema for each tool.
    • --status check server status only; cannot be combined with --brief, --schema, or --all-parameters.
    • --exit-code exit 1 when any checked server is unhealthy.
    • --quiet suppress output and exit 1 when any checked server is unhealthy.
    • --timeout <ms> per-server timeout when enumerating all servers.
    • --no-oauth never start an interactive OAuth flow; use cached tokens only while keeping eligible connections pooled.

mcporter call <server.tool>

  • Invokes a tool once and prints the response; supports positional arguments via pseudo-TS syntax and --arg flags.
  • Useful flags:
    • --server, --tool alternate way to target a tool.
    • -- stop flag parsing so remaining tokens stay literal positional values.
    • --timeout <ms> override call timeout (defaults to CALL_TIMEOUT_MS).
    • --output text|markdown|json|raw choose how to render the CallResult.
    • --save-images <dir> persist image content blocks to files under the specified directory.
    • --raw-strings disable numeric coercion for flag-style and positional values.
    • --no-coerce disable all flag-style/positional value coercion.
    • --tail-log stream tail output when the tool returns log handles.
    • --no-oauth never start an interactive OAuth flow; use cached tokens only while keeping eligible connections pooled.

mcporter resource <server> [uri]

  • Lists resources exposed by a server when no URI is provided.
  • Reads an MCP resource when uri is provided and renders text, markdown, JSON, or raw output using the same output formatter as mcporter call.
  • Hidden alias: resources.
  • Useful flags:
    • --output auto|text|markdown|json|raw choose how to render the response.
    • --json shortcut for --output json.
    • --raw shortcut for --output raw.
    • --no-oauth never start an interactive OAuth flow; use cached tokens only while keeping eligible connections pooled.

mcporter serve [--servers a,b,c] [--stdio | --http <port>]

  • Exposes daemon-managed keep-alive servers as one MCP server for clients that consume MCP over stdio or Streamable HTTP.
  • tools/list queries the daemon for each selected server and publishes tools as server__tool; tools/call strips the prefix and routes the call through the daemon.
  • In HTTP mode, /mcp keeps the aggregate namespaced bridge, while /mcp/<server> exposes one selected keep-alive server with its original, unprefixed tool names.
  • Only configured keep-alive servers participate. Add "lifecycle": "keep-alive" to a server definition when you want it managed by the daemon.
  • Flags:
    • --stdio serve MCP over stdio; this is the default and is the mode to register with Claude Code, Codex, and similar clients.
    • --http <port> serve MCP Streamable HTTP on /mcp and /mcp/<server>, bound to 127.0.0.1 by default.
    • --host <host> override the HTTP bind host when you intentionally need a non-local listener.
    • --servers <csv> expose only the listed keep-alive server names.

mcporter generate-cli

  • Produces a standalone CLI for a single MCP server (optionally bundling or compiling with Bun).
  • Key flags:
    • --server <name> (or inline JSON) choose the server definition.
    • --command <url|command> point at an ad-hoc HTTP endpoint (include https:// or use host/path.tool) or a stdio command (anything with spaces, e.g., "npx -y chrome-devtools-mcp@latest"). If you omit --command, the first positional argument is inspected: whitespace → stdio, otherwise the parser probes for HTTP/HTTPS and falls back to config names.
    • --output <path> where to write the TypeScript template.
    • --bundle <path> emit a bundle (Node/Bun) ready for bun x.
    • --bundler rolldown|bun pick the bundler implementation (defaults to Rolldown unless the runtime resolves to Bun, in which case Buns bundler is used automatically; still requires a local Bun install).
    • --compile <path> compile with Bun (implies --runtime bun).
    • --include-tools <a,b,c> only generate subcommands for the specified tools (exact MCP tool names). It is an error if any requested tool is missing.
    • --exclude-tools <a,b,c> generate subcommands for every tool except the ones listed. Unknown tool names are ignored. If all tools are excluded, generation fails.
    • --include-tools and --exclude-tools are mutually exclusive.
    • --timeout <ms> / --runtime node|bun shared via the generator flag parser so defaults stay consistent.
    • --from <artifact> reuse metadata from an existing CLI artifact (legacy regenerate-cli behavior, must point at an existing CLI).
    • --dry-run print the resolved mcporter generate-cli ... command without executing (requires --from).
    • Positional shorthand: npx mcporter generate-cli linear uses the configured linear definition; npx mcporter generate-cli https://example.com/mcp treats the URL as an ad-hoc server definition.

mcporter emit-ts <server>

  • Emits TypeScript definitions (and optionally a ready-to-use client) describing a servers tools. This reuses the same formatter as mcporter list so doc comments, signatures, and examples stay in sync.
  • Modes:
    • --mode types --out <file.d.ts> (default) export an interface whose methods return Promise<CallResult>, with doc comments and optional summaries.
    • --mode client --out <file.ts> emit both the interface (<file>.d.ts) and a factory that wraps createServerProxy, returning objects whose methods resolve to CallResult.
  • Other flags:
    • --include-optional (alias --all-parameters) show every optional field.
    • --types-out <file> override where the .d.ts sits when using client mode.

For more detail (behavioral nuances, OAuth flows, etc.), see docs/spec.md and command-specific docs under docs/.