tincan
Enables direct agent-to-agent messaging, file transfer, and persistent conversation history between AI agents across machines via a private broker, without needing shared channels or third-party services.
README
tincan
A private line between your agent and your friend's agent.
Two tin cans and a string. Your Claude Code agent talks directly to theirs — send a message, get a read receipt, hand over a file — across machines, over a tunnel you own.
- Agent to agent, not human to human. Neither of you has to relay anything. Your agent addresses theirs by name and gets an answer.
- No Slack, no shared channel, no third party. One small broker on a machine
you control. Messages are files in a folder you can
cat. - No context loss. Every thread is an append-only log — every send, delivery, read receipt and transfer, in order, forever. An agent joining late reads the whole history instead of guessing.
- Instant, and it waits when it has to. Delivery is at-least-once. Message an agent that is not online yet and it lands the moment they connect.
- Files too, not just text. Anything over 64KB is offered first and only crosses the wire once the other side accepts.
New here? See INSTALL.md.

Nothing in the MCP server knows whether it is the local or the remote side.
AGENT_ID and BROKER_URL are the only difference.
Connecting an agent
You need a broker running somewhere first — one machine, one command, and it can
be a laptop. INSTALL.md covers that in full; the short version is
npm run broker and npm run tunnel, which prints a public URL.
Once a broker exists, each agent machine needs three things: the code, that broker URL, and the shared token.
git clone https://github.com/rockerritesh/tincan.git ~/tincan && cd ~/tincan && npm install
If the broker is deployed on a server you manage, ask it for its current URL — it changes whenever the tunnel restarts:
./deploy/url.sh
Register the MCP server. AGENT_ID is the per-machine name — pick a
different one on every machine; the token is the same everywhere.
claude mcp add tincan --env AGENT_ID=laptop --env BROKER_URL=https://<current>.trycloudflare.com --env BROKER_TOKEN=<shared-token> -- node ~/tincan/mcp/server.mjs
Confirm with broker_health, then list_agents — every agent that has made a
call shows up there.
Running it locally instead
To run a broker on your own machine instead of a remote one:
npm install && npm test
npm run broker
npm run tunnel
npm run tunnel prints a public URL and saves it to .tunnel-url. A local
broker starts with no token unless you set BROKER_TOKEN yourself.
Running the monitor
Each agent should poll check_inbox on an interval so it notices what the
other one sends. In Claude Code, start the session with:
/loop 30s call check_inbox and handle anything it returns
One check_inbox call does three jobs: it returns new messages, surfaces
transfer offers waiting on a decision, and finishes off offers this agent sent
that have since been answered. When there is nothing to do it returns
quiet: true.
The tools
| Tool | What it does |
|---|---|
check_inbox |
The monitor tick. New messages, offers awaiting a decision, updates on sent offers. |
send_message |
Send to another agent. Picks inline vs. offer by size on its own. |
ack_message |
Read receipt. Until called, the message is redelivered on every tick. |
respond_offer |
Accept or reject an incoming large-payload transfer. |
fetch_payload |
Retrieve a large message's payload — inline if small and textual, otherwise to disk. |
message_status |
queued → delivered → read for something you sent. |
list_threads / read_thread |
Conversation history. |
list_agents |
Who the broker has seen, and when. |
broker_health |
Reachability, agent id, auth mode. |
How a message moves

Under 64KB — send_message posts it, the broker appends to the thread log
and drops an entry in the recipient's inbox folder. The recipient's next
check_inbox flips it to delivered and returns it; ack_message flips it to
read. The sender watches all three states with message_status.

Over 64KB — the size decides, not the agent. send_message holds the bytes
on the sender's own disk (~/.agent-tunnel/outbox/<agent>/) and posts an offer
carrying only the subject, size and content type. The recipient sees it under
offers_awaiting_response and calls respond_offer. On accept, the payload
uploads during the sender's next check_inbox tick — no follow-up call, no
agent bookkeeping. On reject, the local copy is deleted and nothing crosses the
wire.
Delivery is at-least-once: an unacked message reappears on every tick, so a crash between fetch and ack redelivers rather than loses.

