Skip to content

fix(mcp): bridge follow-ups from #109 review — smoke-script auth, MV3 heartbeat, pending-key collisions#110

Merged
BitHighlander merged 2 commits into
developfrom
fix/mcp-bridge-followups
Jul 16, 2026
Merged

fix(mcp): bridge follow-ups from #109 review — smoke-script auth, MV3 heartbeat, pending-key collisions#110
BitHighlander merged 2 commits into
developfrom
fix/mcp-bridge-followups

Conversation

@BitHighlander

@BitHighlander BitHighlander commented Jul 15, 2026

Copy link
Copy Markdown
Collaborator

Follow-ups from the review of #109 (merged). All client-side; the tool contract is unchanged.

Fixes

1. Smoke script could never pass (must-fix). scripts/test-mcp-bridge.mjs sent no Authorization header, but the merged vault (#360) runs auth.requireAuth on POST /mcp — every run failed. It now sends Bearer $KEEPKEY_API_KEY, fails fast with guidance when unset, and names the cause on a 401. (Node's fetch sends no Origin/Sec-Fetch-Site, which is what passes the vault's browser-exclusion check.)

2. MV3 heartbeat. Chrome evicts an idle service worker after ~30s, and an idle WebSocket does not prevent that — only WS traffic resets the timer. Without a ping the worker dies on a quiet machine, takes the socket with it, and nothing wakes it back up: an agent polling bex_status would see bridge: "down" indefinitely. A 20s ping keeps us inside the window; the vault ignores frames matching no pending call, so no server-side change.

3. clip() could throw on the success path. It runs on every provider call's result, so a BigInt/circular JSON.stringify failure would have turned a good wallet response into a dApp-visible error. Now returns '[unserializable]'.

4. Approval queue keyed by dApp-supplied id. dApp ids are per-page counters, so two tabs collide on 1: one settle evicted the other tab's entry. Read-only today, but a Phase-2 bex_approve_request(id) would have resolved the wrong origin's prompt — fixed now, before the control tools land on top of it. The queue is keyed by an internal uuid; id is retained for correlating with dApp logs.

5. Agent path init parity. walletRequest re-checks init after ensureStarted() like WALLET_REQUEST does, so a vault-closed agent call gets the actionable "launch the Vault" error.

Verification

make test 111 pass (providerLog 5 → 8: adds same-id two-tab isolation + the unserializable-payload regression). make type-check 15/15, make build green.

Live run against the vault, with a real pairing key — the fixed script doing what it could not do before:

ok: initialize returns keepkey-vault serverInfo
ok: tools/list includes bex_status / bex_accounts / bex_pending_requests / bex_connected_sites / bex_logs
ok: bex_status reports bridge state truthfully
bex_status → { bridge: 'down', connected: false, error: '...is the BEX running with Agent mode enabled?' }
exit=2

Auth, the tool catalog, and bex_status's truthful bridge-down reporting are all confirmed. The last mile still needs a human: flip "Enable Agent mode" in the extension options, reload the extension from this branch's dist/, then re-run for exit 0 — that also exercises the heartbeat and bex_accounts.

KEEPKEY_API_KEY=<pairing key> node scripts/test-mcp-bridge.mjs

Related — a vault-side bug this surfaced

Sending the bearer surfaced a real vault bug: /mcp cannot return its 401 at all. auth.requireAuth (rest-api.ts:1305) throws outside the try at :1420 that maps HttpError → status, and Bun.serve has no top-level error handler, so the throw escapes fetch() and the caller gets a dropped socket instead of a 401.

Independent of this PR (nothing here depends on it), but it's what makes a misconfigured key report 401 rather than an inscrutable fetch failed. Written up for the vault owners in HANDOFF_vault_mcp_401.md on this branch.

🤖 Generated with Claude Code

BitHighlander and others added 2 commits July 15, 2026 20:38
…MV3 heartbeat, pending-key collisions

Addresses the review findings on #109 (all client-side; no behavior
change to the tool contract).

- scripts/test-mcp-bridge.mjs: send Authorization: Bearer $KEEPKEY_API_KEY.
  The merged vault (#360) bearer-authenticates POST /mcp, so the script as
  shipped could only ever exit 1 on a 401. Fails fast with guidance when the
  env var is unset, and names the cause on a 401.
- mcpBridge.ts: 20s heartbeat ping. MV3 evicts an idle service worker after
  ~30s and an idle WebSocket does NOT prevent that — only WS traffic resets
  the timer. Without it the worker dies on a quiet machine, takes the socket
  with it, and nothing wakes it: agents would see bridge "down" indefinitely.
  The vault ignores frames matching no pending call, so no server change.
- providerLog.ts: clip() can no longer throw. It runs on the SUCCESS path of
  every provider call, so a BigInt/circular stringify failure would have
  turned a good wallet response into a dApp-visible error.
- providerLog.ts: key the approval queue by an internal uuid, not the
  dApp-supplied id. dApp ids are per-page counters, so two tabs collide on
  "1": one settle evicted the other tab's entry, and a Phase-2
  bex_approve_request(id) would have resolved the WRONG origin's prompt.
  Fixed now, before the control tools land on top of it.
- index.ts: the agent walletRequest path re-checks init after ensureStarted(),
  matching WALLET_REQUEST, so a vault-closed agent call gets the actionable
  "launch the Vault" error.

Tests: 111 pass (providerLog 5 → 8; adds same-id two-tab isolation and the
unserializable-payload regression). make type-check 15/15, make build green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…s-repo PR

Writes up the vault-side finding from the #109 review so the vault owners
can decide: /mcp cannot return its 401 because auth.requireAuth throws
outside the try that maps HttpError → status, and Bun.serve has no
top-level error handler, so the throw escapes fetch().

Includes the diff, the verification already run, the explicit caveat that
the pre-fix symptom was never observed on a healthy vault (the original
dropped-socket reading was taken while the vault was flapping), and a
pointer to the existing branch/PR should they want it.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@BitHighlander BitHighlander merged commit 29ff979 into develop Jul 16, 2026
5 of 6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant