Skip to content

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:

brew install tractat-us/tap/bee
bee-up --name <you>

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:

bin/bee-up --name <you>

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 add line that brings a coworker's Claude session with nothing installed.

Start a Claude session the way it prints:

claude --mcp-config ~/.bee/mcp.json --allowedTools 'mcp__bee__*'

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:

  • /mcp in the session lists bee and 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_HOME moves everything bee-up keeps, the board included, so BEE_HOME=$(mktemp -d) bin/bee-up --port 8300 is 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>.
  • --local keeps 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-open skips 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:

bin/bee-join '<link>' --name <you>

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:

bin/bee-bundle

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:

/mcp__bee__whiteboard login-flow

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:

/mcp__bee__checkin

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)
  1. 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:
claude mcp add --scope user bee -- "$BEE/bee-mcp/build/install/bee-mcp/bin/bee-mcp"
  • 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_HOME or PATH. Set env.JAVA_HOME if Claude Code starts without one.
  • BEE_HOST names the board. Unset, the bridge dials ws://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_NAME names the session on the board, as agent:<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.json applies to every session you start.
  • One project: use that project's .claude/settings.json, or .claude/settings.local.json to 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:

  1. It finds the session's claude process.
  2. It reads ~/.bee/run/<claudePid>.json, which holds the bridge's port, bearer token and pid.
  3. It checks that the bridge is still alive.
  4. It sends POST /retire to 127.0.0.1 with 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

  • /mcp in a session lists bee with 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 with SIGKILL can'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.

  1. On the other device, open that address. Add ?screen=room to name it as a room screen, so the board shows it as human:room.
  2. The device shows a six-digit code, and nothing of the board yet.
  3. 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 offline in its bar while the host is down, and reconnects on its own. After --forget-devices it 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.

  1. Start the host with bee-up (or bee-host --lan). bee-up prints the line below with your address, port and key filled in.
  2. On the teammate's machine, register the URL once. The join link's key goes in ?k=:

    claude mcp add --scope user --transport http bee 'http://192.168.1.5:8233/mcp?k=…&name=<them>'
    

    ?name= names the session on the board as agent:<name>-claude#…. Without it the session is agent:claude#…. --scope user registers bee for every folder on their machine; without it, the server exists only in the folder they ran the command in. 3. They start claude and 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.

claude mcp add --transport http bee 'http://127.0.0.1:8233/mcp?name=<you>'

What to expect:

  • claude mcp add refuses a name it already has. It answers "MCP server bee already exists" and exits 1. To point bee at a new link, run claude mcp remove --scope user bee first.
  • The key is in your Claude config, as with BEE_HOST. Treat the config like the link.
  • No /clear hook. The hook talks to a bridge process on your machine, and this bridge runs on the host. A session that runs /clear keeps 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 /mcp in Claude Code, or restart claude.

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.

  1. Start the host reachable from your network (bee-up, or bee-host --lan), and copy the join link it prints: http://<your machine>.local:8233/?k=….
  2. 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:

    "env": { "BEE_HOST": "http://tractatus.local:8233/?k=…", "BEE_SESSION_NAME": "sam" }
    
  3. They start a session. It joins your board as agent:sam#…, and you both see it in bee_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-link or --forget-devices, the old key no longer admits, so their bridge is refused (Cutting a device off). Send the new link, and have them update BEE_HOST and 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 .local names 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.