π€ What is this?
mcpmesh lets your AI use your friends' tools.
Got an MCP server running on your machine β your notes, your code search, your scripts? Share it with a specific person, and their AI client (Claude Desktop, Claude Code, or anything that speaks MCP) mounts it as if it were local. πͺ
- π« No hosting. The server runs on your machine. Nothing is uploaded anywhere.
- π No middleman. An end-to-end-encrypted connection straight between your two machines.
- π No accounts, no OAuth, nothing to sign up for.
π¬ New here? The tech presentation is a two-minute visual tour of what mcpmesh is and how it works.
π Quick start
One minute, two machines β call them you and friend:
# π₯οΈ you β share a folder of notes as an MCP server, under a name you pick:
# π© friend β redeem the invite:
That's it. β
pair prints the exact next steps β the safety code to confirm, and the one command
that wires the share into Claude Code (claude mcp add you-notes -- mcpmesh connect you/notes) or
the entry to paste into Claude Desktop. (Run mcpmesh use you/notes any time to see them again.)
Both of you also see the same short code, like tango-fig-cabbage. π£οΈ Read it to each other β
matching words mean the pairing is authentic. Now your friend's AI can search your notes over an
encrypted peer-to-peer link, and it works both ways whenever they want to share back.
π€ Driving mcpmesh from a script or an AI agent? See AGENTS.md β every verb takes
--json, errors carry a machine-branchable code, and nothing ever prompts.
π¦ Building in Rust? Embed a full node in-process β no daemon, no sidecar:
mcpmesh-nodeboots your app's own mesh identity in a few lines, speaking the same control API the daemon serves.
π§ͺ Try it alone
Only one machine? Fake the friend. A daemon's whole world lives under HOME +
XDG_RUNTIME_DIR, so a scratch HOME is a complete second identity β and the two pair on one
machine exactly as two machines would (macOS/Linux):
# A real (symlink-free) path β NOT /tmp or $TMPDIR: on macOS both resolve through a symlink
# (/tmp β /private/tmp), and the filesystem MCP server then rejects every path you give it.
FH=/.mcpmesh-demo-friend; &&
# the "friend" serves a folder and mints a real invite under the scratch identityβ¦
HOME= XDG_RUNTIME_DIR=/run
HOME= XDG_RUNTIME_DIR=/run
# β¦and YOUR identity redeems it, exactly as a real friend would:
From pair onward you're in the two-machine flow β safety code, mount, connect. Nothing is
mocked: real keys, a real one-time invite, a real encrypted session. The guided version with a
live end-to-end proof and cleanup is docs/loopback.md, or just run
docs/loopback.sh.
π‘οΈ Why it's safe to try
- π Default-deny. Nothing is shared until you run
serve+invite. Every grant names exactly who gets access, andmcpmesh pair --removecuts a peer off instantly. - π΅οΈ No middleman. Traffic is end-to-end encrypted, machine to machine (iroh/QUIC with NAT hole-punching). No account to create, no server of ours in the trust path.
- β Tamper-evident invites. Invites are expiring and single-use by default (
--uses Nfor a bounded multi-person link), and the spoken safety code catches a tampered invite before anything is shared.
π₯ The full walkthrough: Alice and Bob
Two people, two machines. Alice shares an MCP server with Bob; then Bob shares one back. Install on
both machines first β cargo install mcpmesh, or via Homebrew on macOS/Linux:
1οΈβ£ Alice serves a server and invites Bob
mcpmesh doesn't know or care what a server does β you register any MCP server under a name you pick, and that name is how peers refer to it:
# Everything after `--` is just the command that runs the server;
# "notes" is Alice's alias for it (any name works):
)
π‘ Every command tells you what to do next, so you can follow the whole flow without the docs open.
Alice sends that mcpmesh-invite:β¦ line to Bob over any channel he can paste from β chat,
email, a shared doc. (It's a long line, a couple of KB of text β not one to copy by hand.) The
channel doesn't need to be secure: tampering with the invite makes the next step's code mismatch,
and the invite is expiring and single-use by default.
2οΈβ£ Bob redeems the invite
)
One redemption pairs the two machines both ways π β each side now knows and trusts the other's
device. Access stays one-way until granted: right now Bob can reach alice/notes, and Alice can
reach nothing of Bob's.
βΉοΈ "alice" here is Bob's nickname for Alice β suggested by the invite, local to Bob. Names in mcpmesh are always your names for your peers, never global identities.
3οΈβ£ Both sides confirm the code π£οΈ
Alice checks her side:
)
Alice and Bob compare codes out loud (or over any channel they already trust). Same words on both screens = the pairing is authentic. β A mismatch means someone tampered with the invite in transit β unpair and start over. β οΈ Do this before using the service; it's the whole ceremony.
4οΈβ£ Bob mounts the service in his AI client
Bob follows the instruction pair already printed β one command for Claude Code:
β¦or the pasted entry for Claude Desktop. (Lost the block? mcpmesh use alice/notes prints it
again.) Alice's notes server now appears as a normal local MCP server, and Bob's AI can search it β
every request travels the encrypted P2P session straight to Alice's machine. π
π Run that command as printed, with no
--scope: it lands in Claude Code's default local scope and connects right away. Adding--scope projectinstead writes a checked-in.mcp.json, which Claude Code holds at pending approval until someone approves it interactively β a sensible guard against servers arriving through a shared repo, but it looks like the mount silently failed if you weren't expecting it.
mcpmesh prints these steps rather than editing your AI client's config for you: it's your config, and a line you pasted yourself is one you can find and remove later.
5οΈβ£ Bob shares something back
The same three verbs in the other direction β pairing is already mutual, so Bob just serves, invites, and Alice redeems:
β Grants accumulate β this second pairing adds bob/code to what Alice may dial without touching
what Bob already had, and nobody's chosen names for their peers change. Repeat with more people and
more services; every grant is explicit and per-service.
6οΈβ£ See and undo everything
π‘ status shows a reachability line per paired peer β online or offline, with a round-trip time
β so you can see who's actually up before you dial. It's advisory and on-demand (a peer that's off
just reads offline; it never blocks a dial), and it works both ways once you're paired.
π§ status also closes with a next steps block β the exact command for whatever this machine can
do from where it is (share something, invite someone to a service nobody can reach yet, redeem an
invite, or mount a peer's service). It's silent when there's nothing to nudge.
π’ For teams: roster mode
Pairing is person-to-person. Roster mode layers team management on the same machinery: an operator
creates an org (mcpmesh org create), members join with mcpmesh join, and membership lives in a
signed roster file you can host on any static web host β the signature is the trust boundary, so
the host, the network, and the gossip layer are all untrusted transport. Revoking a member updates
the roster and severs their live sessions everywhere. The full ceremony (including the fingerprint
confirmations that make it safe) is in the π operator runbook.
π§© What's underneath
| Piece | What it is |
|---|---|
mcpmesh (CLI + daemon) |
The binary: porcelain verbs above, plus a per-user daemon it auto-starts |
mcpmesh-net |
The transport kernel: iroh/QUIC sessions, accept-time trust gating |
mcpmesh-trust |
Keys, signed rosters, deviceβuser bindings β no network code |
mcpmesh-codec |
The one NDJSON frame codec both ends of every wire share |
mcpmesh-local-api |
The local control API (mcpmesh-local/1) clients and plugins build on |
πͺͺ Identity is self-sovereign: devices hold keys that never leave the machine, people are verified
deviceβuser bindings, and the names you see are nicknames you chose. By default connections
bootstrap via iroh's public relays; relay_mode = "custom" / discovery_mode = "custom" in the
config let you self-host both, and "disabled" runs fully hermetic
(LAN/localhost only). mcpmesh doctor validates whatever combination you configure.
βοΈ The trust boundary, in one line: mcpmesh authenticates who you're talking to and encrypts the pipe β it does not vet what a peer's MCP server says. Treat tool output from a peer like any content from that person.
π οΈ Building on mcpmesh
To share a tool, you write an ordinary MCP server β the same
artifact you'd hand to Claude Desktop β and serve it. That works in any language with an MCP SDK,
with no mcpmesh code: the mesh hands your server the cryptographically verified caller identity
per call (an env var for a spawned server, an initialize _meta field for a warm one), so you can
scope what you disclose to who is asking.
To drive the mesh β a GUI, a launcher, a plugin daemon β talk to the per-user daemon over
mcpmesh-local/1: newline-delimited JSON over a same-user local endpoint (a Unix socket on
macOS/Linux, a named pipe on Windows), simple enough to implement in any language. The
mcpmesh-local/1 protocol spec documents the
framing, the handshake, every method, and the identity contract. Rust clients can depend on the
mcpmesh-local-api crate directly instead. Runnable
samples of both β the typed Rust client and a dependency-free ~60-line Python client proving the
any-language claim β live in local-api/examples/.
πΊ For a live view of the mesh, the daemon exposes an event stream (subscribe): an initial
snapshot of active sessions and peer reachability, then per-event telemetry β sessions opening and
closing, per-request latency and byte counts, trust changes β as it happens, enough to render the
mesh in real time. mcpmesh internal watch is a thin reference consumer you can run to watch the
stream in a terminal; the protocol spec has the frame
shapes.
π» Platform support
Supported today: macOS, Linux, and Windows 10+ (x86_64 / aarch64). Rust β₯ 1.95 to build from source. On Windows, the local control plane uses a per-user named pipe with an owner-only DACL instead of a Unix socket β same-user-only access, enforced by the kernel either way. These are the three CI runs the full test suite gates on.
iOS and Android are intended targets, on the roadmap. If you are an embedder deciding whether a phone client is possible: it is not an architectural dead end, and you can plan around it. Be precise about what that does and does not mean today:
- β
mcpmesh-nodecross-compiles foraarch64-apple-iosandaarch64-linux-android, and CI keeps it that way β acargo checkjob on both, so a dependency bump that breaks cross-compilation is caught here rather than by whoever first attempts a port. - β No runtime behaviour on mobile is validated. Compiling is not working, and the open
questions are the load-bearing ones:
- Backgrounding. iroh holds long-lived QUIC sessions and a relay connection. iOS revokes sockets and freezes the process; Android Doze exists specifically to kill this pattern.
- Process termination.
state.redbtakes an exclusive file lock. Neither OS promises a clean shutdown, and we have not established what a hard kill leaves behind. - Discovery. pkarr publish and DNS lookup assume a socket that outlives a foreground app.
- Paths. The
config/data/state/runtimesplit has no mapping onto an iOS app container, including which parts must be excluded from backup β a bearer-credential file (invites.json) and the device key both live under it.
So: build against it, but do not ship a phone client assuming a background mesh works. When those questions are answered they will be answered here.
π¦ Status
Pre-release. The wire protocol and config format can still change without migration paths. π Security reports: knotanotsea@protonmail.com.
π License: MIT OR Apache-2.0.