mcporter/tests
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
..
fixtures feat: support refreshable bearer stdio auth 2026-05-14 18:29:43 +01:00
helpers refactor: split call and runtime helpers 2026-03-29 09:36:35 +09:00
live fix: preserve call error exit behavior 2026-05-04 05:05:06 +01:00
adhoc-server.test.ts feat: support ad-hoc HTTP headers 2026-05-04 05:49:25 +01:00
build-bun.test.ts fix: embed bun binary version 2026-03-29 11:10:44 +09:00
call-arguments.test.ts fix(runtime): preserve disableOAuth across headless paths (#198) 2026-06-08 16:11:23 -07:00
chrome-devtools-compat.test.ts fix: patch chrome-devtools auto-connect hang 2026-05-14 12:51:16 +01:00
cli-auth-help.test.ts docs: document headless OAuth auth flow 2026-05-12 23:59:45 -03:00
cli-auth-retry.test.ts refactor: satisfy oxlint rules 2026-04-18 19:33:14 +01:00
cli-auth.test.ts fix: preserve headless auth stdout 2026-05-14 16:47:03 +01:00
cli-call-args.test.ts feat(call): infer stdio commands and single-tool servers 2025-11-10 18:43:04 +00:00
cli-call-errors.test.ts refactor: satisfy oxlint rules 2026-04-18 19:33:14 +01:00
cli-call-execution.test.ts fix(runtime): preserve disableOAuth across headless paths (#198) 2026-06-08 16:11:23 -07:00
cli-call-help.test.ts Handle help tokens for call/auth/list 2025-11-25 22:33:09 +01:00
cli-command-inference.test.ts Add hidden aliases and README artwork 2025-11-26 00:32:36 +01:00
cli-config-command.test.ts Merge pull request #171 from feniix/feat/headless-oauth-no-browser 2026-05-14 16:48:57 +01:00
cli-config-fallback.test.ts Fix daemon implicit config handling and docs 2025-11-28 07:33:44 +01:00
cli-config-routing.test.ts feat(config): tighten config cli ux 2025-11-08 13:36:40 +00:00
cli-daemon-fast-path.test.ts fix(replay): rewrite response ids during replay (#192) 2026-05-31 08:52:02 +01:00
cli-ephemeral-flags.test.ts feat: support ad-hoc HTTP headers 2026-05-04 05:49:25 +01:00
cli-flag-utils.test.ts fix(replay): rewrite response ids during replay (#192) 2026-05-31 08:52:02 +01:00
cli-force-exit-behavior.integration.test.ts test: prebuild dist before vitest 2026-05-09 12:30:11 +01:00
cli-generate-artifacts.test.ts Add Bun bundler option for CLI generation 2025-11-07 17:12:42 +00:00
cli-generate-cli.integration.test.ts test: prebuild dist before vitest 2026-05-09 12:30:11 +01:00
cli-generate-runner.test.ts fix: harden generated cli bundles 2026-05-14 19:22:37 +01:00
cli-global-flags.test.ts Fix global help and version flags 2025-11-07 14:57:58 +00:00
cli-help-shortcuts.test.ts docs(serve): document per-server HTTP endpoints 2026-06-08 12:16:38 -07:00
cli-image-output.test.ts refactor: streamline call parsing/output pipeline 2026-03-02 22:58:07 +00:00
cli-inspect-command.test.ts Add unit tests for CLI helpers 2025-11-07 04:33:21 +00:00
cli-list-classification.test.ts fix(runtime): preserve disableOAuth across headless paths (#198) 2026-06-08 16:11:23 -07:00
cli-list-flags.test.ts fix(runtime): preserve disableOAuth across headless paths (#198) 2026-06-08 16:11:23 -07:00
cli-list-formatting.test.ts feat: add compact list signatures 2026-05-04 06:52:34 +01:00
cli-list-help.test.ts feat: add compact list signatures 2026-05-04 06:52:34 +01:00
cli-list-json.test.ts fix(cli): fail unknown list targets 2026-06-08 12:05:42 -07:00
cli-list-stdio-logs.test.ts feat: add resource read command 2026-05-04 07:50:26 +01:00
cli-list-verbose-e2e.test.ts Release 0.6.2 2025-11-18 10:13:34 +01:00
cli-oauth-timeout-flag.test.ts feat: add resource read command 2026-05-04 07:50:26 +01:00
cli-output-utils.test.ts fix: harden concurrent config writes 2026-05-14 16:32:37 +01:00
cli-regenerate.test.ts refactor: satisfy oxlint rules 2026-04-18 19:33:14 +01:00
cli-resource-command.test.ts fix(runtime): preserve disableOAuth across headless paths (#198) 2026-06-08 16:11:23 -07:00
cli-serve-command.test.ts fix: cover serve daemon edge cases 2026-05-13 20:32:33 -04:00
cli-serve-runtime.test.ts feat: add mcporter serve bridge 2026-05-13 20:26:12 -04:00
cli-version.test.ts Fix version reporting without package.json 2025-11-07 15:39:22 +00:00
config-add-dry-run.test.ts test(config): add modular config tests and helpers 2025-11-17 13:00:20 +01:00
config-add-flags.test.ts feat: support static oauth clients 2026-05-04 08:03:13 +01:00
config-add-imports.test.ts fix: preserve config imports defaults 2025-12-30 01:25:04 +01:00
config-add-persist.test.ts test(config): add modular config tests and helpers 2025-11-17 13:00:20 +01:00
config-add-scope-behavior.test.ts fix: honor xdg directories 2026-05-04 08:14:31 +01:00
config-add-scope.test.ts fix: honor xdg directories 2026-05-04 08:14:31 +01:00
config-add-sse.test.ts test(config): add modular config tests and helpers 2025-11-17 13:00:20 +01:00
config-command-string.test.ts fix: preserve spaced stdio paths 2026-05-06 22:49:36 +01:00
config-doctor.test.ts chore: fix lint and tests 2025-12-03 17:20:09 +00:00
config-get-json.test.ts test(config): add modular config tests and helpers 2025-11-17 13:00:20 +01:00
config-import-dedupe.test.ts test(config): add modular config tests and helpers 2025-11-17 13:00:20 +01:00
config-import-paths.test.ts fix: stabilize windows ci 2025-11-10 16:13:27 +00:00
config-import.test.ts fix: harden concurrent config writes 2026-05-14 16:32:37 +01:00
config-imports-unit.test.ts test: strengthen claude import coverage 2025-11-22 01:59:49 +01:00
config-imports.test.ts feat: support refreshable bearer stdio auth 2026-05-14 18:29:43 +01:00
config-layered.test.ts fix: fall back to legacy config after empty xdg home (#185) 2026-05-21 22:15:13 +01:00
config-list-text-footer.test.ts test(config): add modular config tests and helpers 2025-11-17 13:00:20 +01:00
config-list.test.ts test(config): add modular config tests and helpers 2025-11-17 13:00:20 +01:00
config-missing.test.ts fix: finalize jsonc config support coverage (#42) (thanks @aryasaatvik) 2026-03-03 00:26:13 +00:00
config-normalize.test.ts fix: support daemon idle timeout config 2026-05-20 17:34:42 +01:00
config-remove.test.ts test(config): add modular config tests and helpers 2025-11-17 13:00:20 +01:00
config-render.test.ts feat: support refreshable bearer stdio auth 2026-05-14 18:29:43 +01:00
config-resolution.test.ts fix: fall back to legacy config after empty xdg home (#185) 2026-05-21 22:15:13 +01:00
config-schema-file.test.ts feat: add per-server tool filtering 2026-04-18 21:52:18 +01:00
config-shared.test.ts test(config): add modular config tests and helpers 2025-11-17 13:00:20 +01:00
config-sources.test.ts feat(list): show verbose config sources 2025-11-17 08:16:26 +01:00
daemon-cli-command.test.ts Fix daemon implicit config handling and docs 2025-11-28 07:33:44 +01:00
daemon-client-config-stale.test.ts fix: reconcile daemon lifecycle starts 2026-05-28 16:45:31 +01:00
daemon-client-lifecycle.test.ts fix: reconcile daemon lifecycle starts 2026-05-28 16:45:31 +01:00
daemon-client-timeout.test.ts fix: reconcile daemon lifecycle starts 2026-05-28 16:45:31 +01:00
daemon-client.test.ts fix: reconcile daemon lifecycle starts 2026-05-28 16:45:31 +01:00
daemon-host.test.ts fix(runtime): preserve disableOAuth across headless paths (#198) 2026-06-08 16:11:23 -07:00
daemon-launch.test.ts fix: start bun daemon children through nohup 2026-05-04 07:32:27 +01:00
daemon-request-utils.test.ts fix: support daemon idle timeout config 2026-05-20 17:34:42 +01:00
daemon.integration.test.ts fix(daemon): stop direct starts orphaning live daemon 2026-06-08 11:35:22 -07:00
emit-ts.test.ts fix: handle CLI generation edge cases 2026-05-04 05:05:12 +01:00
ephemeral-target.test.ts fix(runner): harden summary+sleep handling 2025-11-14 22:17:39 +01:00
error-classifier.test.ts fix: detect auth errors from error.code property (StreamableHTTPError/SseError) 2026-03-28 19:21:23 +00:00
fs-json.test.ts fix: harden concurrent config writes 2026-05-14 16:32:37 +01:00
generate-cli-helpers.test.ts fix: improve generated cli raw and array parsing 2026-05-04 05:55:18 +01:00
generate-cli.test.ts fix: make generated cli bundles deterministic 2026-05-20 16:53:37 +01:00
generate-definition.test.ts Document inline command shorthand 2025-11-07 15:08:44 +00:00
generator-flag-parser.test.ts Add emit-ts improvements and shared CLI infrastructure 2025-11-07 01:16:14 +00:00
index-api.test.ts Polish CLI argument parsing and clean lint warnings 2025-11-05 22:39:37 +00:00
keep-alive-runtime.test.ts fix(runtime): preserve disableOAuth across headless paths (#198) 2026-06-08 16:11:23 -07:00
lifecycle.test.ts Release 0.5.8 2025-11-15 07:17:20 +01:00
list-detail-helpers.test.ts test: satisfy lint for list helpers 2025-11-08 01:07:57 +00:00
list-format.test.ts fix(generate-cli): stabilize http command inference 2025-11-18 05:32:45 +00:00
list-inline-stdio.test.ts feat: add resource read command 2026-05-04 07:50:26 +01:00
list-output.test.ts feat: add compact list signatures 2026-05-04 06:52:34 +01:00
node-http-fetch.test.ts fix: add HTTP fetch compatibility mode 2026-05-14 17:31:36 +01:00
oauth-callback.test.ts feat: add OAuth no-browser session support 2026-05-12 23:59:34 -03:00
oauth-open-external.test.ts fix: quote Windows OAuth URLs (#136) 2026-04-18 20:03:37 +01:00
oauth-persistence.test.ts fix: harden OAuth vault recovery (#190) 2026-05-26 15:47:31 +01:00
oauth-session.test.ts feat: add OAuth no-browser session support 2026-05-12 23:59:34 -03:00
paths.test.ts fix: fall back to legacy config after empty xdg home (#185) 2026-05-21 22:15:13 +01:00
record-replay-cli-close.test.ts fix(replay): rewrite response ids during replay (#192) 2026-05-31 08:52:02 +01:00
record-replay-cli.test.ts fix(replay): rewrite response ids during replay (#192) 2026-05-31 08:52:02 +01:00
record-replay.test.ts fix(replay): rewrite response ids during replay (#192) 2026-05-31 08:52:02 +01:00
result-utils.test.ts fix: harden concurrent config writes 2026-05-14 16:32:37 +01:00
runtime-cache-policy.test.ts fix(runtime): preserve disableOAuth across headless paths (#198) 2026-06-08 16:11:23 -07:00
runtime-cache.test.ts fix(runtime): preserve disableOAuth across headless paths (#198) 2026-06-08 16:11:23 -07:00
runtime-call-timeout.test.ts fix(runtime): preserve disableOAuth across headless paths (#198) 2026-06-08 16:11:23 -07:00
runtime-compose.test.ts fix(runtime): preserve disableOAuth across headless paths (#198) 2026-06-08 16:11:23 -07:00
runtime-error-reset.test.ts fix(runtime): preserve disableOAuth across headless paths (#198) 2026-06-08 16:11:23 -07:00
runtime-header-utils.test.ts Test runtime header materialization 2025-11-07 04:24:29 +00:00
runtime-integration.test.ts fix(runtime): preserve disableOAuth across headless paths (#198) 2026-06-08 16:11:23 -07:00
runtime-oauth-connect.test.ts fix: preserve valid cached OAuth tokens 2026-05-20 17:21:05 +01:00
runtime-oauth-detection.test.ts feat: support refreshable bearer stdio auth 2026-05-14 18:29:43 +01:00
runtime-oauth-timeout.test.ts refactor(cli): split generate-cli runner 2025-11-17 19:15:28 +01:00
runtime-oauth-utils.test.ts fix: extend oauth browser timeout 2026-05-08 03:56:38 +01:00
runtime-process-utils.test.ts fix: escalate stuck stdio shutdowns 2026-04-18 20:07:40 +01:00
runtime-transport.test.ts fix(runtime): preserve disableOAuth across headless paths (#198) 2026-06-08 16:11:23 -07:00
runtime-utils.test.ts refactor(cli): split generate-cli runner 2025-11-17 19:15:28 +01:00
runtime.test.ts fix: resolve config env placeholders 2026-05-09 13:14:09 +01:00
schema-cache.test.ts test: make xdg path assertions portable 2026-05-04 08:17:42 +01:00
sdk-patches.test.ts fix: escalate stuck stdio shutdowns 2026-04-18 20:07:40 +01:00
serve.test.ts docs(serve): document per-server HTTP endpoints 2026-06-08 12:16:38 -07:00
server-proxy.test.ts fix(runtime): preserve disableOAuth across headless paths (#198) 2026-06-08 16:11:23 -07:00
stdio-servers.integration.test.ts test: prebuild dist before vitest 2026-05-09 12:30:11 +01:00
template-cwd.test.ts fix: anchor generated cli stdio cwd 2026-05-04 05:16:43 +01:00
tool-cache.test.ts fix(runtime): preserve disableOAuth across headless paths (#198) 2026-06-08 16:11:23 -07:00
tool-filters.test.ts feat: add per-server tool filtering 2026-04-18 21:52:18 +01:00
vault-command.test.ts feat: add headless OAuth vault seeding 2026-05-09 14:55:44 +01:00
version-consistency.test.ts chore(version): derive runtime version from package.json 2025-11-17 08:16:30 +01:00