Skip to main content
Glama
katekyorock-ux

claude-code-relay

README.md
# claude-code-relay

**Relay messages between Claude Code sessions over MCP — without stealing window focus.**

Run several Claude Code sessions in parallel and let them message each other. A message
is injected into the target session's conversation *in the background*, as a `<channel>`
block — no keystroke paste, no window jumping to the foreground. You keep typing where you
are; the message just appears in the other session.

## The problem

If you drive multiple Claude Code sessions at once, you eventually want them to talk — one
hands work to another, or asks a question. The obvious way (paste into the target terminal)
**steals focus**: the target window jumps to the front and your next keystrokes land in the
wrong place. That makes genuine parallel work painful.

## The approach

`claude-code-relay` uses Claude Code's experimental **channel** capability. Each session
runs a tiny MCP server that:

1. listens on a local HTTP port, and
2. pushes any received message into its own conversation via the
   `notifications/claude/channel` notification.

Delivery is a plain HTTP POST from the sender to the receiver's port. Background injection
means the receive is **focus-free**.

```
 session "alice"                             session "bob"
   send.js  ──HTTP POST 127.0.0.1:8802──▶  channel.mjs  (MCP server)
                                                 │
                                                 ▼  notifications/claude/channel
                                    <channel source="claude-code-relay" from="alice">
                                       hey, can you take the migration?
                                    </channel>      ← appears in bob's conversation
```

## Quick start

```bash
npm install
cp ports.example.json ports.json     # map session names -> ports
```

Register the receiver per project in `.mcp.json`, giving each session its own port via
`RELAY_CHANNEL_PORT`:

```jsonc
{
  "mcpServers": {
    "claude-code-relay": {
      "command": "node",
      "args": ["./channel.mjs"],
      "env": { "RELAY_CHANNEL_PORT": "8801" }
    }
  }
}
```

Launch each Claude Code session with development channels enabled (experimental flag), on
its own port:

```bash
RELAY_CHANNEL_PORT=8801 claude --dangerously-load-development-channels   # session "alice"
RELAY_CHANNEL_PORT=8802 claude --dangerously-load-development-channels   # session "bob"
```

Send a message from one session to another:

```bash
node send.js alice bob "hey, can you take the migration?"
```

It shows up in bob's conversation as a `<channel>` block. Bob replies the same way:

```bash
node send.js bob alice "on it"
```

## Notes & caveats

- **Experimental flag.** This relies on Claude Code's `--dangerously-load-development-channels`
  and the `claude/channel` capability. Both are experimental and may change between releases.
- **Receive-only channel.** The channel only delivers *into* a session; there is no reply
  tool. Sessions reply by running `send.js`. Typing in the window sends nothing.
- **Localhost only.** Servers bind `127.0.0.1` — this coordinates sessions on one machine.
- **ASCII session names** are recommended (the sender name travels in an HTTP header).
- **One port per session.** A duplicate spawn on a taken port exits cleanly (no churn).

## Verify it works

`mcp-probe.mjs` starts the receiver as a real MCP server (over stdio, exactly as Claude Code would), sends a message over HTTP, and asserts it comes back as a `notifications/claude/channel` notification — the full injection path, end to end:

```bash
npm test
# -> E2E PASS: HTTP POST -> channel.mjs -> notifications/claude/channel -> MCP client
```

The only thing this can't assert headlessly is the final UI render, which is Claude Code's job.

## License

MIT © TEAM ROCK