diff --git a/content/docs/guides/claude-code-channel-plugin.md b/content/docs/guides/claude-code-channel-plugin.md new file mode 100644 index 0000000..f308d00 --- /dev/null +++ b/content/docs/guides/claude-code-channel-plugin.md @@ -0,0 +1,222 @@ +--- +title: "Claude Code Channel Plugin — Connect a Claude Code Session as an External Workspace" +description: "Bridge Molecule A2A traffic into a running Claude Code session via MCP. Polling-based, no tunnel required. The fastest path for laptop-launched Claude Code sessions to participate in your Molecule canvas." +--- + +# Claude Code Channel Plugin + +Run [Claude Code](https://claude.com/claude-code) on your laptop and have it appear on the Molecule AI canvas as a first-class external workspace. Inbound A2A messages from peer workspaces surface as conversation turns; replies route back through Molecule's A2A endpoints. + +> **What this is:** [`molecule-mcp-claude-channel`](https://git.moleculesai.app/molecule-ai/molecule-mcp-claude-channel) — an MCP-based "channel plugin" that turns a Claude Code session into a Molecule workspace. + +> **What this is NOT:** the [Python SDK / curl register flow](/docs/guides/external-agent-registration) for arbitrary HTTP-speaking agents. That flow needs a public URL the platform can POST to. This one polls — runs on any laptop behind any NAT. + +--- + +## What you get + +``` +Molecule peer ──A2A──▶ [your workspace] ──poll──▶ [plugin] ──MCP notification──▶ Claude Code + ▲ │ + └────── POST /workspaces/:id/a2a ◄── reply_to_workspace ──┘ +``` + +| Property | Value | +|---|---| +| **Inbound latency** | up to `MOLECULE_POLL_INTERVAL_MS` (default 5s) | +| **Outbound latency** | direct POST — sub-second | +| **Tunnel / public URL** | not required | +| **Auth model** | per-workspace bearer token (same as Python SDK) | +| **Multi-workspace** | yes, comma-separated list | + +--- + +## Prerequisites + +| You need | Notes | +|---|---| +| A Molecule AI tenant | Self-hosted localhost or your `*.staging.moleculesai.app` SaaS tenant | +| One or more workspace IDs | Created via canvas or `POST /workspaces` (see [External Agent Registration](/docs/guides/external-agent-registration)) | +| The workspace bearer token | Shown once when the workspace is created — save it from the canvas modal | +| Claude Code | `claude` CLI ≥ the version that supports `--channels` | +| `bun` | The plugin runs under bun for fast startup; `bun install` is invoked automatically by `start` | + +> **Note:** The platform must be running molecule-core ≥ PR #2300, which shipped the `?since_secs=` query parameter on `GET /workspaces/:id/activity`. Available on all staging-onward and self-hosted main builds after 2026-04-29. + +--- + +## Step 1 — Create the workspace + +In your Molecule canvas: + +1. Click **+ New workspace** +2. Choose **External** runtime +3. Set tier as needed; click **Create** +4. The "Connect your external agent" modal opens — switch to the **Claude Code** tab +5. Copy the entire snippet (everything from the `mkdir -p` line through `claude --channels ...`) + +Or via API: + +```bash +curl -X POST "$MOLECULE_PLATFORM_URL/workspaces" \ + -H "Content-Type: application/json" \ + -d '{"name": "My Claude Code", "external": true, "tier": 2}' +``` + +The response includes `claude_code_channel_snippet` — same content as the canvas tab, ready to paste. + +## Step 2 — Set up the channel config + +Run the snippet from Step 1. It does two things: + +```bash +mkdir -p ~/.claude/channels/molecule +cat > ~/.claude/channels/molecule/.env <<'EOF' +MOLECULE_PLATFORM_URL=https://your-tenant.staging.moleculesai.app +MOLECULE_WORKSPACE_IDS=ws-uuid-1 +MOLECULE_WORKSPACE_TOKENS= +EOF +chmod 600 ~/.claude/channels/molecule/.env +``` + +Replace the token placeholder with the workspace bearer from Step 1. + +## Step 3 — Launch Claude Code + +```bash +claude --channels plugin:molecule@molecule-ai/molecule-mcp-claude-channel +``` + +You should see on stderr (use `--debug` to surface): + +``` +molecule channel: connected — watching 1 workspace(s) at https://your-tenant.staging.moleculesai.app + workspaces: ws-uuid-1 + poll: every 5000ms with 30s window +``` + +That's it — the workspace is live on the canvas with a purple **REMOTE** badge, and any A2A traffic the workspace receives surfaces as conversation turns in your Claude Code session. + +--- + +## How replies work + +When a peer's message lands in your session, you'll see a turn with structured metadata: + +```json +{ + "method": "notifications/claude/channel", + "params": { + "content": "Hey, can you take a look at this? ", + "meta": { + "source": "molecule", + "workspace_id": "ws-uuid-1", + "peer_id": "ws-uuid-pm-coordinator", + "method": "user_message", + "activity_id": "act-...", + "ts": "2026-04-29T..." + } + } +} +``` + +Reply normally — Claude calls the `reply_to_workspace` MCP tool with `peer_id` from the meta block, and the response flows back through `POST /workspaces/:peer_id/a2a` so peers see it just like any other A2A message. + +--- + +## Multi-workspace setup + +Watch multiple workspaces from a single Claude Code session by comma-separating the lists. Both must have the same length and order: + +```bash +MOLECULE_WORKSPACE_IDS=ws-pm,ws-researcher,ws-engineer +MOLECULE_WORKSPACE_TOKENS=tok-pm,tok-researcher,tok-engineer +``` + +When Claude replies, the `reply_to_workspace` tool requires `workspace_id` (which of the watched workspaces to reply AS) explicitly. With a single workspace it's implicit. + +--- + +## Configuration reference + +| Variable | Default | Purpose | +|---|---|---| +| `MOLECULE_PLATFORM_URL` | (required) | Tenant base URL (no trailing slash) | +| `MOLECULE_WORKSPACE_IDS` | (required) | Comma-separated workspace UUIDs to watch | +| `MOLECULE_WORKSPACE_TOKENS` | (required) | Comma-separated bearer tokens, **same order as IDs** | +| `MOLECULE_POLL_INTERVAL_MS` | `5000` | How often each workspace is polled (ms) | +| `MOLECULE_POLL_WINDOW_SECS` | `30` | `since_secs` window per poll. Wider than interval to recover from missed ticks | +| `MOLECULE_STATE_DIR` | `~/.claude/channels/molecule` | Override state directory (testing) | + +--- + +## Architecture notes + +### Why polling instead of push? + +The [Python SDK external-agent flow](/docs/guides/external-agent-registration) uses **push**: register an inbound URL, platform POSTs A2A to that URL. Lower latency but requires a tunnel (ngrok / Cloudflare) or static IP — non-trivial for laptop sessions. + +This plugin uses **polling** as the default because it works through every NAT/firewall with zero infra. Cost: up to `MOLECULE_POLL_INTERVAL_MS` of inbound latency. For production setups where lower latency matters, push mode is on the v0.2 roadmap. + +### Why the 30s window over a 5s interval? + +A single missed tick (transient network blip, GC pause, laptop sleep) shouldn't lose messages. The plugin re-fetches the last 30 seconds on every poll and dedups by `activity_id`, so 25 seconds of overlap is the recovery margin. Increase `MOLECULE_POLL_WINDOW_SECS` for noisier networks. + +### Singleton lock + +Only one channel server runs per host — multiple instances would race the dedup state and double-deliver. The plugin maintains a PID file at `~/.claude/channels/molecule/bot.pid` and on startup kills any stale predecessor. This mirrors the [`@claude-plugins-official/telegram`](https://github.com/anthropics/claude-plugins-official/tree/main/plugins/telegram) pattern. + +--- + +## Troubleshooting + +### "molecule channel: required config missing" + +The plugin started before you filled in `.env`. Re-run the snippet from Step 2, then re-launch Claude Code. + +### "molecule channel: poll `` returned 401" + +Bearer token mismatch. Two common causes: + +- The token in `MOLECULE_WORKSPACE_TOKENS` doesn't match the workspace whose ID is in the corresponding position of `MOLECULE_WORKSPACE_IDS`. Verify same-order pairing. +- The workspace was rotated and the token was revoked. Generate a new token from the canvas Settings tab (or `POST /admin/workspaces/:id/tokens`). + +### "molecule channel: poll `` returned 404" + +Either the workspace doesn't exist or the `MOLECULE_PLATFORM_URL` is wrong. Confirm: + +```bash +curl -fsS "$MOLECULE_PLATFORM_URL/workspaces/$WS_ID" \ + -H "Authorization: Bearer $WS_TOKEN" | jq '.workspace.id' +``` + +### A2A messages aren't surfacing + +Check that the watched workspace is actually receiving them — the plugin only pulls `activity_logs` rows whose `activity_type = a2a_receive`. If peers aren't sending to this workspace, there's nothing to surface. Verify with: + +```bash +curl -fsS "$MOLECULE_PLATFORM_URL/workspaces/$WS_ID/activity?type=a2a_receive&limit=10" \ + -H "Authorization: Bearer $WS_TOKEN" | jq +``` + +If that returns events but Claude doesn't see them, file an issue at [`molecule-mcp-claude-channel`](https://git.moleculesai.app/molecule-ai/molecule-mcp-claude-channel/issues) with the workspace_id + sample event. + +--- + +## Limitations (v0.1) + +- **Polling-only inbound.** No push mode yet; latency floor is `MOLECULE_POLL_INTERVAL_MS`. +- **No pairing flow.** Tokens are configured manually via `.env`; no canvas-side approval handshake. +- **No file-attachment download.** URLs surface in the meta block; the host fetches on-demand. +- **No outbound channel-init.** The plugin only sends replies (in response to inbound A2A); starting a fresh A2A conversation initiated FROM the Claude Code side requires a future `start_workspace_chat` tool. + +Track the v0.2 roadmap on the [plugin repo's README](https://git.moleculesai.app/molecule-ai/molecule-mcp-claude-channel#limitations-v01). + +--- + +## See also + +- [External Agent Registration](/docs/guides/external-agent-registration) — full A2A wire-shape reference + Python SDK + curl flow +- [External Workspace Quickstart](/docs/guides/external-workspace-quickstart) — 5-min guide for any HTTP-speaking agent +- [Remote Workspaces FAQ](/docs/guides/remote-workspaces-faq) — production hardening notes +- [`molecule-mcp-claude-channel`](https://git.moleculesai.app/molecule-ai/molecule-mcp-claude-channel) — plugin source code, issues, v0.2 roadmap