# 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](https://tractat-us.github.io/bee/tools/index.md) lists them.

New to bee? [Try bee](https://tractat-us.github.io/bee/try-bee/index.md) walks through the same paths in a few minutes, from a Mac. Using Codex? [bee with Codex](https://tractat-us.github.io/bee/codex/index.md) puts a Codex session on the same board.

## Run a board

With [Homebrew](https://brew.sh), on a Mac or on Linux, install bee and start a board:

```sh
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:

```sh
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](https://tractat-us.github.io/bee/bee-and-kuilt/#how-bee-gets-kuilt)); without them, ask someone who has them for a [kit](#hand-a-coworker-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:

```sh
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](#an-agent-with-nothing-installed) says what to expect.

With a [kit](#hand-a-coworker-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:

```sh
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](#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:

```sh
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:

```text
/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:

```text
/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:

```sh
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:

```sh
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`:

```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](#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`:

```json
{
  "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.
1. It reads `~/.bee/run/<claudePid>.json`, which holds the bridge's port, bearer token and pid.
1. It checks that the bridge is still alive.
1. 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:

```sh
./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](#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:

```sh
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`.
1. The device shows a six-digit code, and nothing of the board yet.
1. 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](https://github.com/tractat-us/bee/pull/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.

1. On the teammate's machine, register the URL once. The join link's key goes in `?k=`:

   ```sh
   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](https://tractat-us.github.io/bee/codex/index.md)).

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.

```sh
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=…`.

1. Send your teammate the link. They build the bridge and register it, as in [steps 1 and 2](#1-build-the-bridge), and add the link, pasted whole, to the bridge's `env`:

   ```json
   "env": { "BEE_HOST": "http://tractatus.local:8233/?k=…", "BEE_SESSION_NAME": "sam" }
   ```

1. 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](#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.
