From ef1e014b4155fbf3577210c8ddeefcfe754225eb Mon Sep 17 00:00:00 2001 From: Dai Ha Date: Mon, 10 Aug 2026 19:54:49 +0200 Subject: [PATCH] CB-527: ship the bridge as an installable Claude Code plugin MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The orchestration contract had no distributable form. Every consuming project hand-copied a block of CLAUDE.md and hand-wrote an .mcp.json, and we keep a script whose only job is to notice those copies drifting apart. A plugin is versioned, installed once, and updates in place. Ships no credentials, deliberately: every secret is referenced by environment variable NAME and the value never enters a file, which is what makes the artifact safe to publish. The setup skill states the two rules that are easy to get wrong for the right-sounding reasons — the PR token must not be able to merge (a worker opens, the primary gates), and ANTHROPIC_BASE_URL must never be set by setup, because mounting the bridge must not move a session off subscription. The plugin root is plugin/, not the repo root. An installed plugin's .mcp.json is a committed file, while this repo's root .mcp.json is local-only and --skip-worktree; rooting the plugin at the repo would commit the primary's IDE servers and hand them to every worker — the exact failure CB-525 exists to prevent. Scope is client-side setup only. herdr and bridged stay separate services with their own lifecycles, and the skill refuses to install them rather than guess. It also refuses to accept /healthz as proof: health reports only that the daemon can reach herdr, and CB-521 showed it staying green while every spawn failed, so verification ends with a real spawn. Both manifests pass `claude plugin validate --strict`. --- .claude-plugin/marketplace.json | 18 +++ plugin/.claude-plugin/plugin.json | 18 +++ plugin/.mcp.json | 8 ++ plugin/README.md | 54 +++++++ plugin/skills/setup/SKILL.md | 227 ++++++++++++++++++++++++++++++ 5 files changed, 325 insertions(+) create mode 100644 .claude-plugin/marketplace.json create mode 100644 plugin/.claude-plugin/plugin.json create mode 100644 plugin/.mcp.json create mode 100644 plugin/README.md create mode 100644 plugin/skills/setup/SKILL.md diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json new file mode 100644 index 0000000..90c5530 --- /dev/null +++ b/.claude-plugin/marketplace.json @@ -0,0 +1,18 @@ +{ + "name": "claude-bridge", + "description": "Tooling for orchestrating a fleet of delegated coding agents through the bridged MCP gateway.", + "owner": { + "name": "LTMS" + }, + "plugins": [ + { + "name": "claude-bridge", + "source": "./plugin", + "description": "Make a project bridge-ready: mount the bridged MCP gateway and apply standard Claude Code settings so a session can orchestrate delegated workers. Ships no credentials.", + "version": "0.1.0", + "author": { + "name": "LTMS" + } + } + ] +} diff --git a/plugin/.claude-plugin/plugin.json b/plugin/.claude-plugin/plugin.json new file mode 100644 index 0000000..3e5d1fa --- /dev/null +++ b/plugin/.claude-plugin/plugin.json @@ -0,0 +1,18 @@ +{ + "name": "claude-bridge", + "description": "Make a project bridge-ready: mount the bridged MCP gateway and set up standard Claude Code settings so this session can orchestrate a fleet of delegated workers. Ships no credentials.", + "version": "0.1.0", + "author": { + "name": "LTMS" + }, + "homepage": "https://git.ltms.dev/lms/claude-bridge", + "repository": "https://git.ltms.dev/lms/claude-bridge", + "license": "MIT", + "keywords": [ + "mcp", + "orchestration", + "multi-agent", + "delegation", + "codex" + ] +} diff --git a/plugin/.mcp.json b/plugin/.mcp.json new file mode 100644 index 0000000..05a0289 --- /dev/null +++ b/plugin/.mcp.json @@ -0,0 +1,8 @@ +{ + "mcpServers": { + "bridged": { + "type": "http", + "url": "http://127.0.0.1:8765/mcp" + } + } +} diff --git a/plugin/README.md b/plugin/README.md new file mode 100644 index 0000000..54efabd --- /dev/null +++ b/plugin/README.md @@ -0,0 +1,54 @@ +# claude-bridge (Claude Code plugin) + +Makes a project **bridge-ready**: mounts the `bridged` MCP gateway and applies standard Claude Code +settings, so the session can orchestrate a fleet of delegated workers. + +**This plugin ships no credentials.** Every secret is referenced by environment-variable *name*; +the values stay with the user. Nothing the plugin writes is unsafe to commit. + +## What it is not + +The plugin is the **client-side setup**, not the bridge. `bridged` is a separate daemon and `herdr` +is a separate PTY multiplexer, each with its own lifecycle and install. The plugin mounts an +already-running daemon and tells you what is missing when one isn't there — it deliberately does +not try to install system services on your behalf. + +## Install + +```shell +/plugin marketplace add ltms/claude-bridge +/plugin install claude-bridge@claude-bridge +``` + +Then, in the project you want to onboard: + +```shell +/claude-bridge:setup +``` + +## What you get + +| Component | Effect | +|---|---| +| `.mcp.json` | mounts `bridged` at `http://127.0.0.1:8765/mcp` for any session with the plugin enabled | +| `skills/setup` | `/claude-bridge:setup` — preflight, project settings, credential guidance, and verification | + +Because the plugin carries its own `.mcp.json`, an installed plugin needs no project-level MCP +file at all. The setup skill writes one only when you want the mount to work *without* the plugin — +for teammates who haven't installed it, or for CI. + +## Verifying a setup + +The setup skill ends by requiring a **real spawn**, not a health check. `/healthz` only reports +that the daemon can reach herdr; a protocol mismatch between the daemon's adapter and the herdr +binary leaves health green while every spawn fails. Only a spawn that reaches `ready` proves the +fleet. + +## Local development + +```shell +claude --plugin-dir ./plugin +claude plugin validate ./plugin +``` + +`/reload-plugins` picks up edits without restarting the session. diff --git a/plugin/skills/setup/SKILL.md b/plugin/skills/setup/SKILL.md new file mode 100644 index 0000000..02a03f2 --- /dev/null +++ b/plugin/skills/setup/SKILL.md @@ -0,0 +1,227 @@ +--- +name: setup +description: Make the current project bridge-ready — check the prerequisites, mount the bridged MCP gateway into the project's .mcp.json, apply standard Claude Code settings, and verify this session resolves as the primary. Writes no credentials. Load this when asked to set up, install, configure, or onboard a project onto claude-bridge, or when bridge_* tools are expected but absent. +--- + +# Bridge setup — make this project bridge-ready + +This skill configures **the project you are currently in** so that this Claude Code session can +orchestrate a fleet of delegated workers through `bridged`. + +**It writes no credentials, ever.** Every secret is referenced by environment-variable *name*, and +the user exports the value themselves. Nothing this skill creates is unsafe to commit. If you are +ever about to write a token, key, or password into a file, you have misread this skill — stop. + +Work through the steps in order. Each one has a check; **report what actually happened**, including +failures. A setup that half-worked and was reported as done is worse than one that failed loudly. + +## 0. Establish where you are + +```bash +pwd +git rev-parse --show-toplevel 2>/dev/null || echo "(not a git repo)" +ls -a | head -30 +``` + +Everything below is written into **this** project root. If the user meant a different directory, +confirm before writing anything. + +## 1. Preflight — what must already exist + +The bridge is three moving parts, and the plugin is only one of them. Check all of it before +changing any file, so you can tell the user the whole story at once instead of failing one step at +a time. + +```bash +command -v herdr && herdr --version 2>&1 | head -1 || echo "MISSING: herdr" +command -v ccs && ccs version 2>&1 | head -1 || echo "MISSING: ccs (needed for worker profiles)" +command -v codex && codex --version 2>&1 | head -1 || echo "absent: codex (optional)" +curl -s -m 5 http://127.0.0.1:8765/healthz || echo "MISSING: bridged daemon is not reachable" +``` + +A healthy daemon answers with its status **and the herdr protocol it negotiated**: + +```json +{"status":"ok","herdr":{"version":"0.8.0","protocol":19}} +``` + +| Missing | What to tell the user | +|---|---| +| `herdr` | The PTY multiplexer that owns worker terminals. Install it first; nothing else works without it. | +| `bridged` | The daemon. It is a separate service, not part of this plugin — the plugin only *mounts* it. Point the user at the project's own install instructions. | +| `ccs` | Only needed to launch worker profiles. The bridge itself will still start. | +| `codex` | Optional. Only needed if this fleet will run Codex peers. | + +**Do not attempt to install these yourself.** They are system services with their own lifecycles; +guessing at an install is how you end up with two daemons on one socket. Report what is missing and +let the user install it. + +## 2. Mount the bridge MCP — merge, never overwrite + +The project's `.mcp.json` may already declare servers. **Read it first and merge**; clobbering +someone's existing MCP config is not a recoverable mistake. + +```bash +cat .mcp.json 2>/dev/null || echo "(no .mcp.json yet)" +``` + +The entry to add, exactly: + +```json +{ + "mcpServers": { + "bridged": { + "type": "http", + "url": "http://127.0.0.1:8765/mcp" + } + } +} +``` + +If `.mcp.json` already exists, add only the `bridged` key and leave every other server untouched. +If a `bridged` entry is already there with a different URL, **ask** rather than assuming yours is +right — a non-default port usually means a deliberate second daemon. + +> **If this plugin is installed, you can skip this step entirely.** The plugin ships its own +> `.mcp.json`, so `bridged` is already mounted for any session with the plugin enabled. Write the +> project-level file only when the user wants the mount to work *without* the plugin — for +> teammates who have not installed it, or for CI. + +**Before writing it, settle whether `.mcp.json` is committed here:** + +```bash +git ls-files --error-unmatch .mcp.json 2>/dev/null && echo "TRACKED" || echo "untracked" +``` + +A tracked `.mcp.json` is inherited by every checkout of this repo — including git worktrees the +bridge provisions for workers. Servers bound to *your* machine (an IDE index, a local language +server) will then be mounted by workers too, and every path they return points into **your** +checkout rather than the worker's. That failure is silent and expensive: it has produced a worker +that made all of its edits in the wrong tree while its builds passed, because it was building the +tree it was not editing. Keep machine-local servers out of a tracked `.mcp.json`, or keep the file +untracked. + +## 3. Standard project settings + +Create or merge `.claude/settings.json`. These are defaults, not requirements — keep anything the +project already set. + +```json +{ + "$schema": "https://json.schemastore.org/claude-code-settings.json", + "permissions": { + "allow": [ + "mcp__bridged__bridge_whoami", + "mcp__bridged__bridge_list", + "mcp__bridged__bridge_status", + "mcp__bridged__bridge_profiles", + "mcp__bridged__bridge_poll" + ] + } +} +``` + +Only the **read-only** bridge verbs are pre-allowed. `bridge_spawn`, `bridge_send`, and +`bridge_stop` start processes, deliver work, and tear down terminals — those stay behind a prompt +on purpose. Do not "helpfully" add them. + +Never write `settings.local.json` on the user's behalf; that file is personal and usually +gitignored. + +## 4. Credentials — by reference only + +The bridge takes every secret from the **environment**, and the daemon's config names the variable +rather than holding the value. Your job is to tell the user which variables to export, not to +collect or store them. + +| Variable | Needed for | Notes | +|---|---|---| +| `BRIDGED_WORKER_TOKEN` | authenticating a worker to the daemon | only when the daemon is configured with `tokenEnv` | +| `GITEA_TOKEN` / equivalent | letting a worker open its own PR | **minimal `write:repository` scope** — see below | +| `GITEA_HOST` | the forge base URL | no secret; safe anywhere | + +Two rules to state plainly to the user: + +- **The PR token must not be able to merge.** A worker opens a PR; the primary is the gate. A token + that can merge makes the gate decorative. Mint a narrow, repo-scoped token — never reuse a + personal admin token. +- **Never set `ANTHROPIC_BASE_URL` or `ANTHROPIC_AUTH_TOKEN`** in this project, this shell, or any + settings file. The primary stays on subscription; only the daemon moves a *worker* off it, at + spawn. Mounting the bridge must never move a session across that boundary — if setup appears to + need this, something is wrong and you should stop and say so. + +Write none of these into any file. Show the user the `export` lines to run themselves. + +## 5. Verify — and do not trust a green health check + +Reconnect MCP if needed (`/mcp`), then confirm the tools are live and this session is the primary: + +``` +bridge_whoami +``` + +- `{"role":"primary"}` — correct, you are done with this step. +- `{"role":"worker", …}` — **this is the trap.** If the primary runs inside a herdr pane, the + daemon resolves it to a terminal and classifies it as a worker, refusing `spawn`/`send`/`stop`: + every verb an orchestrator exists to call. It is **self-locking**, because the daemon can only + *learn* the primary's terminal from those same refused calls. The only way out is an + operator-set pin in the daemon's config: + + ```yaml + primary: + terminal: term_xxxxxxxxxxxx # the terminalId bridge_whoami just reported + ``` + + The daemon reads this **at boot**, so it needs a restart. Re-pin whenever the primary moves + panes — a stale pin fails exactly as silently as no pin. + +Then prove the fleet actually works, with a real spawn: + +``` +bridge_profiles → the configured backends +bridge_spawn{profile: ""} → must reach state "ready" +bridge_stop{paneId: ""} +``` + +**`/healthz` reporting `ok` is not evidence that spawning works.** It reports that the daemon can +reach herdr — nothing more. A version mismatch between the daemon's adapter and the herdr binary +leaves health green while every single spawn fails. Only a real spawn proves the fleet. Do this +even when everything above looked fine. + +## 6. Optional — Codex parity + +Only if the user wants Codex and Claude Code to share instructions, skills, and MCP config: + +```bash +npm install -g ai-config-sync-manager +ai-config-sync connect +ai-config-sync status # compare both hosts +ai-config-sync sync --dry-run # preview — always look before applying +ai-config-sync sync --apply +``` + +It maps `~/.claude/CLAUDE.md` ↔ `~/.codex/AGENTS.md`, `~/.claude/skills/` ↔ `~/.codex/skills/`, and +Claude's MCP servers ↔ `[mcp_servers.*]` in `~/.codex/config.toml`. + +**Raise the boundary before running it.** That sync is *user-level* and bidirectional, while the +bridge deliberately isolates each worker's tool surface (§2). Syncing your MCP servers into +`~/.codex/config.toml` gives every Codex session your machine-local servers — the same +wrong-tree failure as §2, in a different runtime. Use the sync for the two CLIs *you* drive +interactively; leave anything the bridge spawns isolated. Always `--dry-run` first. + +## 7. Report + +State plainly: + +``` +prereqs: herdr · bridged · ccs · codex +written: +role: +spawn: +env: +skipped: +``` + +Never report a step as done that you did not verify. If the daemon was unreachable, say so and stop +— the remaining steps cannot be checked, and guessing at them is how a broken setup gets called +finished.