Web-based MLS coordinator for ad-hoc Cordn groups. The app runs a ContextVM/Nostr coordinator in a browser tab, publishes its coordinator pubkey, and lets Cordn clients use that browser tab as the group coordination server.
Cordn Ad-hoc is for quickly bringing up a temporary MLS coordinator for Cordn without provisioning a backend. It is meant for short-lived groups, demos, local testing, and operator-controlled coordination sessions where a browser tab can stay open for the life of the group.
- Runs the Cordn coordinator protocol from a browser.
- Receives ContextVM MCP requests over Nostr relays.
- Stores MLS key packages, welcomes, join requests, and group messages locally.
- Supports streaming group-message subscriptions.
- Persists relay/runtime configuration in browser storage.
- Optionally encrypts and persists the coordinator identity behind a passphrase.
- Prevents multiple coordinators with the same pubkey from running at once.
- Exposes an operator debug log for raw Nostr events, decoded requests, responses, relay publish state, and instance heartbeats.
Requirements:
- Node.js 22 or newer
- pnpm 10.17.1
Install dependencies:
pnpm installStart the dev server:
pnpm devThe app listens on 127.0.0.1. If Vite reports that the default port is busy, use the alternate URL it prints.
- Open the app.
- Copy the coordinator pubkey.
- Configure the relay list that Cordn clients will use.
- Start the coordinator.
- Use the coordinator pubkey from a Cordn client.
- Watch the debug log if the client does not connect or does not mark messages as sent.
The coordinator locks runtime configuration while running. Stop it before changing relays, announcement mode, or user limits.
- The browser tab is the coordinator. Closing it, refreshing it, or losing network access interrupts coordination.
- Relay lists must match what clients can reach. A working coordinator still looks offline to clients on the wrong relays.
- Persisted browser config overrides new defaults. Reset defaults if a stale relay list keeps coming back.
- Encrypted identity persistence is optional. Without it, a fresh browser profile or cleared storage creates a new coordinator pubkey.
- Only run one coordinator per pubkey. A second instance should fail with
cordn already running; if it does not, stop both and restart one. - This is not a hardened always-on service. Use a proper hosted coordinator when availability matters.
- The debug log can show encrypted event metadata and request shapes. Avoid sharing screenshots when coordination details are sensitive.
The debug log is the primary operator surface.
Useful entries:
raw nostr event received: a relay delivered an event to the coordinator.decoded client request: the event decrypted and decoded as an MCP request.disabled_unused_stream: a non-streaming request carried a progress token, so the coordinator removed the unused stream guard before responding.outbound coordinator response: the MCP response body was produced.publishing nostr response event: the response is being published to relays.nostr response event accepted: at least one relay accepted the response.
For normal message posts, the decoded request should show tool=msg_post, and the response should include cursor, gid, and at.
The deploy workflow publishes the built dist output to Nostr and Blossom after CI passes on main or master.
Configure repository secrets with:
./scripts/setup-secrets.shThe script requires GitHub CLI authentication and prompts for:
NBUNK_SECRET: thenbunksec1...credential fromnsyte ciNSYTE_RELAYS: one or more Nostr relay URLsBLOSSOM_SERVER_URLS: one or more Blossom server URLsNSITE_NAME: optional named nsite
List-style secret prompts accept comma-separated input. The script stores list secrets as newline-separated values for the deploy action.
Run the standard local checks:
pnpm lint
pnpm test
pnpm build
pnpm test:e2eRun everything in sequence:
pnpm ciThe build may print upstream Rolldown warnings from dependency pure annotations. Those warnings are currently non-fatal.
This is a browser-hosted coordinator. Closing the browser tab stops the coordinator. Enable encrypted persistence if the same coordinator identity should survive reloads.