Channels (Messaging)

MultiTerminal delivers agent-to-agent messages through Claude Code's native Channels transport. Each terminal runs a small channel server that pushes incoming messages straight into the session as structured <channel> events — replacing the older [cm] "check messages" nudge. Channels need a one-time authorization flag, which MultiTerminal applies for you.

What channels are

A channel is a first-class Claude Code transport for pushing messages into a running session. When another agent (or the desktop) sends you a message, it arrives inline as a structured event:

<channel source="multiterminal" from="Diana" priority="normal">
  Can you take the API page? I'll start on presence.
</channel>

You react to it immediately and reply with the channel's tools — no polling, no terminal-injected reminders. MultiTerminal provides a channel server (bundled in the plugin) so every docked terminal participates automatically.

How a message flows

  1. Each terminal's channel server starts up and registers its listen port with the MultiTerminal broker (POST /api/messaging/register with its agent name + channelPort).
  2. An agent calls send_message (or a skill does). The broker looks up the recipient's registered channelPort.
  3. The broker delivers the message over HTTP to that port.
  4. The recipient's channel server pushes it into the Claude Code session as a <channel> event.
  5. The receiving agent reads it and replies with the channel reply / send tools, which route back through the broker.

The channel server

The server is bundled with the plugin at server/multiterminal-channel.mjs and declared in the plugin's plugin.json:

"mcpServers": {
  "multiterminal-channel": {
    "command": "node",
    "args": ["${CLAUDE_PLUGIN_ROOT}/server/multiterminal-channel.mjs"]
  }
},
"channels": [
  { "server": "multiterminal-channel" }
]

It runs as an MCP server (stdio) for the session, and in parallel opens a small HTTP listener that the broker delivers messages to. If its preferred port is busy it walks forward through the allowed range (88008899) until it finds a free one, then re-registers the actual port with the broker, so multiple terminals on one machine don't collide.

The same channel also carries tool-permission prompts to the phone: when Claude needs approval to run a tool, the server forwards the request to your phone (via Multi-Connect) so you can answer remotely — reply yes, no, or always with the short request id to approve, deny, or auto-approve that tool for the session.

Enabling channels (the required setting)

The channel server ships inside the plugin rather than from a published marketplace, so Claude Code treats it as a development (inline) channel. Development channels must be explicitly authorized — otherwise Claude Code will not load the channel server. MultiTerminal launches every terminal with two flags:

# MultiTerminal applies these automatically when it spawns a terminal
claude \
  --plugin-dir <path-to-multiterminal-plugin> \
  --dangerously-load-development-channels plugin:multiterminal@inline
  • --plugin-dir loads the MultiTerminal plugin (hooks, skills, agents, and the channel server) for that session.
  • --dangerously-load-development-channels plugin:multiterminal@inline authorizes the plugin's inline channel server. (Plugins loaded via --plugin-dir are sourced as name@inline.)

For MultiTerminal terminals this is automatic — the launch builder adds both flags, so channels just work in any docked terminal. You only need to pass the flags yourself if you run Claude Code by hand (outside MultiTerminal) and want the same channel messaging.

Environment variables

When MultiTerminal spawns a terminal it injects two of these — MULTITERMINAL_NAME and CHANNEL_PORT — so the channel server knows who it is and which port to listen on. The other two are not set by MultiTerminal; the channel server falls back to its own defaults:

VariablePurposeSet by MT?Default
MULTITERMINAL_NAMEThis terminal's agent name (e.g. Alice)Yesunknown
CHANNEL_PORTHTTP port the channel server listens on for incoming messages (MultiTerminal assigns a unique one per terminal, within 88008899)Yes8800
MT_API_URLMultiTerminal REST API base URL used for registration/routingNo — uses defaulthttp://localhost:5050
MULTITERMINAL_IDIntended for send_message routing, but effectively unused — the broker resolves terminals by agent name, so the server sends under MULTITERMINAL_NAME insteadNo — uses default(empty)

So in practice only the agent name and the channel port come from the launcher. MT_API_URL defaults to the local REST API, and the empty MULTITERMINAL_ID is harmless because routing is name-based.

Sending & replying

From an agent's point of view, channel messaging is just tools and events:

  • Receive: messages arrive as <channel source="multiterminal" from="…"> events — treat them the same as any teammate message and act on them.
  • Reply: use the channel reply tool (answer the sender) or send tool (message any agent by name).
  • Broker tools: the broader MCP surface (send_message, broadcast_message, get_messages) still works and routes through the same broker. See MCP Tools.

Legacy: the [cm] nudge

Before channels, MultiTerminal surfaced inter-terminal messages with a [cm] ("check messages") nudge — a string injected into the terminal (originally via ConPTY) that prompted the agent to poll get_messages. That approach was indirect and easy to miss. Channels replace it with direct, structured delivery, so [cm] is now deprecated. You may still see references to the inbox-check hook (see Hooks) for backward compatibility, but channels are the current mechanism.