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.
On This Page
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
- Each terminal's channel server starts up and registers its listen port with the MultiTerminal broker (
POST /api/messaging/registerwith its agent name +channelPort). - An agent calls
send_message(or a skill does). The broker looks up the recipient's registeredchannelPort. - The broker delivers the message over HTTP to that port.
- The recipient's channel server pushes it into the Claude Code session as a
<channel>event. - The receiving agent reads it and replies with the channel
reply/sendtools, 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 (8800–8899) 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-dirloads the MultiTerminal plugin (hooks, skills, agents, and the channel server) for that session.--dangerously-load-development-channels plugin:multiterminal@inlineauthorizes the plugin's inline channel server. (Plugins loaded via--plugin-dirare sourced asname@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:
| Variable | Purpose | Set by MT? | Default |
|---|---|---|---|
MULTITERMINAL_NAME | This terminal's agent name (e.g. Alice) | Yes | unknown |
CHANNEL_PORT | HTTP port the channel server listens on for incoming messages (MultiTerminal assigns a unique one per terminal, within 8800–8899) | Yes | 8800 |
MT_API_URL | MultiTerminal REST API base URL used for registration/routing | No — uses default | http://localhost:5050 |
MULTITERMINAL_ID | Intended for send_message routing, but effectively unused — the broker resolves terminals by agent name, so the server sends under MULTITERMINAL_NAME instead | No — 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
replytool (answer the sender) orsendtool (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.