Self-hosting

HyphaHypha is one Cloudflare Worker — an MCP server, a thin web ledger, and a Monday-morning email — backed by D1 (SQLite) and a cron trigger. There is nothing else to run: no servers to babysit, no third-party email provider, no message queue. This page takes you from a fresh checkout to a deployed instance with a first member signed in.

If you just want to understand the network, start with Concepts. If you want to wire up your agent, see the Quickstart. This page is for the person running the instance.

Prerequisites

The first wrangler command opens a browser to log in to Cloudflare. Run npx wrangler login ahead of time if you prefer.

Bindings and configuration

Everything the Worker needs is declared in wrangler.toml, with two values kept as secrets. Here's the whole surface:

WhereNameWhat it is
[[d1_databases]]DBThe D1 (SQLite) database. Fill in database_id after creating it.
[[kv_namespaces]]OAUTH_KVKV namespace holding OAuth grants and tokens for connected MCP clients. Fill in id after creating it.
[[send_email]]EMAILNative Cloudflare Email Sending binding. No API key.
[vars]BASE_URLYour deployed origin, no trailing slash. Used to build magic-link and action URLs in emails.
[vars]FROM_EMAILThe sender address, on your onboarded domain.
wrangler secret putSECRETSigns session cookies and action / magic-link tokens.
wrangler secret putADMIN_TOKENBearer for POST /admin/invite, the seed endpoint.

Generate the two secrets with strong random values:

openssl rand -hex 32

Local development

Copy the example dev vars and fill in the two secrets (the same openssl rand -hex 32 values are fine for local use):

cp .dev.vars.example .dev.vars   # set SECRET and ADMIN_TOKEN

BASE_URL and FROM_EMAIL are not secrets — they come from wrangler.toml [vars]. For local runs keep BASE_URL = "http://localhost:8787".

Apply the migrations to your local D1, then start the dev server:

npm run migrate:local   # wrangler d1 migrations apply time-debt --local
npm run dev             # wrangler dev on http://localhost:8787

wrangler dev simulates email locally — it logs each message to the dev console instead of sending it, so magic links and action emails show up right there in your terminal. Run the test suite any time with npm test.

Deploy

Do these in order, the first time:

  1. Create the D1 database and paste the printed database_id into wrangler.toml under [[d1_databases]]:
   npx wrangler d1 create time-debt
  1. Create the OAuth KV namespace and paste the printed id into wrangler.toml under [[kv_namespaces]]:
   npx wrangler kv namespace create OAUTH_KV
  1. Set the two vars in wrangler.toml [vars]:
  1. Set the two secrets:
   npx wrangler secret put SECRET
   npx wrangler secret put ADMIN_TOKEN
  1. Apply migrations to the remote DB, then deploy:
   npm run migrate:remote   # wrangler d1 migrations apply time-debt --remote
   npm run deploy           # wrangler deploy

Make sure the URL Wrangler prints matches BASE_URL. If it doesn't, update BASE_URL and run npm run deploy again — the emails are only as correct as that value.

Seed the first member

There's a chicken-and-egg here: the in-app invite flow needs an existing member, so the very first one is created out-of-band with the admin token. POST /admin/invite creates a member and emails them a welcome magic link (valid for 7 days):

curl -X POST "$BASE_URL/admin/invite" \
  -H "X-Admin-Token: $ADMIN_TOKEN" \
  -H 'content-type: application/json' \
  -d '{"name":"You","email":"you@domain.com","help_offered":"code review, intros"}'

The response carries the new member's id. Open the emailed link to sign into the web ledger.

There's nothing else to hand out: agents connect themselves. An interactive MCP client (claude.ai, Claude Desktop, Claude Code) gets your /mcp URL and walks the member through OAuth — sign in, approve scopes, done. For headless agents and curl, members mint personal access tokens in the Settings tab of their profile. See the Quickstart and MCP reference.

Make yourself an admin

A freshly seeded member is not an admin (there's deliberately no API to mint the first one). Promote your founder account directly in D1 — use the id from the seed response:

npx wrangler d1 execute time-debt --remote \
  --command "UPDATE members SET is_admin = 1 WHERE id = '<your-member-id>'"

After that, every other admin can be promoted from the /admin panel — no more raw SQL.

The admin surface

Signed-in admins get an extra page at /admin for running the circle:

Inviting new people, by contrast, is not an admin action — any member can invite from /invite (it costs the inviter 0.5 hours of debt).

Migrations

Schema lives in migrations/ as ordered .sql files and is applied with Wrangler's built-in migration runner:

npx wrangler d1 migrations apply time-debt --local    # local D1
npx wrangler d1 migrations apply time-debt --remote    # deployed D1

npm run migrate:local and npm run migrate:remote are thin aliases for exactly these. When you add a new migration file, re-run the matching command — locally before npm run dev, remotely before npm run deploy.

What's next