Skip to content

[DESK-30518][DESK-28302] - Stop Claude Desktop asking to approve Beeper over and over - #4

Open
iFixRobots wants to merge 1 commit into
beeper-mainfrom
iFixRobots/desk-30518
Open

[DESK-30518][DESK-28302] - Stop Claude Desktop asking to approve Beeper over and over#4
iFixRobots wants to merge 1 commit into
beeper-mainfrom
iFixRobots/desk-30518

Conversation

@iFixRobots

@iFixRobots iFixRobots commented Sep 3, 2026

Copy link
Copy Markdown

Problem

Claude Desktop users keep getting Beeper's "Approve connection" dialog, approve it, and get it again minutes later. Two rageshakes show the same shape: Beeper issues the code, and nobody ever calls the token endpoint.

This extension is a September 2025 copy of upstream mcp-remote 0.1.29 with our defaults patched in, and it has two defects that feed each other:

  1. It can only finish a login when it starts. When a Beeper token expires while Claude is open, every request gets a 401, every 401 opens the browser and pops the dialog, and the code that comes back is dropped because nothing is waiting for it. Only a full Claude restart gets a working login again. With the default 30-day token, every user hits this monthly.
  2. It reuses one fixed callback port, treats a live lock as stale after 30 minutes, and dies with a TypeError if the port is taken, so a leftover copy of the proxy makes even a fresh start hang or crash, and every approval is delivered to the wrong process and thrown away.

