mcpmesh 0.28.0

Share MCP servers with people you trust β€” peer to peer, default-deny, no accounts
Documentation

🀝 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:

cargo install mcpmesh    # on both machines (macOS, Linux, or Windows)

# πŸ–₯️  you β€” share a folder of notes as an MCP server, under a name you pick:
mcpmesh serve notes -- npx -y @modelcontextprotocol/server-filesystem ~/notes
mcpmesh invite notes     # prints a one-time mcpmesh-invite:… line β€” send it to your friend

# πŸ“© friend β€” redeem the invite:
mcpmesh pair mcpmesh-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-node boots 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=$HOME/.mcpmesh-demo-friend; mkdir -p $FH/notes $FH/run && echo hi > $FH/notes/hello.md

# the "friend" serves a folder and mints a real invite under the scratch identity…
HOME=$FH XDG_RUNTIME_DIR=$FH/run mcpmesh serve notes -- npx -y @modelcontextprotocol/server-filesystem $FH/notes
HOME=$FH XDG_RUNTIME_DIR=$FH/run mcpmesh invite notes

# …and YOUR identity redeems it, exactly as a real friend would:
mcpmesh pair mcpmesh-invite:…

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, and mcpmesh pair --remove cuts 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 N for 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:

brew tap counterpunchtech/mcpmesh https://github.com/counterpunchtech/mcpmesh
brew install counterpunchtech/mcpmesh/mcpmesh

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):
alice$ mcpmesh serve notes -- npx -y @modelcontextprotocol/server-filesystem ~/notes

alice$ mcpmesh invite notes
One-time invite (expires in 24h). Share it out-of-band:
  mcpmesh-invite:…
Whoever redeems it can access: notes

Next: send them that line over any channel. They redeem it with `mcpmesh pair <line>`,
which prints a short safety code β€” run `mcpmesh status` to see yours and confirm the two
match, out loud. Same words = the pairing is authentic.

πŸ’‘ 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

bob$ mcpmesh pair mcpmesh-invite:…
Paired with alice β€” code: tango-fig-cabbage
Next: confirm this code matches what alice sees, out loud (they see it under `mcpmesh status`).
Same words = the pairing is authentic.

You can now use: alice/notes

Tools from alice run on alice's machine. Treat their output as you would anything else alice sends you.

To use in Claude Code, run:
  claude mcp add alice-notes -- mcpmesh connect alice/notes

To use in Claude Desktop, add this under "mcpServers" in
  ~/Library/Application Support/Claude/claude_desktop_config.json
then quit and restart Claude Desktop:
  "alice-notes": {"command": "mcpmesh", "args": ["connect", "alice/notes"]}

Any other MCP client: add a stdio server with the command `mcpmesh connect alice/notes`.

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$ mcpmesh status
…
recent pairings (confirm the code with the other side):
  bob Β· code: tango-fig-cabbage Β· just now

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:

bob$ claude mcp add alice-notes -- mcpmesh connect alice/notes

…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 project instead 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:

bob$   mcpmesh serve code -- npx -y @modelcontextprotocol/server-filesystem ~/projects
bob$   mcpmesh invite code

alice$ mcpmesh pair mcpmesh-invite:…       # Bob's invite this time
Paired with bob β€” code: quartz-melon-drift
Next: confirm this code matches what bob sees, out loud …

You can now use: bob/code
…
alice$ claude mcp add bob-code -- mcpmesh connect bob/code   # the command `pair` just printed

βž• 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

mcpmesh status               # what you serve, to whom; which peers are online (+ RTT); recent pairings; next steps
mcpmesh use alice/notes      # the exact steps to mount a peer's service in your AI client
mcpmesh doctor               # local, read-only health check of this machine's setup
mcpmesh pair --remove alice  # unpair: alice loses access to YOUR services from now on

πŸ“‘ 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-node cross-compiles for aarch64-apple-ios and aarch64-linux-android, and CI keeps it that way β€” a cargo check job 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.redb takes 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 / runtime split 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.