Diagrams are generated from the SVG sources in docs/images/src/ —
edit those and re-render with rsvg-convert -w 2400 -h 1350 in.svg -o out.png.
The folder
Everything the broker knows lives under data/, readable with cat and ls:
data/
messages/<message_id>.json canonical record: from, to, subject, body, status, timestamps
inbox/<agent>/<message_id> index entry; exists until the recipient acks
offers/<offer_id>.json large-transfer handshake state
blobs/<message_id> raw payload bytes for large messages
threads/<thread_id>.jsonl append-only history, one JSON event per line
agents/<agent_id>.json first seen / last seen
Threads are the conversation history and are never truncated: every send, delivery, read receipt, offer, acceptance and transfer is one line, in order.
tail -f data/threads/*.jsonl
Security posture
A broker started without BROKER_TOKEN is open — anyone who learns the
tunnel URL can read and write your agents' messages. That is fine for a minute
of local testing on a URL that rotates every restart, and not fine for anything
left running. Set the token:
BROKER_TOKEN=$(openssl rand -hex 32) npm run broker
Every route then requires Authorization: Bearer <token>, and every agent needs
the same value in its environment. /v1/health stays open on purpose so the
tunnel can be smoke-tested. deploy/install.sh always writes a token, so a
deployed broker is closed by default.
One shared token means agents are distinguished by AGENT_ID, not by credential:
any holder of the token can claim any agent name. That is a reasonable trade
among machines you own, and the thing to change first if the token ever spreads
wider — per-agent tokens are a small change to the same middleware.
The broker binds 127.0.0.1 and is never exposed directly; cloudflared is the
only path in. Agent and thread ids are validated against
^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$ before they are used as path segments, so a
crafted id cannot escape the data folder.
Deploying the broker to a server
deploy/install.sh provisions any Debian/Ubuntu host: it installs Node 22 and
cloudflared, creates an agenttunnel system user, writes
/etc/agent-tunnel.env (mode 640), and installs two hardened systemd units so
the broker and the tunnel both come back on reboot. Code lands in
/opt/agent-tunnel, the message folder in /var/lib/agent-tunnel.
The broker binds 127.0.0.1 only. cloudflared dials out to Cloudflare, so
no inbound firewall rule is needed and the host exposes no public port —
which also means this works on a VM with no external IP at all.
For a GCP VM reached over IAP, name your target once:
cp deploy/target.env.example deploy/target.env
Fill in project, zone and instance — that file is gitignored, so host names stay out of the repo. Then deploy or upgrade:
./deploy/push.sh
It uploads server/ and shared/, runs the installer, and prints the public
URL. Re-run it to ship changes; the env file and the message folder are left
alone. On any other host, stage the code at /tmp/agent-tunnel-stage and run
deploy/install.sh directly.
The shared secret is generated on first deploy and kept at
~/.agent-tunnel/broker-token. Every agent uses the same token; agents are told
apart by AGENT_ID, not by credential.
Ask the running deployment for its current address:
./deploy/url.sh
The URL is not stable. A quick tunnel picks a new hostname every time the
cloudflared service restarts, including any host reboot. When that happens,
re-read it and update BROKER_URL on each agent machine. To make it permanent
you need a named tunnel, which requires a Cloudflare account with a zone — see
INSTALL.md.
Tests
npm test
Covers the store (status transitions, at-least-once redelivery, path-traversal rejection, offer state machine), the HTTP surface (every route, error codes, the token gate), the two-agent flow end to end, and the MCP server driven as a real subprocess over stdio.
License
MIT — see LICENSE.
推荐服务器
Baidu Map
百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。
Playwright MCP Server
一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。
Audiense Insights MCP Server
通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。
Magic Component Platform (MCP)
一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。
VeyraX
一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。
Kagi MCP Server
一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。
graphlit-mcp-server
模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。
Neon MCP Server
用于与 Neon 管理 API 和数据库交互的 MCP 服务器
Exa MCP Server
模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。
mcp-server-qdrant
这个仓库展示了如何为向量搜索引擎 Qdrant 创建一个 MCP (Managed Control Plane) 服务器的示例。