Upstream fixed the first class in August 2026 (#302, #326, #340, #307, #348) and rewrote the coordination behind the second (#325, #262, #315, #329, #330, #331). None of it ever reached us.

Fix

Stop carrying a copy of upstream. @beeper/mcp-remote is now a thin layer over mcp-remote 0.8.3, bundled into one file at build time:

  • src/setup.ts applies the Beeper defaults (URL, the client name shown on the dialog, read and write scope, config dir), rewrites process.argv, and exits the moment the process that launched it is gone. That last part is ours: upstream still only wires its shutdown hooks after login, so without it a stuck sign-in can leave a proxy behind holding the callback port.
  • src/authorize.ts is what upstream gets when it imports its browser opener (aliased in build.ts, upstream's code untouched). For an authorize URL on the configured Beeper origin it does what the consent page used to do, in-process: posts the authorization request to Beeper's callback endpoint, waits for the approval, hands the code to its own loopback callback, and opens claude:// to bring Claude back. No browser tab, no page; Beeper's approval dialog is the whole experience. Anything else still goes to the real opener, so Codex, Cursor and Claude Code keep their browser flow and see Beeper's consent page.
  • src/proxy.ts imports setup and then upstream's CLI, so everything runs in one process. Everything under src/lib, the copied client, the custom pages and the tests go away; updating upstream is a version bump from here on.

In-process is not a style choice. Claude Desktop 1.44 runs node extensions inside its own Electron helper, where process.execPath is Claude's binary and cannot be turned into node. Spawning it launches Claude's helper as an app ("Unable to find helper app"), which shows up as a crash loop and a string of keychain prompts.

The default URL is now http://127.0.0.1:23373, matching what Beeper's own Copy Command hands out and what Beeper advertises as its OAuth resource.

Verified against a dev build of beeper/beeper#4685 with the real Claude Desktop 1.44 and Codex: a clean install through Beeper's Download Extension button (zero consent page loads, one dialog, code exchanged 13 ms after Approve), an update from the currently published build (exactly one dialog-only sign-in, because the old build's token folder is not reused), a token revoked mid-session (one dialog, login completes in place, prompt answered, no extension restart), and a Beeper restart under a live session (no dialog). Against a stand-in endpoint the same build also passes the lifecycle checks: a proxy left waiting at the approval exits within two seconds of its parent dying.

A security pass over the wrapper also dropped --allow-http (upstream already allows plain http for loopback; anything else has to be https), limited the in-process sign-in to the configured Beeper origin so a foreign authorization server gets the browser flow instead, and made the post-sign-in claude:// hop a fixed value rather than something read back from the URL.

Users also pick up upstream's year: token files written 0600 instead of world-readable, the callback server bound to 127.0.0.1 instead of every interface, the Windows callback host fix, SDK 1.25, and the newer flags. Existing installs sign in once more after updating. Version 0.1.1.

Needs https://github.com/beeper/beeper/pull/4685 first: without it upstream refuses Beeper's metadata at startup.

Fixes DESK-30518
Fixes DESK-28302

@linear-code

linear-code Bot commented Sep 3, 2026

Copy link
Copy Markdown

DESK-30518

DESK-28302

@iFixRobots
iFixRobots marked this pull request as ready for review September 3, 2026 10:41
@iFixRobots

Copy link
Copy Markdown
Author

@indent

@indent

indent Bot commented Sep 3, 2026

Copy link
Copy Markdown
PR Summary

Stops the Claude Desktop extension from repeatedly prompting to approve Beeper by dropping the vendored September-2025 fork of mcp-remote (which could only complete a login at startup and mishandled the callback port) in favor of a thin wrapper over upstream mcp-remote 0.8.3, bundled at build time. The two long-standing defects that caused the repeated approval dialog are fixed upstream, and the wrapper adds a Beeper-specific shutdown so a stuck sign-in can't leave a proxy behind holding the OAuth callback port.

  • Rewrites src/proxy.ts into a thin layer: applies Beeper defaults (server URL, dialog client name, read write scope, config dir), spawns the bundled upstream proxy, pipes stdin through, and kills the child when stdin ends or a signal arrives.
  • Adds src/mcp-remote.ts (import 'mcp-remote/dist/proxy.js') so bun bundles upstream's CLI into dist/mcp-remote.js.
  • Deletes the vendored fork and tests (src/client.ts, src/html.ts, src/lib/*, utils.test.ts; ~4,200 lines removed) and swaps the mcp-remote git dependency for mcp-remote@0.8.3 in devDependencies.
  • Updates the build to bundle both entrypoints, bumps versions to 0.1.0, refreshes README/AGENT docs, and drops the now-empty unit-test CI step.

Issues

1 potential issue found:

  • The wrapper always appends --allow-http to the upstream proxy, so a non-localhost http:// server URL is now accepted and the OAuth flow/tokens are sent in cleartext; the previous build rejected such URLs. Latent: triggers only if a user points BEEPER_DESKTOP_BASE_URL (or the first-arg URL) at a non-localhost http host. Default localhost use is unaffected.

Select any checkbox above to have Indent auto-fix the issue

Bulk Actions
  • Autofix all issues

Comment thread src/proxy.ts Outdated
'--transport',
'http-only',
'--allow-http',
'--static-oauth-client-metadata',

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Failing Latent · --allow-http passed unconditionally allows plaintext OAuth to non-localhost URLs

--allow-http is passed on every invocation. Upstream mcp-remote 0.8.3 only requires this flag for non-localhost, non-HTTPS URLs (it already allows http for localhost); it refuses other http URLs otherwise ("Non-HTTPS URLs are only allowed for localhost or when --allow-http flag is provided"). The old vendored proxy had the same guard (src/lib/utils.ts:760) and never passed --allow-http. So this is a behavior broadening: if a user sets BEEPER_DESKTOP_BASE_URL (exposed via manifest user_config) or the first-arg URL to a non-localhost http:// host, the old build errored but the new build silently proceeds and sends the OAuth flow/tokens over plaintext. It's unneeded for the default localhost URL, so consider dropping it or gating it on a localhost host check.

@indent

indent Bot commented Sep 3, 2026

Copy link
Copy Markdown

I've reviewed the PR. It's a clean refactor: verified the typecheck, the bun bundle, and a runtime smoke test (args forwarded with --client stripped, config dir defaulted, stdin piped, SIGTERM forwarded on stdin end, exit code propagated).

One latent finding, posted above: src/proxy.ts now passes --allow-http unconditionally, which permits a non-localhost http:// URL and would send OAuth over cleartext, where the old build refused it. Only triggers if someone overrides BEEPER_DESKTOP_BASE_URL to a non-localhost http host; default localhost use is fine.

Want me to fix that (drop --allow-http, or gate it on a localhost check)? Or let me know what you'd like next.

@iFixRobots
iFixRobots force-pushed the iFixRobots/desk-30518 branch 4 times, most recently from 51e41a8 to 7c56868 Compare September 3, 2026 13:14
…er over and over

Replace the copied mcp-remote sources with a thin layer over upstream
0.8.3 that runs in-process: apply the Beeper defaults, rewrite argv,
exit when the launching process is gone, complete Beeper's sign-in over
loopback instead of a browser, then import upstream's CLI.
@iFixRobots

Copy link
Copy Markdown
Author

@mattwondra - I could use your eyes to make sure I'm not doing anything super bad here, but I've tested this pretty extensively and I'm covering both upgrades and new setups. The only thing I'm not checking is whether they are downloading the extension while having an older client, but I'm not taking that into consideration.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant