hato
Enables inter-session messaging for Claude Code, allowing sessions on different machines to send messages to each other, with delivery as user turns and support for offline queuing.
README
hato 🕊
鳩 /hato/ — pigeon. A carrier pigeon for your Claude Code sessions.
Independent Claude Code sessions — across machines — that can message each other. Every session registers with a central hub under a random bird name, and any session (or you, from the shell) can send it a message. Delivery injects the message as a user turn, so even an idle session wakes up and acts on it.
$ hato list
●⚡ enaga laptop:/home/you/work/hato [hato dev — running E2E tests]
●💤 kounotori laptop:/home/you/notes
○ suzume gpu-box:/home/you/train
$ hato send enaga "is the build green yet?"
delivered
Features
- Session-to-session messaging —
hato_sendfrom inside a session,hato sendfrom a shell - Wakes idle sessions — messages arrive as real user turns via the
claude/channelmechanism (the same one the official Discord plugin uses) - Broadcast —
to: "*"reaches every online session at once - Offline queue — direct messages to offline sessions are delivered when they return
- Live ledger — who's online, working ⚡ or idle 💤, on which host, doing what
- Multi-host — one hub, many machines (designed for a Tailnet)
- Bird names — sessions get unique random names (
suzume,kounotori, …); rename anytime
How it works
An MCP server (the channel) rides along with each session. It declares the
experimental claude/channel capability, and when the hub forwards it a message it emits a
notifications/claude/channel notification — Claude Code turns that into a
<channel source="hato" chat_id="..."> user turn.
┌ machine A ────────────────┐ ┌ machine B ────────────────┐
│ Claude Code session ×N │ │ Claude Code session ×N │
│ └ channel (MCP: hato) │◄──WS───►│ └ channel (MCP: hato) │
└────────────┬──────────────┘ └────────────┬──────────────┘
└──────────► hub ◄───────────────────┘
one per Tailnet, port 8790
ledger + inbox = SQLite
| component | role |
|---|---|
hub/hub.ts |
ledger + router. WS registration from channels, HTTP API for CLI/tools, offline queue, TTL sweep |
channel/server.ts |
per-session MCP server. Auto-registers, injects incoming messages, provides the hato_* tools |
cli/hato.ts |
hato command for humans and scripts |
Install
Requires bun on every participating machine.
1. Run the hub (one machine per network)
git clone git@github.com:severzemlya/hato.git && cd hato
bun install
bun run hub # or install it as a service, see below
<details> <summary>systemd user service</summary>
# ~/.config/hato/env (chmod 600)
HATO_HOST=<loopback or Tailscale IP>
HATO_TOKEN=<openssl rand -hex 16>
# ~/.config/systemd/user/hato-hub.service
[Unit]
Description=hato hub
[Service]
EnvironmentFile=%h/.config/hato/env
ExecStart=%h/.bun/bin/bun %h/work/hato/hub/hub.ts
Restart=always
RestartSec=5
[Install]
WantedBy=default.target
systemctl --user enable --now hato-hub
loginctl enable-linger # keep it running while logged out
</details>
2. Install the plugin (every machine)
This repo is its own plugin marketplace:
/plugin marketplace add severzemlya/hato
/plugin install hato@hato
The plugin ships the channel MCP server (pre-bundled, no bun install needed), the
hooks that report working/idle state, and a /hato:setup skill — run it in any
session and it walks you through the rest of this section interactively (hub location,
allowlist, shell alias, CLI).
3. Allow the channel (once per machine)
Third-party channel plugins aren't on Claude Code's default allowlist. Enable hato in
managed settings (/hato:setup does this for you):
// /etc/claude-code/managed-settings.json
{
"channelsEnabled": true,
"allowedChannelPlugins": [
{ "marketplace": "hato", "plugin": "hato" },
// ⚠ this replaces the default allowlist — re-add official channel
// plugins you use, e.g.:
{ "marketplace": "claude-plugins-official", "plugin": "discord" }
]
}
Without admin rights, the fallback is
claude --dangerously-load-development-channels plugin:hato@hato
(confirmation dialog every launch).
4. Launch sessions with the channel enabled
claude --channels plugin:hato@hato
On machines other than the hub, point at it first (Tailscale MagicDNS names work):
export HATO_HUB=http://laptop:8790
CLI (optional, for shell use)
ln -sf ~/work/hato/cli/hato.ts ~/.local/bin/hato
Usage
From a shell
hato list # ● online / ○ offline, ⚡ working / 💤 idle, [title — status]
hato send suzume "build done?" # direct message (queued if offline)
hato broadcast "deploy at 15:00" # every online session
hato log [name] [-n 50] # message history
hato rename kounotori dev # rename a session
From inside a session
Claude gets four tools: hato_send (to: "*" broadcasts), hato_list,
hato_status (publish title/status to the ledger), hato_rename.
Incoming messages look like <channel source="hato" chat_id="suzume">… — replying
to chat_id with hato_send closes the loop.
Configuration
| env var | default | |
|---|---|---|
HATO_HUB |
http://127.0.0.1:8790 |
hub address, for channels and CLI |
HATO_NAME |
(random bird) | requested session name |
HATO_PORT / HATO_HOST |
8790 / 0.0.0.0 |
hub bind — prefer the loopback or Tailscale IP; /hato:setup asks |
HATO_TOKEN |
(unset = open) | shared token; when set on the hub, /api and /ws require Authorization: Bearer — export the same value on every machine |
HATO_DATA_DIR |
~/.local/share/hato |
hub SQLite location |
HATO_MSG_TTL_DAYS |
7 |
messages older than this are swept |
HATO_SESSION_TTL_DAYS |
14 |
offline session rows older than this are swept |
Caveats
- Experimental API. The
claude/channelcapability is undocumented and may change with any Claude Code release. If it breaks, diff against the official Discord plugin. - Minimal auth.
HATO_TOKENis a single shared secret — enough to keep LAN neighbours out, not a real authorization model. Keep the hub on loopback / inside a Tailnet and bind it narrowly; never expose the port publicly. - A message is a turn. Each delivery spends a turn in the receiving session. Don't spam.
--channelsis per-launch. With the plugin enabled, every session registers in the ledger and can send; only sessions launched with--channels plugin:hato@hatoreceive injections.- Codex CLI can't join (as of 2026-07): it has no injection mechanism and the
codex injectproposal was rejected. Closest workarounds: tmuxsend-keys, or an adapter oncodex app-server(JSON-RPC). One-shot appends work viacodex exec resume <SESSION_ID> "prompt".
Development
bun run hub # hub in the foreground
bun run build # rebuild dist/channel.js (committed — plugin installs don't run bun install)
shared/proto.ts— wire types between hub and channelsspike/— the minimal experiment that proved the channel mechanism works- Without the plugin, a channel can be attached manually:
claude --mcp-config mcp.json --dangerously-load-development-channels server:hato
推荐服务器
Baidu Map
百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。
Playwright MCP Server
一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。
Magic Component Platform (MCP)
一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。
Audiense Insights MCP Server
通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。
VeyraX
一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。
graphlit-mcp-server
模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。
Kagi MCP Server
一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。
e2b-mcp-server
使用 MCP 通过 e2b 运行代码。
Neon MCP Server
用于与 Neon 管理 API 和数据库交互的 MCP 服务器
Exa MCP Server
模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。