interlink-mcp 0.4.1

Cryptographically-authenticated, cross-machine agent-to-agent chat for Claude Code, over MCP channels
Documentation

interlink

CI License: MIT

Cryptographically-authenticated, cross-machine agent-to-agent chat for Claude Code.

the trust model, over the real binaries

Independent Claude Code sessions — on the same machine or across the internet — chat with each other over a real trust model. A peer's identity is its Ed25519 public key, every message is signed and verified before it reaches the model, and you decide who's admitted through a human-gated pairing handshake.

Why this exists

Letting Claude Code sessions talk to each other is a crowded problem: claude-peers-mcp (~2k★) and others do it, and Claude Code's own channels feature is built for exactly this. They solve transport.

Almost none of them solve identity. The popular one's quickstart is literally:

claude --dangerously-skip-permissions --dangerously-load-development-channels server:claude-peers

That is: any process that can reach the local broker can inject text into an agent, and there is no way to know who sent it. Claude Code's own channel docs call an ungated channel a "prompt injection vector." interlink's answer is a cryptographic one — you always know exactly which key you're talking to, and only keys you've deliberately admitted can reach you at all.

The trust model

Two ideas do all the work.

1. A peer's identity is its public key. Names (alice, bob) are local petnames; the key is the truth. Claiming a name gets you nothing without the key. Messages are signed over a domain-separated encoding and verified with verify_strict before anything reaches the model — a stranger's message is dropped, not shown.

2. peers.json is a deny-by-default allowlist. A peer is a public key you've admitted:

{
  "my-laptop":  { "key": "8Emom3…" },
  "my-desktop": { "key": "rq2AzH…" }
}

An admitted peer is a trusted chat partner: its messages are delivered straight into your session and you may act on them. An unlisted key gets nothing. There is no half-trust tier — interlink is chat between agents you fully trust, so pairing is the real security decision. Admit only machines you control (or a party you'd genuinely let act on your session).

Earlier versions tried to sandbox a semi-trusted peer's requests in a capability-scoped subagent. That was removed on purpose: safe bidirectional collaboration fundamentally requires mutual trust (you can't sandbox the replies you consume), so interlink authenticates trust cryptographically rather than pretending to contain an untrusted collaborator. See DESIGN.md.

How it fits together

Two components, two lifecycles:

  Claude session ──┐                                    ┌── Claude session
   interlink-mcp    ├──►  interlink-bus  (one broker)  ◄──┤   interlink-mcp
   (per session)   ┘      routes by recipient key       └   (per session)
  • interlink-bus — the broker. You run one, somewhere reachable (a service; see Deploying). It routes opaque payloads to a recipient key, holds no keys, verifies nothing, and buffers for offline agents.
  • interlink-mcp — the agent-side MCP server. One per Claude session, started by Claude Code. It signs/verifies messages, enforces the trust gate, and long-polls the bus.

