feat(mcp): first-class lite mode — npx beeperbox against a local Beeper Desktop#17
Merged
Conversation
…er Desktop Run the MCP verb server standalone (no Docker/Electron/Xvfb) against a Beeper Desktop the user already runs. Same single mcp/server.js the container runs, so the tool surface and serverInfo.version are identical by construction. - npm package `beeperbox` (zero-dep) with a `beeperbox` bin -> `npx beeperbox` / `--stdio` - version single-sourced from package.json (the container COPYs it into the image); a unit test pins serverInfo.version == package.json and the exact 12-verb set - startup preflight: one bounded /v1/accounts probe logs a clear reachable+token verdict on boot (replaces the Docker HEALTHCHECK lite mode lacks); non-fatal, BEEPERBOX_PREFLIGHT=0 opts out - fix: the echo-guard sent-ledger defaulted to a container-only /root path that silently failed to persist on a normal host -> per-user XDG default ($XDG_CONFIG_HOME/beeperbox or ~/.config/beeperbox/sent-ledger.json), parent dir auto-created; one code path for both modes (homedir is /root in the container) - de-containerize the BEEPER_TOKEN-missing error copy - docs: README "Lite mode"; PRD (reverses the npm + local-Beeper non-goals, §3/§8); GUIDE section; CHANGELOG [Unreleased] Validated live against a real Beeper account: the send -> restart -> poll echo-guard round-trip reads back source:"api" across HTTP, the installed npx bin, and stdio; all preflight branches (OK/401/unreachable/skip/opt-out); the rebuilt image keeps the full MCP guard matrix and reads its version from the copied package.json. Regression tests for the ledger fix were watched failing on the pre-fix code. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…or mcp/) Future lite-mode publishes run via .github/workflows/publish.yml — npm trusted publishing over OIDC (no NPM_TOKEN), manual workflow_dispatch, idempotent (skips an already-published version), asserts the registry end-state instead of trusting npm's exit code. Adapted from the canonical hamr0 template for this repo: the package root is the mcp/ subdir (defaults.run.working-directory), and the template's `npm ci` step is dropped since the package is zero-dependency with no lockfile. PRD §8 updated to describe OIDC publishing (no token) and the one-time manual name reservation. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
/security on the lite-mode change found a HIGH issue: the MCP server bound 0.0.0.0 unconditionally. Safe in the container (Docker publishes the port on 127.0.0.1 — that loopback PUBLISH is the boundary), but lite mode (`npx beeperbox`) has no such layer, so it listened on every interface. A same-network attacker could reach the full tool surface (read every message, send across every network) unauthenticated by spoofing the Host header past the allowlist — demonstrated live against the host LAN IP. - new MCP_BIND_ADDR env, default 127.0.0.1 (loopback) — safe default for lite mode - Dockerfile bakes ENV MCP_BIND_ADDR=0.0.0.0 so the container's published port keeps working AND the CI guard-check (runs `node` directly) still binds all ifaces - regression test pins the loopback default so it can't silently revert - docs corrected: the "same posture as the container" claim was false — lite binds loopback, the container relies on the loopback publish (README, mcp/README, GUIDE, PRD §5.3/§7); CHANGELOG Security entry Verified live: default lite mode binds 127.0.0.1 (LAN attack now connection-refused, loopback client 200); MCP_BIND_ADDR=0.0.0.0 still binds all interfaces; rebuilt image binds 0.0.0.0 via ENV with the published port serving 12 tools; in-container homedir=/root and ledger=/root/.config/beeperbox/sent-ledger.json grounded. Also corrected an over-claim: the send->restart->poll round-trip was validated over HTTP; the npx bin and stdio were each exercised with real list_accounts + preflight. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…FLIGHT=0) Branch self-review caught it: the entrypoint launches the MCP server BEFORE its API-readiness loop, and preflight is one-shot, so in the container it ran while Beeper's API was still coming up (or pre-login) and logged a misleading "preflight FAIL: unreachable" on essentially every boot. Preflight is the LITE-mode boot check (lite mode has no healthcheck); the container already has a Docker HEALTHCHECK + supervisor + the entrypoint's own API-wait, so it's redundant there. Bake ENV BEEPERBOX_PREFLIGHT=0 into the image; lite mode (npx, without this ENV) still runs it. Verified: image ENV BEEPERBOX_PREFLIGHT=0; container with a token + dead API logs NO preflight line and still binds 0.0.0.0; host `node mcp/server.js` (no ENV) still logs "preflight OK ... 4 account(s)". Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Cuts 0.8.0 (MINOR): first-class lite mode (`npx beeperbox` against a local Beeper Desktop), startup preflight, the sent-ledger per-user XDG fix, and the lite-mode loopback-bind security fix (MCP_BIND_ADDR). serverInfo.version reads from package.json, so bumping the manifest to 0.8.0 is the single stamp the npm package + the container + the MCP initialize response all follow. - mcp/package.json 0.7.0 -> 0.8.0 - CHANGELOG [Unreleased] -> [0.8.0] — 2026-06-16 [MINOR] - PRD status + release-history row + lite-mode roadmap item marked shipped - beeperbox.context.md version header + compatibility table - publish.yml: setup-node v6 -> v4 (match mcp-test.yml; avoid an unresolved-tag failure on first dispatch) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Makes lite mode a first-class, supported path: run beeperbox's MCP verb server standalone against a Beeper Desktop the user already runs locally — no Docker, no Electron, no Xvfb. Same single
mcp/server.jsthe container runs, so the tool surface andserverInfo.versionare identical by construction. Driven by multis (laptop users with Beeper already open).Changes
beeperbox(mcp/package.json, zero-dep) with abeeperboxbin →npx beeperbox(HTTP) /npx beeperbox --stdio. Already published (beeperbox@0.7.0, name reserved). Tarball isserver.js+ npm README only.package.json; a unit test pinsversion == package.jsonand the exact 12-verb set so lite/container can't drift./v1/accountsprobe logs a clearOK/FAILverdict on boot (replaces the DockerHEALTHCHECKlite mode lacks). Non-fatal;BEEPERBOX_PREFLIGHT=0opts out./root/.config/beeperbox-sent-ledger.json(fails on a non-root host, silently degrading the echo-guard). Now per-user XDG (~/.config/beeperbox/sent-ledger.json), parent dir auto-created. One code path (os.homedir()is/rootin-container)./securityon this branch). The MCP server bound0.0.0.0unconditionally — safe in the container (loopback publish is the boundary) but in lite mode it exposed the full tool surface to the LAN, reachable unauthenticated via a spoofedHostheader (demonstrated live against the host LAN IP). Now binds127.0.0.1by default (MCP_BIND_ADDR); the Dockerfile bakesMCP_BIND_ADDR=0.0.0.0so the container's published port + the CI guard-check still work. Regression test pins the loopback default. Docs corrected (the "same posture as the container" claim was false)..github/workflows/publish.yml(hamr0 OIDC trusted-publishing template, adapted for themcp/subdir, zero-dep). Manualworkflow_dispatch, idempotent.[Unreleased].Validation (live, against a real Beeper account on the host)
source:"api"+client_tagfrom the reloaded per-user ledger — over HTTP. The installednpxbin and stdio transports were each separately exercised against the live account (reallist_accounts→ 4 accounts + preflightOK).127.0.0.1(LAN attack → connection refused; loopback client → 200);MCP_BIND_ADDR=0.0.0.0still binds all interfaces.OK,FAIL401-bad-token,FAILunreachable, no-token skip, opt-out.homedir=/root, ledger/root/.config/beeperbox/sent-ledger.json, binds0.0.0.0, published port serves 12 tools.Not in this PR / follow-ups
0.7.0; the OIDCpublish.ymlpublishes the next version (a release bump) — it's idempotent and skips an already-published version. Requires the npm trusted-publisher configured for the package first.0.7.0; a release PR stamps the next minor per the established flow.🤖 Generated with Claude Code