Skip to content

feat(server): carry re-pair guidance on a typed scope error - #196

Merged
chrischall merged 1 commit into
mainfrom
feat/typed-scope-error
Aug 3, 2026
Merged

feat(server): carry re-pair guidance on a typed scope error#196
chrischall merged 1 commit into
mainfrom
feat/typed-scope-error

Conversation

@chrischall

Copy link
Copy Markdown
Owner

Closes #195.

The problem

Gate #2 refuses a request whenever an MCP widens its declared scope, until the user re-approves. #187 taught the CLI to explain that — but MCPs don't go through the CLI. They consume the bridge via @fetchproxy/bootstrap, catch failures, and re-wrap them in their own copy, so the remedy never survived:

SignUpGenius auth: no SIGNUPGENIUS_USER_KEY or SIGNUPGENIUS_EMAIL/PASSWORD set,
and fetchproxy lift failed: cookie keys not in declared set: refreshToken

Structurally accurate and useless — it names the symptom, points at auth config that isn't the problem, and omits the one action that fixes it. This is a routine event: signupgenius-mcp widened its scope twice in one afternoon.

The fix

FetchproxyScopeError carries the guidance on .hint, mirroring FetchproxyBridgeDownError — a shape consumers already surface verbatim, so adopting it needs no new habit:

SignUpGenius auth: fetchproxy lift failed: cookie keys not in declared set: refreshToken.
the declared scope changed since you paired, so the extension is refusing the request.
Revoke this MCP in the Transporter extension popup, then re-run — you will be asked to
approve the new scope. This is not a version problem and does not need an update.

It extends FetchproxyProtocolError, so every existing catch site and classifyBridgeError() keep working unchanged.

Two things done deliberately

All eight sites that turn an extension ok:false into a throw now route through protocolErrorFrom(). Typing it at one site and leaving seven others would recreate the same partial-coverage bug in a new place.

The factory matches not in declared rather than enumerating the nine wordings — enumerating them is exactly how the first cut of the CLI fix missed five (#188).

The CLI drops its own regex and reads the typed error: the same knowledge in one place instead of two copies free to drift.

Verification

1105 tests, typecheck and build clean. The CLI tests now construct errors via protocolErrorFrom() — the way the server actually builds them — rather than hand-rolling a FetchproxyProtocolError that no longer represents the real path.

Gate #2 refuses a request whenever an MCP widens its declared scope
until the user re-approves. The CLI learned to explain that (#187), but
the explanation lived in the CLI — and MCPs do not go through the CLI.
They consume the bridge via @fetchproxy/bootstrap, catch failures, and
re-wrap them in their own copy, so the remedy never survived:

  SignUpGenius auth: no SIGNUPGENIUS_USER_KEY or SIGNUPGENIUS_EMAIL/
  PASSWORD set, and fetchproxy lift failed: cookie keys not in declared
  set: refreshToken

Structurally accurate and useless: it names the symptom, points at auth
config that is not the problem, and omits the one action that fixes it.

FetchproxyScopeError carries the guidance on `.hint`, mirroring
FetchproxyBridgeDownError — a shape consumers already surface verbatim,
so adopting it needs no new habit. It extends FetchproxyProtocolError, so
every existing catch site and classifyBridgeError() keep working.

All eight sites that turn an extension `ok:false` into a throw now route
through protocolErrorFrom(), so a scope rejection cannot lose its
guidance again at one forgotten call site. The factory matches on "not in
declared" rather than enumerating the nine wordings — enumerating them is
exactly how the first cut of the CLI fix missed five.

The CLI drops its own regex and reads the typed error instead: the same
knowledge in one place rather than two copies free to drift.

Closes #195

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@chrischall chrischall added the enhancement New feature or request label Aug 3, 2026
@github-actions github-actions Bot added the auto-review Trigger Claude + Copilot review on this PR label Aug 3, 2026
@claude

claude Bot commented Aug 3, 2026

Copy link
Copy Markdown

Claude finished @chrischall's task in 3m 25s —— View job


Review complete ✅

  • Read CLAUDE.md conventions
  • Read PR diff
  • Review packages/server/src/ws-server.ts changes (typed scope error, protocolErrorFrom())
  • Review packages/cli/src/bridge-errors.ts changes (regex removal)
  • Review test coverage
  • Post findings and verdict

Summary: Clean, well-scoped fix. FetchproxyScopeError mirrors FetchproxyBridgeDownError's existing .hint shape exactly, protocolErrorFrom() is genuinely applied at all 8 sites that turn an extension ok:false into a throw (verified by grep — the remaining new FetchproxyProtocolError(...) calls are for unrelated failure modes: malformed-response-shape errors, rejectAllPending, and the pair-pending guard, none of which are extension scope rejections), and the regex (/not in declared/) matches all nine real wire-format rejection strings emitted by extension-core/src/background.ts (verified against the actual source, not just the test fixtures). The CLI now reads the typed error instead of re-deriving its own copy of the guidance text, which is exactly the stated goal. Tests cover the new factory, the CLI consumption path, and confirm classifyBridgeError still buckets it under 'protocol' for back-compat.

One pre-existing (not introduced by this PR, not blocking) observation: extension-core/background.ts:1367,1466 also rejects with capability "X" not granted (declared: [...]) when an MCP widens its capability set (not a key/bucket) — per CLAUDE.md, "Tightening (or widening) the capability set forces a re-pair," so this is the same class of gate-#2 event, but its wording doesn't contain "not in declared" and so falls through to the generic "version mismatch" hint rather than getting FetchproxyScopeError. This gap predates this PR (the CLI's old regex had the identical blind spot), so it's out of scope here, but worth a future follow-up if capability-widening errors are ever seen in the wild.

No 🔴 Important or 🟡 Nit findings against the diff itself.

Verdict: pass

@github-actions

github-actions Bot commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

✅ Auto-review verdict: pass — Well-scoped, correctly implemented typed-error fix: FetchproxyScopeError mirrors the existing FetchproxyBridgeDownError.hint shape, protocolErrorFrom() is applied at all 8 relevant sites, and the matching regex covers all nine real extension rejection wordings. No blocking issues.

@chrischall chrischall added the ready-to-merge Owner has reviewed; arm auto-merge to land when CI is green label Aug 3, 2026
@chrischall
chrischall enabled auto-merge (squash) August 3, 2026 05:02
@chrischall
chrischall merged commit 63cbc2e into main Aug 3, 2026
16 checks passed
@chrischall
chrischall deleted the feat/typed-scope-error branch August 3, 2026 05:03
chrischall added a commit that referenced this pull request Aug 3, 2026
🤖 I have created a release *beep* *boop*
---


##
[1.10.0](v1.9.1...v1.10.0)
(2026-08-03)


### Features

* **server:** carry re-pair guidance on a typed scope error
([#196](#196))
([63cbc2e](63cbc2e))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

auto-review Trigger Claude + Copilot review on this PR enhancement New feature or request ready-to-merge Owner has reviewed; arm auto-merge to land when CI is green

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Bridge-error guidance stops at the CLI — MCPs re-wrap and lose it

1 participant