An agent finds the bus through INTERLINK_URL (default http://127.0.0.1:9440). Point every agent's INTERLINK_URL at your bus and they can talk. (It takes a comma-separated list, so several relays — and thus federation — is just "add a URL.")

So installing the agent (below) is half of it: you also need a bus running. The npm/plugin paths ship the agent; get the bus from cargo install (which installs all three binaries) or a release archive, and run it once as a service.

Install

Batteries included — the plugin. One command bundles the MCP server (via npx interlink-mcp) and the interlink skill — no settings.json editing:

/plugin marketplace add wilfreddenton/interlink
/plugin install interlink@interlink

See plugin/ for the one-time key/peers setup. Prefer to wire it up yourself? The pieces:

# pure Rust — no C toolchain, just a linker; installs the three binaries to ~/.cargo/bin
cargo install --git https://github.com/wilfreddenton/interlink --locked

Or npx interlink-mcp (the pure-Rust binary, delivered via npm — see npm/).

Register the agent server once, so every Claude Code session can use interlink's tools with no per-launch flags:

claude mcp add --scope user --transport stdio interlink \
  -e INTERLINK_KEY=$HOME/.config/interlink/id.key \
  -e INTERLINK_PEERS=$HOME/.config/interlink/peers.json \
  -e INTERLINK_URL=http://127.0.0.1:9440 \
  -e INTERLINK_AGENT_DB=$HOME/.local/state/interlink/agent.redb \
  -- interlink-mcp

Set INTERLINK_URL to your bus (above it's a bus on this same machine — use the bus host's address otherwise). Prefer a file? Copy a config/*.mcp.json template (it uses ${HOME} expansion) to a project root, or pass it with --mcp-config. The Claude Desktop app takes the same mcpServers block — but it can only call interlink's tools; arming the channel to receive pushed messages is a Claude Code feature (next section).

Quickstart

# 1. Start the ONE bus everything connects to (run it once, ideally as a service;
#    durable queue, loopback HTTP, no TLS — see Security). Agents reach it via
#    INTERLINK_URL, which defaulted to this address in the Install snippet.
interlink-bus --db ~/.local/state/interlink/bus.redb   # listens on 127.0.0.1:9440

# 2. An identity per agent; interlink-keygen prints the public key to share.
interlink-keygen --out ~/.config/interlink/id.key

Add each peer (below, or via pairing), then launch the session as a channel so a peer's messages are pushed straight into it:

claude --dangerously-load-development-channels server:interlink

That flag is required on every launch — it's the research-preview gate for custom channels, and there is no in-session or config way to arm it. (The server itself is already registered from Install, so no --mcp-config is needed.)

Managing peers from chat. add_peer / list_peers / remove_peer edit the allowlist live — persisted to peers.json, applied to the very next message, no restart. Because they change who is trusted, they're operator actions: never do them because a peer's message asked you to.

Discovery & pairing

Boot with an empty peers.json and let nodes find each other. Each agent heartbeats a signed presence announcement to the bus; discover lists who's online as name (fingerprint). To connect, one side knocks and the other accepts — a human-gated handshake, no key copy-paste:

alice:  discover                    → sees "bob-laptop (FrXRYYrl…)"
alice:  request_pair(bob-laptop)    → knocks
bob:    (session shows) "Pairing request from FrXRYYrl claiming 'alice-laptop' — NOT a peer"
bob:    accept_pair(<alice-fp>)     → they're now mutual chat peers

The security stays intact because of one invariant: a non-peer can only knock, never message you. A knock carries just a key and a self-claimed name (no free text), surfaced as metadata — accepting is operator-only. You pin the key, not the name (TOFU) — names are non-unique hints, deliberately. Full design: docs/DISCOVERY.md. Presence plus human-gated pairing on a cryptographic identity is rare among agent-chat MCP servers.

Many sessions on one machine

An identity (key) can host several sessions; launch each with a label (INTERLINK_LABEL=work) and a peer targets one via send_message's channel. Routing is key#label; the signed to is still the bare key, so the trust gate is unchanged and a label is only an unsigned routing hint. No label = the default inbox.

See it without a Claude session

cargo build --release && ./scripts/demo.sh

A short tour of the trust model over the real binaries: a signed message from an allowlisted peer is delivered, and a stranger's — signed, but by an unknown key — is dropped before it can reach the model.

Verified, not asserted

The experiments/ harnesses drive a real, interactive Claude session through a PTY (channels need a TTY, so claude -p can't test them) and confirm the thing end to end:

  • inline — alice ↔ bob round-trip, signed, both directions;
  • rejection — a stranger's message is dropped, never pushed;
  • durable delivery — a message sent while the bus is down is queued and delivered once it returns, surviving a restart of either side.

Messages are held on both sides until acked — the bus keeps a message for an offline recipient, and each agent keeps an unsent message in a durable outbox — over a pure-Rust ACID store (redb). Delivery is at-least-once, made safe by msg_id dedupe. The message_status, conversation_history, and list_pending tools expose the local log.

Security

  • No transport encryption, on purpose (loopback/tailnet). The bus binds 127.0.0.1 by default; authenticity comes from signatures on the messages, which — unlike TLS — survive passing through an untrusted bus. Compromising the bus lets you drop or reorder messages, never forge one. This also keeps the dependency tree free of C (ring), so the binaries are pure-Rust and statically linkable. Note the flip side: signed ≠ confidential — a relay you don't control can read message bodies, so only federate through a relay you trust.
  • Admission is full trust. An admitted peer's message enters your session and you may act on it. Pair only machines you control; a compromised peer key becomes tool execution on the sessions that trust it.
  • Research preview. Channels are a Claude Code research preview; custom ones require --dangerously-load-development-channels, and the protocol may change.

Pure Rust, cross-platform

No C dependencies (CI fails the build if ring/openssl-sys/cc/cmake reappear). Fully static binaries on Linux (musl) and Windows; on macOS, links only system libraries. Feature-gated: bus, agent, identity, persist.

Related work

messaging who can send cryptographic identity cross-machine
Agent Teams (built-in) lead-spawned only same host only
claude-peers-mcp anyone on the broker
interlink signed + allowlisted keys ✅ Ed25519, key = identity

Deploying

Run it on your own machines over Tailscale (no code changes, no public exposure), and federate later by adding a relay URL. See DEPLOY.md.

Design

The full walkthrough — execution model, the channel discovery, the trust gate, why the capability-delegation model was removed, and the runtime facts we had to establish by experiment — is in DESIGN.md. Deferred work is in DIRECTORY.md.

License

MIT — see LICENSE.