diff --git a/README.md b/README.md index 58002af..7046a27 100644 --- a/README.md +++ b/README.md @@ -1,41 +1,60 @@ # ClaudeMailbox -A standalone MCP mail server that lets parallel Claude sessions coordinate with each other. Any Claude session (plain terminal, ClaudeDo worktree, anything that consumes `.mcp.json`) can send messages to a peer session's inbox, check for pending messages, and discover other active mailboxes. +A standalone MCP mail server that lets parallel Claude sessions coordinate with each other. Messages are queued in a tiny SQLite database via a local HTTP daemon. Any Claude session — Claude Code, ClaudeDo worktree, plain MCP client — can send to a peer's inbox, check for pending messages, and discover other active mailboxes. -Not a substitute for `run_in_background: true` — that handles single-session responsiveness. This handles **session-to-session** coordination. +Not a substitute for `run_in_background: true` (which handles single-session responsiveness). This handles **session-to-session** coordination. -## Architecture +--- -One long-running daemon binds HTTP on loopback, hosts the MCP server at `/mcp` and a small REST API at `/v1/*`, and persists state in a single SQLite file. Sessions declare themselves via an `X-Mailbox` header in their `.mcp.json`. +## Getting started + +Pick one path. Most users want path A. + +### A. Claude Code plugin (recommended — three prompts) + +Inside Claude Code: ``` - session-backend session-frontend external sender - (X-Mailbox: backend) (X-Mailbox: frontend) (CLI / UI / hook) - | | | - | HTTP | | - +--------------+-----------------+--------------------------+ - v - claude-mailbox serve (ASP.NET Core + Kestrel) - /mcp MCP tools - /v1/* REST for non-MCP senders - /health - v - ~/.claude-mailbox/mailbox.db (SQLite WAL) +/plugin marketplace add https://git.kuns.dev/releases/ClaudeMailbox +/plugin install claude-mailbox@claude-mailbox +/claude-mailbox:mailbox-doctor ``` -## Install +The doctor command does the rest: -The recommended path is the npm package — it works on Windows, macOS, and Linux. +1. installs the daemon binary via `npm install -g @kuns/claude-mailbox` if missing (asks first) +2. registers the daemon for autostart and starts it +3. optionally lets you pick a base prefix (e.g. `backend`, `frontend`); without one, mailbox names are anonymous (`claude-a8b3c1d2`) +4. runs a self → self smoke test + +After that, every Claude Code session automatically: + +- gets a **unique mailbox identity** derived from its session UUID (so two parallel sessions never collide), +- announces that identity and the **list of currently active peers** at session start, +- pulls unread mailbox messages into context before every prompt. + +You can then say things like: + +> "I started a second session, coordinate with it on the refactor." + +Claude already has the peer's mailbox name in context from the SessionStart announcement, so it calls `mcp__mailbox__send(from="", to="", body="...")` directly. + +See [`plugin/README.md`](./plugin/README.md) for the full walkthrough, including the `mailbox-status` and `mailbox-update` slash commands. + +### B. Manual install (no Claude Code plugin) + +If you're using a different MCP client, scripts, or you don't want the plugin: ```sh # one-time per machine: point the @kuns scope at the public Gitea npm registry npm config set @kuns:registry=https://git.kuns.dev/api/packages/releases/npm/ -# install +# install + autostart npm install -g @kuns/claude-mailbox +claude-mailbox install-autostart ``` -Or use the bootstrap one-liner: +Or the bootstrap one-liner: ```powershell # Windows @@ -47,96 +66,7 @@ irm https://git.kuns.dev/releases/ClaudeMailbox/raw/branch/main/install.ps1 | ie curl -fsSL https://git.kuns.dev/releases/ClaudeMailbox/raw/branch/main/install.sh | sh ``` -macOS users can also install via Homebrew once the tap is published: - -```sh -brew install kuns/tap/claude-mailbox -``` - -### Autostart - -```sh -claude-mailbox install-autostart # per-user, no admin -claude-mailbox install-autostart --service # Windows only: register as a Windows Service (admin) -claude-mailbox status # Running | Stopped | NotInstalled -claude-mailbox uninstall-autostart [--purge] -``` - -| Platform | Default mechanism | `--service` mechanism | -|---|---|---| -| Windows | Scheduled Task at logon (no admin) | Windows Service (admin, via `node-windows`) | -| macOS | launchd LaunchAgent in `~/Library/LaunchAgents/` | n/a | -| Linux | systemd `--user` unit in `~/.config/systemd/user/` | n/a | - -### Config precedence - -``` -CLI flag > mailbox.json > built-in defaults -``` - -`mailbox.json` is searched at `~/.claude-mailbox/mailbox.json` (per-user), and on Windows additionally at `%ProgramData%\ClaudeMailbox\mailbox.json` (machine-wide, written by `--service` install). Pass `--config ` to override. - -Defaults: port `47822`, bind `127.0.0.1`, database at `~/.claude-mailbox/mailbox.db`. - -### Smoke test - -```sh -claude-mailbox install-autostart -claude-mailbox status -curl http://127.0.0.1:47822/health -claude-mailbox uninstall-autostart --purge -``` - -### Build the .NET binary (alternative) - -The original .NET 8 implementation still lives in `src/ClaudeMailbox/`. Build a self-contained Windows exe with: - -```powershell -dotnet publish src/ClaudeMailbox -c Release -r win-x64 --self-contained -p:PublishSingleFile=true -``` - -Put the resulting `claude-mailbox.exe` on your `PATH` and use the legacy `install-service` verbs (Windows-only, admin shell): - -``` -claude-mailbox install-service [--port 47822] [--bind 127.0.0.1] [--db-path ] -claude-mailbox uninstall-service [--purge] -``` - -The .NET and Node builds are wire-compatible (same port, same `X-Mailbox` header, same MCP tool names, same SQLite schema), so a `.mcp.json` configured against one works against the other. - -## Use from Claude Code (plugin) - -Easiest path — three prompts, all inside Claude Code: - -``` -/plugin marketplace add https://git.kuns.dev/releases/ClaudeMailbox -/plugin install claude-mailbox@claude-mailbox -/claude-mailbox:mailbox-doctor -``` - -The doctor command auto-installs the daemon binary via npm (asks first), registers autostart, optionally takes a base prefix (e.g. `backend`), and runs a smoke test. Subsequent slash commands: - -- `/claude-mailbox:mailbox-status` — read-only health check -- `/claude-mailbox:mailbox-update` — pull the latest daemon version and restart - -**Each Claude session gets its own mailbox identity** derived from the session's UUID — `claude-a8b3c1d2` by default, or `-a8b3c1d2` if you set a prefix. Parallel sessions in the same project automatically get distinct names. The `SessionStart` hook announces the current session's name in context so Claude knows its own identity and which args to pass to MCP tools. - -The plugin auto-wires the MCP server too. Because two parallel sessions share one `.mcp.json`, the MCP tools take the caller's mailbox name as an **explicit argument** (`from` / `name`) instead of relying on a shared HTTP header — so multi-session coordination just works: - -``` -You: "I started a second session, work together on this." -Claude (session A): looks up peers via mcp__mailbox__list_mailboxes(name=""), - sends via mcp__mailbox__send(from="", to="", body="...") -Claude (session B): receives the message on the next prompt via the UserPromptSubmit hook. -``` - -If the daemon goes down later, the hook emits a one-line setup hint instead of staying silent. - -See [`plugin/README.md`](./plugin/README.md) for the full walkthrough. - -## Use from a Claude session (without the plugin) - -If you're not using the Claude Code plugin, drop this into your project's `.mcp.json`: +Then drop this into your project's `.mcp.json`: ```json { @@ -149,20 +79,70 @@ If you're not using the Claude Code plugin, drop this into your project's `.mcp. } ``` -The MCP tools take the caller's identity as an argument. If you want the legacy single-session style where every call uses the same mailbox name without specifying it, add a header (Claude then doesn't need to pass `from` / `name`): +Optionally add a static identity (so your client doesn't need to pass `from` / `name` on every call): ```json "headers": { "X-Mailbox": "backend" } ``` -Four MCP tools are exposed: +### C. Build the .NET binary from source -| Tool | Purpose | +The original .NET 8 implementation lives in `src/ClaudeMailbox/`. Wire-compatible with the npm build (same port, same `X-Mailbox` header, same MCP tool names, same SQLite schema). + +```powershell +dotnet publish src/ClaudeMailbox -c Release -r win-x64 --self-contained -p:PublishSingleFile=true +``` + +Put the resulting `claude-mailbox.exe` on `PATH`. Windows-only `install-service` verbs (admin shell): + +``` +claude-mailbox install-service [--port 47822] [--bind 127.0.0.1] [--db-path ] +claude-mailbox uninstall-service [--purge] +``` + +--- + +## How identity works + +Every Claude Code session gets a unique mailbox name derived from its UUID: + +| Setup | Resulting mailbox name | |---|---| -| `mcp__mailbox__send(from, to, body)` | Send a message. `from` is your mailbox; falls back to X-Mailbox header. | -| `mcp__mailbox__check_inbox(name)` | Pull all pending messages for `name` (marks delivered). Falls back to X-Mailbox header. | -| `mcp__mailbox__peek_inbox(name)` | Non-consuming check — returns `{ pending, oldestAt }`. Falls back to X-Mailbox header. | -| `mcp__mailbox__list_mailboxes(name)` | Discover known mailboxes; `name` is needed for accurate `pendingForYou`. Falls back to X-Mailbox header. | +| Default | `claude-<8-hex-of-session-id>` | +| `CLAUDE_MAILBOX_NAME=backend` (in `.claude/settings.json` env) | `backend-<8-hex>` | +| Manual `.mcp.json` with `X-Mailbox: backend` header (no plugin) | `backend` (legacy mode) | + +The plugin's `SessionStart` hook prints the session's identity and the list of peers active in the last hour into the conversation context, so Claude knows who it is and who's around without needing to call any tools first. + +--- + +## Autostart + +```sh +claude-mailbox install-autostart # per-user, no admin +claude-mailbox install-autostart --service # Windows only: Windows Service (admin) +claude-mailbox status # Running | Stopped | NotInstalled +claude-mailbox uninstall-autostart [--purge] +``` + +| Platform | Default mechanism | `--service` mechanism | +|---|---|---| +| Windows | Scheduled Task at logon (no admin) | Windows Service (admin, via `node-windows`) | +| macOS | launchd LaunchAgent in `~/Library/LaunchAgents/` | n/a | +| Linux | systemd `--user` unit in `~/.config/systemd/user/` | n/a | + +--- + +## MCP tools + +| Tool | Required args | Purpose | +|---|---|---| +| `mcp__mailbox__send` | `to`, `body`, `from` | Send a message. `from` falls back to X-Mailbox header. | +| `mcp__mailbox__check_inbox` | `name` | Pull all pending messages and mark delivered. Falls back to header. | +| `mcp__mailbox__peek_inbox` | `name` | Non-consuming `{ pending, oldestAt }`. Falls back to header. | +| `mcp__mailbox__list_mailboxes` | `name` | Discover known mailboxes + `pendingForYou`. Falls back to header. | + +The plugin's SessionStart announcement tells Claude exactly which name to pass for the current session, so the args are filled in automatically. ### Suggested CLAUDE.md snippet for poll discipline @@ -172,41 +152,93 @@ after each subagent completes. If pending > 0, call mcp__mailbox__check_inbox and treat the messages as input with priority over the current plan. ``` -## CLI client mode +--- -Any external process (scripts, UIs, hooks) can talk to a running daemon without needing MCP: +## CLI + +Any external process — scripts, UIs, manual debugging — can talk to a running daemon directly: ``` -claude-mailbox send --to --from --body [--url http://127.0.0.1:47822] -claude-mailbox peek --name [--url ...] -claude-mailbox check --name [--url ...] -claude-mailbox list [--url ...] +claude-mailbox send --from --to --body +claude-mailbox peek --name +claude-mailbox check --name [--hook] +claude-mailbox list +claude-mailbox status +claude-mailbox session-announce # hook helper, reads stdin JSON +claude-mailbox install-hook --name [--user|--project] +claude-mailbox uninstall-hook [--user|--project] ``` -The CLI subcommands are thin HTTP clients against the `/v1/*` endpoints. +All subcommands accept `--url ` to target a non-default daemon address. + +--- ## REST surface -| Method | Path | Requires `X-Mailbox` | Purpose | +| Method | Path | `X-Mailbox` required | Purpose | |---|---|---|---| | `GET` | `/health` | no | `{ status, version, dbPath }` | -| `POST` | `/v1/send` | yes (sender) | `{ to, body }` | +| `POST` | `/v1/send` | yes (sender) | body: `{ to, body }` | | `GET` | `/v1/peek?name=` | no | read-only status | | `POST` | `/v1/check-inbox?name=` | yes (must match `name`) | consume inbox | -| `GET` | `/v1/list` | no | list all mailboxes | +| `GET` | `/v1/list` | optional (presence registers caller) | list all mailboxes | + +--- + +## Config precedence + +``` +CLI flag > mailbox.json > built-in defaults +``` + +`mailbox.json` is searched at `~/.claude-mailbox/mailbox.json` (per-user), and on Windows additionally at `%ProgramData%\ClaudeMailbox\mailbox.json` (machine-wide, written by `--service` install). Override with `--config `. + +Defaults: port `47822`, bind `127.0.0.1`, database at `~/.claude-mailbox/mailbox.db`. + +--- + +## Architecture + +One long-running daemon binds HTTP on loopback, hosts the MCP server at `/mcp` and a small REST API at `/v1/*`, and persists state in a single SQLite file. + +``` + session-A session-B external sender + mailbox: claude-a8b3c1d2 mailbox: claude-d4e5f6a7 (CLI / UI / script) + | | | + | HTTP | | + +--------------+-----------------+--------------------------+ + v + claude-mailbox serve (npm: Fastify; .NET: Kestrel) + /mcp MCP tools + /v1/* REST for non-MCP senders + /health + v + ~/.claude-mailbox/mailbox.db (SQLite WAL) +``` + +--- ## Development -``` +```sh +# Node port (the recommended runtime) +cd node +npm install +npm run build +npm test + +# .NET 8 port (wire-compatible alternative) dotnet build dotnet test tests/ClaudeMailbox.Tests/ClaudeMailbox.Tests.csproj dotnet run --project src/ClaudeMailbox -- serve ``` -Test suite covers end-to-end coordination, concurrent `check_inbox` race safety, and schema idempotency. +The test suites cover end-to-end coordination, concurrent `check_inbox` race safety, schema idempotency, hook stdin parsing, session-id derivation, and settings-file patching. + +--- ## Scope - Loopback bind only (v1). Cross-machine coordination is a future extension — swap the middleware for token auth and change the bind address. - No auth on loopback. Local filesystem permissions are the trust boundary. -- No message expiry or cleanup. Delivered messages stay as a timeline/audit log. +- No message expiry. Delivered messages remain as an audit log.