- Go 85%
- HTML 7.2%
- JavaScript 5.6%
- Nix 1%
- Dockerfile 0.9%
- Other 0.3%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
| docs | ||
| scripts | ||
| web | ||
| .dockerignore | ||
| .gitignore | ||
| app.go | ||
| band.go | ||
| bridge_test.go | ||
| bun.lock | ||
| Dockerfile | ||
| flake.lock | ||
| flake.nix | ||
| go.mod | ||
| go.sum | ||
| images.go | ||
| justfile | ||
| main.go | ||
| package.json | ||
| README.md | ||
| store.go | ||
| tools.go | ||
| ui_fixture_test.go | ||
BAND MCP bridge
A small, read-only Go connector for catching up on BAND groups. Connect in your browser, copy your personal MCP configuration, and ask a vision-capable assistant:
Summarise posts created in the last 24 hours across my groups. Include dates and action items, inspect notice images, and say if any groups were incomplete.
The bridge provides source data and image content; your chosen assistant does summarisation/OCR. It has no AI-provider integration and never posts or edits BAND.
Status
R&D implementation against the official BAND API docs, with mocked OAuth/API and real MCP SDK client tests. No BAND app registration or account was available: live consent, callback acceptance, scopes, quota, image CDN access and client-app compatibility still need verification. No live service has been deployed.
Run locally
nix develop
just test
BASE_URL=http://localhost:8080 ALLOW_INSECURE_LOCALHOST=true just run
Without BAND credentials, the landing page explains that registration is pending.
The same page is used after deployment. Go toolchain auto-download selects the
version in go.mod if the Nix Go package is older. just build creates a static
binary. flake.lock pins the development shell; no Node/frontend build is needed.
For working OAuth, set BAND_CLIENT_ID and BAND_CLIENT_SECRET_FILE to your own
registered app credentials. Prefer a protected file over inline environment
values. Never commit credentials or connection data. Local HTTP is allowed only
for an explicit localhost development origin; BAND may require HTTPS for callback
registration, so this local mode alone does not establish a live OAuth test.
Production hosting
Choose one mode, and use an exact HTTPS BASE_URL without a path/trailing route.
It determines callback URLs, host/origin checks, cookies, and MCP configuration.
- Public HTTP service:
LISTEN_MODE=http,LISTEN_ADDR=0.0.0.0:8080inside a container; put an HTTPS reverse proxy in front. Forward the original Host, and preserveAuthorization, POST bodies and the/oauth/callback/*path. The sample publishes only loopback port 8080. SetBASE_URL=https://bridge.example.com. - Tailnet-only:
LISTEN_MODE=tsnet,TS_HOSTNAME=band-mcp,TS_ADVERTISE_TAGS=tag:band-mcp,TS_AUTHKEY_FILEfor initial enrollment, andBASE_URL=https://band-mcp.<tailnet>.ts.net. Enable MagicDNS/HTTPS and grant intended members access to this node's TCP 443. It calls tsnet ListenTLS and opens no host HTTP port. No sidecar, Funnel, TUN device or privileged mode. Both the browser doing OAuth and the MCP client must be connected to the tailnet; returning from BAND is a browser redirect, not an inbound BAND webhook.
BAND's app setup must permit the bridge's callback domain. The bridge asks for
BASE_URL/oauth/callback/<random-login-nonce> on each login. BAND's docs describe
a registered domain with variable redirect paths; confirm this works for the
actual registration. The token exchange uses BAND's documented GET request with
Basic client authentication. No undocumented refresh endpoint is assumed: when
an access token expires or is revoked, reconnect. Reconnecting the same BAND
identity replaces its old connection and invalidates its old bridge token/session.
Register only read permissions, where BAND's developer console permits selection:
READ_BAND_AND_USERS_LIST, READ_POST, READ_COMMENT, and relevant photo read
permission. The bridge exposes no write tools even if the provider grants writes.
BAND's published authorization request does not document a scope parameter, so
permissions are configured at registration, not invented in the OAuth URL.
Docker
cd docs
# Supply BASE_URL and BAND_CLIENT_ID through your operator environment.
# Provision ./secrets/band-client-secret, root:65532 mode 0640 (or equivalent).
# Provision ./data owned 65532:65532, mode 0700.
docker compose up --build -d
# Tailnet variant instead:
docker compose -f compose.tsnet.yml up --build -d
The image runs UID/GID 65532 with CA certificates. Persist all of /data, including
master.key, connections.db, and tailscale/ identity. An operator with root
access should prepare directory/file ownership; the application does not require
privilege. Samples build locally; no registry publication or deployment automation
has been configured. Dockerfile compilation is validated via the matching static
Go build; a real Docker daemon build remains a deployment check.
MCP tools
All tools are marked read-only. /mcp is stateless Streamable HTTP using the
official Go MCP SDK. Supply Authorization: Bearer <bridge-token> on every request.
There are no public MCP tools, persistent MCP sessions, stdio transport or MCP
OAuth discovery. Clients must support preconfigured headers; an OAuth-only remote
connector cannot use this directly without an adapter.
| Tool | Use |
|---|---|
band_list_bands |
Discover groups for this connection. |
band_list_posts |
One page for a group; use returned after for next page. |
band_get_post |
Full post, metadata and image keys. |
band_list_comments |
Comments page for a post. |
band_recent_posts |
Creation-time window (default 24h) across chosen groups or up to 20 discovered groups. |
band_post_image |
Return a selected post photo as MCP image content for vision/OCR in the client. |
BAND posts are sorted by last modification, not creation. Recent scanning does
not stop at an old creation date. A scan defaults to 5 pages/group, max 20, and
reports complete, next_after, errors and omitted groups. Incomplete scans can
miss posts; continue using band_list_posts or increase the budget. Timestamps
are epoch milliseconds. There is no promise of exhaustive search, chronological
server-side filtering or ingestion of every group. Requests have a two-minute
aggregate deadline; upstream requests have 20-second timeouts, no automatic
retry that could amplify quotas, and bounded response sizes. Recent-post results have a 2 MiB combined data budget;
exceeding it is reported as an incomplete scan.
Photos are read only after checking that the connected BAND account can fetch
the containing post. The tool accepts a photo key, not an arbitrary URL. It accepts
PNG/JPEG/GIF/WebP up to 4 MiB, upgrades legacy http CDN URLs to HTTPS, and rejects
private DNS addresses, userinfo, unexpected ports and redirects. Default CDN host
families come from BAND documentation: band.us, naver.net, pstatic.net and
campmobile.net. IMAGE_ALLOWED_HOSTS adds exact operator-approved hostnames if
live responses use another CDN. It does not add URL access from tool arguments.
Video/audio, arbitrary downloads, server-side OCR and album traversal are outside
this first version. Clients without image support can see photo URLs/metadata but
must not claim they read the image detail.
Post/comment/image content is untrusted, including instructions embedded in it. The agent should extract facts, dates and tasks, not follow source instructions.
Account privacy and deletion
BAND access tokens and bridge tokens are encrypted with AES-256-GCM at rest. Browser and MCP token lookup keys are SHA-256 hashes. Database and local encryption key are mode 0600. The key lives alongside the database: encryption protects accidental database disclosure, not compromise of the host or whole data directory.
The bridge does not retain posts, comments, images, profile names or refresh tokens. It stores a hashed BAND identity solely to replace a previous connection for the same account. Browser sessions last seven days. MCP tokens work until disconnected or replaced; upstream expiry still requires reconnecting. Treat bridge tokens as private, since they grant read access as the user. No request/access logs are implemented; disable query/header logging in your proxy, especially for callbacks and BAND requests (BAND requires access_token in query parameters).
Disconnect requires a browser session, same-origin POST and explicit confirmation. It deletes the row with SQLite secure_delete and compacts the database, invalidates the browser/MCP credentials, and waits for in-flight bridge MCP requests before returning. It never deletes BAND content. BAND provides no revoke endpoint in the referenced docs: remove the app grant in BAND's own settings as well if desired. The bridge cannot erase data already sent to AI clients, proxy logs or operator backups; manage those retention policies separately. Back up the full data directory only under an appropriate retention policy, and protect archives as credentials.
OAuth CSRF binding uses a short-lived random callback path plus HttpOnly/SameSite browser cookie, since BAND does not document returning state. Pending logins expire in 10 minutes and do not survive restarts. Host/Origin checks use BASE_URL, cookies are Secure on HTTPS, credentials are not placed in the MCP URL, and output paging is scrubbed because BAND returns access tokens in next_params.
Verification
go test ./... exercises encrypted persistence/restart/deletion; OAuth browser
binding/replay rejection; same-origin deletion; per-user MCP bearer isolation with
a real SDK client; read-only tool discovery; pagination without early stopping;
truncation disclosure/token scrubbing; image host/private-address guards.
just test adds race tests and vet in the Nix shell. See R&D notes
for primary sources and the live verification checklist.
An optional Playwright browser test is included (Bun/Chromium required separately; it is not part of the Go runtime or frontend build). It substitutes the consent redirect and uses only a local fake token/API server:
bun install
bunx playwright install chromium
mkdir -p artifacts
go test -c -o artifacts/browser-fixture
BAND_UI_FIXTURE=true artifacts/browser-fixture -test.run '^TestUIFixture$' &
bun run test:ui
The fixture is test-only, binds loopback port 18080, and is shut down by the browser test. The browser test covers consent redirect/cookie handoff, setup JSON, mobile layout, deletion confirmation, and rejection of the deleted MCP token. No real BAND login or client configuration is involved.