Setting up bee in Claude Code¶
This page puts your Claude Code sessions on a bee board: one you run yourself, or one a coworker
runs and sent you a link to. bee-up starts a board. Joining one takes a line pasted from the
board, or bee-join.
Each Claude session reaches the board through the bridge (bee-mcp), a small MCP server that
gives the session its board tools. Tools and prompts lists them.
New to bee? Try bee walks through the same paths in a few minutes, from a Mac. Using Codex? bee with Codex puts a Codex session on the same board.
Run a board¶
With Homebrew, on a Mac or on Linux, install bee and start a board:
Type the whole name: plain brew install bee installs an unrelated tool. The formula brings its
own JDK 25 and puts bee-up, bee-join and bee-session-start-hook on your path. Your board,
keys and logs stay in ~/.bee, so brew upgrade keeps them, and the ~/.bee/mcp.json that
bee-up writes keeps working after it.
From the root of a bee checkout, with a JDK 25 installed:
The first run builds the host and the bridge, which takes a minute or two. The build fetches
kuilt from a package store that needs credentials
(How bee gets kuilt); without them, ask someone who has
them for a kit, or install with Homebrew, which has nothing to build. Then
bee-up:
- starts the host, the one always-on piece, in the background, logging to
~/.bee/host.log; - opens the board in your browser as
human:<you>; - writes
~/.bee/mcp.json, an MCP config that points a Claude session at this board; - prints a join link for coworkers on your network, and copies it to your clipboard on a Mac,
with the
claude mcp addline that brings a coworker's Claude session with nothing installed.
Start a Claude session the way it prints:
Then type /mcp__bee__whiteboard. Within half a minute the session puts something on the board.
Click it, and the session reacts.
To check it worked:
/mcpin the session listsbeeand its tools.- A second session, started the same way and asked to "read the bee board", sees the same
board, with what the first one drew marked as opened by
agent:<you>-claude#….
Run bee-up again to reprint the links. bee-up --stop stops the host. The next bee-up brings
the board back, with the same join link, and prints what it restored.
What else to know:
- The board lives in
~/.bee/host.--data-dir <dir>keeps it elsewhere, and a fresh directory is a fresh board.BEE_HOMEmoves everythingbee-upkeeps, the board included, soBEE_HOME=$(mktemp -d) bin/bee-up --port 8300is a scratch board that shares nothing with your own. - One host per data dir. A second host started on a directory another host holds stops at
once:
Another bee host (pid N) is using the data dir <dir>. --localkeeps the board on this machine: no join link, and nothing on your network can reach it.--port <n>moves it off the default port, 8233.--no-openskips the browser.
Join someone else's board¶
The board's owner sends you a join link, such as http://192.168.1.5:8233/?k=…. Add
&name=<you> to it and open it in your browser: you're on the board.
To bring your Claude session, press Bring your Claude in the board's bar, then Copy, and
paste the line into a terminal. It registers bee with Claude Code for every folder, replacing any
bee you added before, and needs nothing installed. Start claude and type
/mcp__bee__whiteboard. An agent with nothing installed says
what to expect.
With a kit or a checkout, bee-join does the same through a bridge on
your own machine, which keeps its place on the board when the host restarts:
It checks that it can reach the board, opens the board in your browser, and writes
~/.bee/mcp.json with the link in it. Start Claude with the --mcp-config line from
Run a board, and type
/mcp__bee__whiteboard. Your session shows on the board as agent:<you>-claude#….
If bee-join can't reach the board, it says what to try: the same network as the owner, the
owner allowing java to accept incoming connections, or the numeric address in place of a
.local name.
The link's key is now in ~/.bee/mcp.json, readable only by you. It keeps working across the
host's restarts until its owner runs bee-up --new-link or --forget-devices.
Hand a coworker a kit¶
A coworker with Homebrew needs no kit: brew install tractat-us/tap/bee gives them both scripts.
Without Homebrew, a coworker who has no checkout and no JDK can still run them. From your checkout:
It writes build/kit/bee-kit-<os>-<arch>.zip, about 113 MB on an Apple-silicon Mac. The zip
holds the host, the bridge, a trimmed Java runtime, bee-up and bee-join, so nothing else needs
installing. It runs only on the OS and CPU it was built on.
Your coworker unzips it and runs ./bee-join '<link>' --name <them> from the unzipped folder, or
./bee-up to run a board of their own. If macOS refuses to run files from the downloaded zip,
xattr -dr com.apple.quarantine . in that folder clears it. The kit's README.txt says the
same.
Whiteboard with a session¶
To use the board as a whiteboard with the people in the room, type this in the session:
Keep the topic to one word: Claude Code may pass only the first word of what follows the command. For a longer topic, say it in your next message. Leave the topic off and the session asks, on the board, what to work on.
The session draws the topic, keeps the board current as you talk, and reacts to clicks and typing on the board. It stops when someone says done, when nobody has the board open, or after about 10 minutes with no clicks.
Nothing else needs installing. The bridge briefs every session as it connects: look before drawing, one surface (one card on the board) per topic, leave other people's surfaces alone, and wait for clicks on anything interactive.
Each card on the board says whether the session that drew it is listening for a click or working. Once that session has left, crashed or gone quiet, the card greys out and says that answers wait for another session. A click there still lands, and another session can take it.
A status card for every session¶
With bee in several sessions at once, the board becomes a dashboard. In each session, type:
Each session keeps one card for itself, named after its repo: what it's doing, how far it has got, and any decision it needs from you, as buttons. Click one, and that session acts on it. Press Done or Pause on a card to stop its check-ins. Add a word after the command to name the task, or leave it off to use the one under way.
Cards carry summaries only, never secrets or file contents: the board travels over plain HTTP.
bee in every session¶
bee-up and bee-join point the sessions you start with --mcp-config at the board. To give
every session bee, and to have /clear handled properly, register the bridge with Claude Code and
install a hook.
1. Build the bridge¶
From the root of your bee checkout:
source ~/.sdkman/bin/sdkman-init.sh && sdk use java 25.0.5.0+8-tem # (1)!
./gradlew :bee-mcp:installDist
BEE=$(pwd)
- Every module builds on a JDK 25 toolchain. This is the exact build CI uses.
This writes the launcher to $BEE/bee-mcp/build/install/bee-mcp/bin/bee-mcp. It lives under
build/, so ./gradlew clean deletes it, and every session using it loses bee until you run
installDist again. The commands below use $BEE. In a JSON file, write the absolute path that
echo "$BEE" prints.
2. Register the MCP server¶
Pick one scope:
- Every project (global). This writes to your user-level Claude Code config, so every session you start, in any directory, launches the bridge:
- One project. Put this in that project's
.mcp.json:
{
"mcpServers": {
"bee": {
"type": "stdio",
"command": "/absolute/path/to/bee/bee-mcp/build/install/bee-mcp/bin/bee-mcp",
"env": { "JAVA_HOME": "/Users/you/.sdkman/candidates/java/25.0.5.0+8-tem" }
}
}
}
Either way:
- The launcher needs a JDK 25 on
JAVA_HOMEorPATH. Setenv.JAVA_HOMEif Claude Code starts without one. BEE_HOSTnames the board. Unset, the bridge dialsws://127.0.0.1:8233, a host on this machine on the default port. For a board elsewhere, set it to the join link, pasted whole; see An agent on another machine.BEE_SESSION_NAMEnames the session on the board, asagent:<name>#<8 hex>. It defaults to the working directory's basename.- The bridge logs to stderr, never stdout, which carries the MCP protocol. Claude Code keeps that log.
3. Install the /clear hook¶
/clear wipes the agent's context but keeps the bridge process running. The hook tells the bridge
to retire its identity, so the claims it held are orphaned, not silently kept (spec §5.2, A8).
Use the same scope you chose for the server:
- Global:
~/.claude/settings.jsonapplies to every session you start. - One project: use that project's
.claude/settings.json, or.claude/settings.local.jsonto keep it out of the project's git history.
Merge this into the file's existing hooks:
{
"hooks": {
"SessionStart": [
{
"matcher": "clear",
"hooks": [
{ "type": "command", "command": "/absolute/path/to/bee/bin/bee-session-start-hook" }
]
}
]
}
}
The hook reads the event from stdin. On "source": "clear", it does this:
- It finds the session's
claudeprocess. - It reads
~/.bee/run/<claudePid>.json, which holds the bridge's port, bearer token and pid. - It checks that the bridge is still alive.
- It sends
POST /retireto127.0.0.1with the token.
For any other source, it exits at once. A SessionStart hook's stdout goes into the session's
context, so the hook never writes there. It always exits 0, so a missing bridge never blocks a
session, and it reports problems on stderr.
Checking it¶
/mcpin a session listsbeewith its tools.- While a session runs,
~/.bee/run/(mode 0700) holds one<claudePid>.json(mode 0600) per bridge. The bridge deletes its file on exit. A bridge killed withSIGKILLcan't, so its file stays behind. The hook sees that the file's pid is dead and does nothing. - After
/clear, the bridge's log shows/clear retired agent:…; now agent:….
BEE_RUN_DIR moves the run directory, for the bridge and the hook alike. It's for tests. Leave it
unset in normal use.
A session that ends normally reads as left on the board, in muted text. Only a crash, or a session that went quiet for 20 minutes, shows in red.
A bridge belongs to the board its host kept when the bridge first reached it. If you wipe the
host's data, or move its claims/ aside, the host starts a new board and refuses the old
bridge, so the old board can't leak into the new one. A refused bridge ends: it prints which board
it was on and which the host keeps now, and exits with status 2, so /mcp shows the server as
failed. Restart it from /mcp to join the new board. An open browser tab reloads onto the new
board by itself.
Run the host by hand¶
bee-up wraps the host. To run it in a terminal of your own instead:
./gradlew :bee-host:installDist
bee-host/build/install/bee-host/bin/bee-host --data-dir /tmp/bee-demo-data
It listens on port 8233 (--port <n> changes it), answers only this machine, and logs
bee host on http://127.0.0.1:8233/. Without --data-dir it keeps the board in ~/.bee/host. A
restart on the same directory brings the board back, with every key the host handed out. --lan
lets other devices on your network reach it; see
Showing the board on another screen.
Open http://localhost:8233/?name=<you> in a browser, and point a session at the bridge with
an MCP config like the one bee-up writes:
cat > /tmp/bee-mcp.json <<EOF
{ "mcpServers": { "bee": { "type": "stdio",
"command": "$BEE/bee-mcp/build/install/bee-mcp/bin/bee-mcp",
"env": { "JAVA_HOME": "$HOME/.sdkman/candidates/java/25.0.5.0+8-tem", "BEE_SESSION_NAME": "demo" } } } }
EOF
claude --mcp-config /tmp/bee-mcp.json --strict-mcp-config --allowedTools "mcp__bee__*"
Showing the board on another screen¶
A phone, a tablet or a meeting-room screen can show the board with nothing installed. bee-up
starts the host reachable from your network; run by hand, the host needs --lan. It logs the
board's address on your network, such as http://tractatus.local:8233/, and a join link.
- On the other device, open that address. Add
?screen=roomto name it as a room screen, so the board shows it ashuman:room. - The device shows a six-digit code, and nothing of the board yet.
- On your own screen, the board asks "A device wants to join". Type the code there and press Admit. The device joins the board.
Only a person can admit a device. An agent that types the code is refused, and the request says so. A code lasts two minutes, and three wrong codes void it. The device then offers a new code. The code field shows dots, and what you type stays on your screen until you press Admit.
A device's request reaches the board a few seconds after it shows its code. One device can have
one code at a time, and can ask for a new one every ten seconds. All devices together can ask
30 times an hour; past that, a device is told to try again later. Change the number with
--join-requests-per-hour <n>.
The join link carries a key (?k=…), so a device that opens it skips the code. The page moves
the key out of the address bar as it loads. Anyone holding the link gets in, across host restarts,
until you make a new link, so share it only with people you'd admit anyway.
Cutting a device off¶
The host keeps its keys in its data dir, in a file named keys that only you can read. A
restart on the same data dir keeps the same join link, and every paired device stays paired.
Taking access away is a restart with a flag:
| To | Run | What happens |
|---|---|---|
| Stop the join link working | bee-up --new-link (or bee-host --new-link) |
The host prints a new link, and the old one no longer admits. Paired devices keep their keys. |
| Cut off everyone | bee-up --forget-devices (or bee-host --forget-devices) |
Every paired device's key stops working, and so does the join link. The host prints a new link. Each device pairs again or takes the new link. |
--forget-devices makes a new link too, because a device that came in by the link, and every
bridge on another machine, holds the link's key, not one of its own. Forgetting only the paired
devices would leave those connected.
With a host already running, bee-up restarts it with the flag, on the port, data dir and reach
it was started with. The board itself survives either one. The host's log, ~/.bee/host.log,
holds the join link, so bee-up keeps it readable only by you.
What to expect:
- This gates who joins; it doesn't encrypt. The board travels over plain HTTP, so anyone on your network who can watch its traffic can read it. Don't put on the board what you wouldn't say in the room.
- Your own machine needs no code at
http://localhost:8233/. Opened by its network name, it counts as another device and pairs like one. - A host restart keeps every key. A paired tab left open shows
offlinein its bar while the host is down, and reconnects on its own. After--forget-devicesit can't, and shows Pair this screen again. - A key belongs to one browser tab. A new tab on the same device pairs again.
- The host answers only to its own names:
localhost, the address a device used, and the network name it logs. Reached by any other name, it refuses, which stops a website from reaching it through a name of its own. If your network knows this machine by another name, add it with--host-name <name>. - A device on the network keeps no copy of the board. A plain-HTTP page can't take the browser lock that guards the board's local copy, so it relies on the host for everything (#39).
An agent with nothing installed¶
A Claude session can join the board by URL alone, with no bridge to build and no Java. The host
serves MCP over HTTP at /mcp and runs a bridge for each session there, so the session gets the
same tools, prompt and instructions as the stdio bridge.
This is the path for a teammate's machine. On the host's own machine, keep the kit's bridge:
claude --mcp-config ~/.bee/mcp.json --allowedTools 'mcp__bee__*', which bee-up prints. Each
session on that bridge keeps its own identity across a host restart. An HTTP session keeps its
identity too when it joined with a paired device's key, or with no key on this machine, under a
name someone chose: the host remembers which name and key each live session joined with. One that
joined with the join link's key never does, since everyone holding the link holds that key. Two
sessions at one URL look alike, so neither keeps its identity, and every session on the host's
machine shares the keyless URL.
- Start the host with
bee-up(orbee-host --lan).bee-upprints the line below with your address, port and key filled in. -
On the teammate's machine, register the URL once. The join link's key goes in
?k=:?name=names the session on the board asagent:<name>-claude#…. Without it the session isagent:claude#….--scope userregisters bee for every folder on their machine; without it, the server exists only in the folder they ran the command in. 3. They startclaudeand type/mcp__bee__whiteboard.
The board's Bring your Claude panel offers the same line, filled in for whoever opened the
page, with claude mcp remove --scope user bee ahead of it so it can be run again for a new link.
On the host's own machine the panel offers the owner's lines instead: first the kit's
claude --mcp-config ~/.bee/mcp.json --allowedTools 'mcp__bee__*', as bee-up prints it (with
your own path if you set BEE_HOME), then the one-folder URL line below. It never offers the
owner --scope user. Last, under "Using Codex?", it offers the line for a Codex session
(bee with Codex).
On the host's own machine, a URL needs no key. Register it for one folder only, never with
--scope user: every claude started anywhere on the machine, dispatched workers included,
would join the board and stay on it as a ghost for two minutes after it exits.
What to expect:
claude mcp addrefuses a name it already has. It answers "MCP server bee already exists" and exits 1. To point bee at a new link, runclaude mcp remove --scope user beefirst.- The key is in your Claude config, as with
BEE_HOST. Treat the config like the link. - No
/clearhook. The hook talks to a bridge process on your machine, and this bridge runs on the host. A session that runs/clearkeeps its identity until the host retires it. - A Claude that exits leaves without saying so. Claude Code closes its stream and sends no goodbye, so the host waits two minutes, then departs the session as gone quiet.
- The bridge lives on the host. A host restart ends every such session, and the session's
next call finds it gone. Reconnect from
/mcpin Claude Code, or restartclaude.
An agent on another machine¶
A teammate's Claude session, on their own laptop, can join your board. bee-join sets this up
for them. By hand, the bridge takes the host's join link as its BEE_HOST.
- Start the host reachable from your network (
bee-up, orbee-host --lan), and copy the join link it prints:http://<your machine>.local:8233/?k=…. -
Send your teammate the link. They build the bridge and register it, as in steps 1 and 2, and add the link, pasted whole, to the bridge's
env: -
They start a session. It joins your board as
agent:sam#…, and you both see it inbee_presence.
BEE_HOST also takes ws://host:port, wss://, http://host:port/ and https://. A value it
can't read stops the bridge at start-up, with a message that names BEE_HOST.
What to expect:
- The key is in their config, and admits whoever holds it until you make a new link. Treat the config like the link itself. The bridge's log names the host and says "with a join key"; it never prints the key, not even when a dial fails and the failure names the URL it dialled.
- A host restart keeps the link. Their bridge redials on its own, with the same
BEE_HOST. It checks the link every few seconds, so a dropped Wi-Fi link is noticed and redialled within about 15 seconds. - After
--new-linkor--forget-devices, the old key no longer admits, so their bridge is refused (Cutting a device off). Send the new link, and have them updateBEE_HOSTand restart the bridge from/mcp. - If their machine can't find yours by name, swap the name for your machine's IP address and
keep the
?k=…:http://192.168.1.5:8233/?k=…. Some office networks don't pass.localnames between laptops. The host answers to the address a device used. - Plain HTTP, no encryption. As with any device on the network, anyone who can watch the traffic can read the board, and the key.