Some checks failed
* 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>
7.6 KiB
7.6 KiB
| summary | read_when | |
|---|---|---|
| Quick reference for mcporter subcommands, their arguments, and shared global flags. |
|
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
instructionsreturned during MCP initialization. - With
server.tool, prints just that tool; combine with--schemafor a single tool schema. - Add
--briefor--signatureswith a server orserver.tooltarget to keep the server header/instructions and print compact signatures without doc comments, examples, or schemas. - Add
--statuswith a server target to print only the concise status row instead of full tool docs. - Add
--exit-codeto make the command exit 1 when any checked server is unhealthy, or--quietto suppress output and imply--exit-code. - Hidden alias:
list-tools(kept for muscle memory; not advertised in help output). - Hidden ad-hoc flag aliases:
--ssefor--http-url,--insecurefor--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
--argflags. - 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 toCALL_TIMEOUT_MS).--output text|markdown|json|raw– choose how to render theCallResult.--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
uriis provided and renders text, markdown, JSON, or raw output using the same output formatter asmcporter 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/listqueries the daemon for each selected server and publishes tools asserver__tool;tools/callstrips the prefix and routes the call through the daemon.- In HTTP mode,
/mcpkeeps 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/mcpand/mcp/<server>, bound to127.0.0.1by 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 (includehttps://or usehost/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 forbun x.--bundler rolldown|bun– pick the bundler implementation (defaults to Rolldown unless the runtime resolves to Bun, in which case Bun’s 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-toolsand--exclude-toolsare 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 (legacyregenerate-clibehavior, must point at an existing CLI).--dry-run– print the resolvedmcporter generate-cli ...command without executing (requires--from).- Positional shorthand:
npx mcporter generate-cli linearuses the configuredlineardefinition;npx mcporter generate-cli https://example.com/mcptreats the URL as an ad-hoc server definition.
mcporter emit-ts <server>
- Emits TypeScript definitions (and optionally a ready-to-use client) describing
a server’s tools. This reuses the same formatter as
mcporter listso doc comments, signatures, and examples stay in sync. - Modes:
--mode types --out <file.d.ts>(default) – export an interface whose methods returnPromise<CallResult>, with doc comments and optional summaries.--mode client --out <file.ts>– emit both the interface (<file>.d.ts) and a factory that wrapscreateServerProxy, returning objects whose methods resolve toCallResult.
- Other flags:
--include-optional(alias--all-parameters) – show every optional field.--types-out <file>– override where the.d.tssits when using client mode.
For more detail (behavioral nuances, OAuth flows, etc.), see docs/spec.md and
command-specific docs under docs/.