feat(server): carry re-pair guidance on a typed scope error - #196
Conversation
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>
|
Claude finished @chrischall's task in 3m 25s —— View job Review complete ✅
Summary: Clean, well-scoped fix. One pre-existing (not introduced by this PR, not blocking) observation: No 🔴 Important or 🟡 Nit findings against the diff itself. Verdict: pass |
|
✅ 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. |
🤖 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>
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: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-mcpwidened its scope twice in one afternoon.The fix
FetchproxyScopeErrorcarries the guidance on.hint, mirroringFetchproxyBridgeDownError— a shape consumers already surface verbatim, so adopting it needs no new habit:It extends
FetchproxyProtocolError, so every existing catch site andclassifyBridgeError()keep working unchanged.Two things done deliberately
All eight sites that turn an extension
ok:falseinto a throw now route throughprotocolErrorFrom(). 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 declaredrather 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 aFetchproxyProtocolErrorthat no longer represents the real path.