Autonomous WireGuard mesh VPN with P2P, relay fallback, and MCP server
Project description
wire
Self-hosted WireGuard mesh VPN — like Tailscale, but you own everything.
pip install meshpop-wire
No external services. No accounts. No cloud dependency. Pure Python stdlib — installs and runs on any Linux or macOS machine.
Why wire?
The problem with existing VPN solutions
Traditional WireGuard is powerful but painful to manage at scale:
# Old way: manual WireGuard setup for every new node
# On each existing node — add the new peer manually:
wg set wg0 peer <NEW_PUBLIC_KEY> allowed-ips 10.0.0.5/32 endpoint NEW_IP:51820
# Repeat on EVERY other node
# When a node's IP changes — update EVERY other node again
# No central view of who's online
Tailscale / ZeroTier solve this — but you're dependent on their coordination servers. Your VPN stops working if their service goes down. Your traffic metadata goes through their infrastructure.
wire gives you the Tailscale experience with zero external dependencies:
| Manual WireGuard | Tailscale / ZeroTier | wire | |
|---|---|---|---|
| Setup per new node | Edit every existing node | One command | One command |
| Coordination server | You manage manually | Their cloud (required) | Your own server |
| IP changes | Re-configure everything | Auto | Auto |
| Works if vendor is down | Depends | ✗ No | ✓ Yes |
| Self-hosted | Manual config files | ✗ Enterprise only | ✓ Always |
| Dependencies | None | Account + agent | None |
| AI management (MCP) | ✗ | ✗ | ✓ |
Before wire
# Adding a new server to existing 10-node WireGuard mesh:
# 1. Generate keys on new server
wg genkey | tee private.key | wg pubkey > public.key
# 2. On EVERY existing node — add the new peer
for host in server1 server2 server3 server4 server5 \
server6 server7 server8 server9 server10; do
ssh root@$host "wg set wg0 peer <PUBKEY> \
allowed-ips 10.0.0.11/32 endpoint NEW_SERVER_IP:51820"
done
# 3. Add all existing peers to new server — one by one
# 4. When any server changes its IP — repeat
# Result: 10 servers × 10 config files = manually maintained mesh
After wire
# On new server — one command, done:
sudo wire up --server http://YOUR_COORD_SERVER:8787 --name newserver
# Every existing node automatically discovers and connects to newserver
# within 30 seconds. No manual config on existing nodes.
Architecture
┌────────────────────────────────────────┐
│ Coordination Server (one always-on) │
│ wire_server.py │
│ HTTP :8787 — peer registry │
│ UDP :8788 — STUN (NAT discovery) │
│ │
│ Knows: who exists, their public IPs │
│ Does NOT carry VPN traffic │
└──────────┬─────────────┬──────────────┘
│ │
registers / │ │ registers /
heartbeat │ │ heartbeat
every 30s │ │ every 30s
│ │
┌────────────▼──┐ ┌──▼────────────┐
│ Node A │◄─────►│ Node B │
│ wire_client │ │ wire_client │
│ 10.99.x.x │ │ 10.99.y.y │
└───────────────┘ └───────────────┘
direct P2P WireGuard tunnel
end-to-end encrypted
coordination server not involved
Three files, three roles:
| File | Role | Where it runs |
|---|---|---|
wire_server.py |
Coordination server + STUN | One always-on server |
wire_client.py |
VPN daemon + CLI | Every node |
wire_mcp_server.py |
MCP tools for AI agents | Machines with Claude |
Installation
No external dependencies
wire is pure Python standard library. No pip packages, no system packages beyond Python 3.8+.
pip install meshpop-wire
This installs:
wire— CLI commandwire-mcp— MCP server for AI integration
WireGuard kernel module (every node)
wire uses the WireGuard Linux kernel module for the actual VPN tunnels. Install the userspace tools:
# Debian / Ubuntu
apt install wireguard wireguard-tools
# RHEL / Fedora / CentOS
dnf install wireguard-tools
# Alpine
apk add wireguard-tools
# macOS
brew install wireguard-tools wireguard-go
Run wire install to check your platform and see the right command.
Step 1 — Start the coordination server (once, on one always-on machine)
# Copy to your server and run
scp wire_server.py user@COORD_SERVER:/opt/wire/
ssh user@COORD_SERVER "python3 /opt/wire/wire_server.py"
As a systemd service:
# /etc/systemd/system/wire-server.service
[Unit]
Description=wire coordination server
After=network.target
[Service]
ExecStart=/usr/bin/python3 /opt/wire/wire_server.py
Restart=always
[Install]
WantedBy=multi-user.target
systemctl enable --now wire-server
Step 2 — Join every node to the mesh
# First time on each node (saves config for future starts):
sudo wire up --server http://COORD_SERVER:8787 --name myserver
# After first run — just:
sudo wire up
That's it. The node generates its own WireGuard key pair, discovers its NAT-mapped port via the built-in STUN server, registers with the coordination server, and starts syncing peers every 30 seconds.
How It Works
VPN IP assignment — no DHCP, no conflicts
Each node's VPN IP is derived deterministically from its own identity:
node_id = sha256(hostname + mac_address)
vpn_ip = f"10.99.{node_id[0]}.{node_id[1]}" # e.g. 10.99.23.187
Same machine → same VPN IP every time. No central IP allocation. No conflicts.
NAT traversal — works behind home routers
wire has its own STUN server built in (no Google STUN or external service). Before starting WireGuard, each node probes the coordination server's UDP port to discover what IP and port its NAT router is using:
Node UDP :51820 → COORD_SERVER UDP :8788
Server sees: EXTERNAL_IP:MAPPED_PORT
Server replies: {"ip": "EXTERNAL_IP", "port": MAPPED_PORT}
Two nodes behind different NAT routers use WireGuard's PersistentKeepalive=25 to punch holes in both NATs simultaneously — tunnels work without any port forwarding.
Peer sync — automatic, every 30 seconds
The client daemon queries /peers from the coordination server every 30 seconds and updates WireGuard with any new or changed peers. When you add a new node, all existing nodes discover it within 30 seconds without any manual action.
CLI Reference
wire up — join the network
# First time (saves config):
sudo wire up --server http://COORD_SERVER:8787 --name NODENAME
# After first run:
sudo wire up
| Flag | Description |
|---|---|
--server / -s |
Coordination server URL |
--name / -n |
This node's name (default: hostname) |
--port / -p |
WireGuard listen port (default: 51820) |
wire status — view the whole mesh
wire status
wire status http://COORD_SERVER:8787
4 online / 1 offline / 5 total
● web1 10.99.23.187 203.0.113.10 5s ago
● web2 10.99.45.22 198.51.100.20 12s ago (this node)
● db1 10.99.87.3 192.0.2.30 8s ago
● storage1 10.99.200.5 203.0.113.40 3s ago
○ backup1 10.99.100.1 198.51.100.30 14m ago OFFLINE
wire status --json # machine-readable
wire peers — list all registered nodes
wire peers
wire ping — ping a node by name
wire ping web1
wire ping 10.99.23.187
Resolves names via the coordination server, then pings over the VPN.
wire down — leave the network
sudo wire down
Removes the WireGuard interface and stops the background daemon. The node appears offline on wire status within 5 minutes.
wire install — check WireGuard installation
wire install
Checks if WireGuard is installed and shows platform-specific instructions if not.
AI Management via MCP
wire ships with an MCP server that lets AI agents (Claude, etc.) manage your VPN mesh through natural language — from initial setup through ongoing monitoring.
Setup
pip install meshpop-wire # installs wire-mcp automatically
Add to Claude config (~/.claude/settings.json):
{
"mcpServers": {
"wire": { "command": "wire-mcp" }
}
}
What the AI can do
Once connected, you can ask Claude in plain language:
"Set up wire on this server and connect it to the mesh" "Which nodes are currently offline?" "Why can't web1 reach db1?" "Ping all nodes and tell me which have high latency" "Is the WireGuard module installed on this machine?"
The AI handles the commands, interprets the output, and explains what's happening.
MCP Tools Reference
| Tool | Description |
|---|---|
wire_status |
Full mesh view — all nodes, online/offline, VPN IPs, last seen |
wire_up |
Bring up the VPN tunnel on this machine |
wire_down |
Tear down the VPN tunnel |
wire_peers |
List all registered peers |
wire_ping |
Ping a peer by name or VPN IP |
wire_install |
Check WireGuard installation status |
wire_diagnose |
Full diagnostic: module loaded? interface up? server reachable? peers synced? |
wire_watchdog |
Check peer handshake ages, stale connections, service health |
Configuration
Config is written automatically by wire up. Locations:
| Context | Path |
|---|---|
| Root / system daemon | /etc/wire/config.json |
| Regular user | ~/.wire/config.json |
Example:
{
"server_url": "http://COORD_SERVER:8787",
"node_name": "web1",
"node_id": "a1b2c3d4...",
"vpn_ip": "10.99.23.187",
"listen_port": 51820,
"nat_port": 54321
}
Server-side environment variables:
| Variable | Default | Description |
|---|---|---|
WIRE_PORT |
8787 |
HTTP port (UDP STUN = this + 1) |
WIRE_VPN_SUBNET |
10.99 |
VPN IP prefix |
WIRE_STATE_FILE |
/etc/wire/state.json |
Peer state file |
File Reference
wire/
├── wire_server.py Coordination server + UDP STUN
│ Run on ONE always-on server
├── wire_client.py VPN daemon + CLI
│ Run on EVERY node in the mesh
└── wire_mcp_server.py MCP server for AI integration
/etc/wire/ (root) or ~/.wire/ (user)
├── config.json Node config — written by wire up
├── private.key WireGuard private key (chmod 600)
├── public.key WireGuard public key
└── state.json Server peer state (coordination server only)
Stack Position
wire is Layer 1 of the MeshPOP infrastructure stack:
Layer 3 mpop Fleet orchestration — monitor, manage, automate
Layer 2 vssh Fast authenticated transport — remote exec, file transfer
Layer 1 wire Encrypted mesh VPN — connects all nodes
Each layer is independently installable. wire works without vssh or mpop. vssh works with any VPN or direct network. mpop works with any transport.
Design Principles
No external services. The coordination server you run provides everything including STUN for NAT discovery. Nothing phones home.
No central traffic bottleneck. The coordination server handles only small JSON heartbeats. All VPN traffic flows directly between nodes, peer to peer.
No manual peer management. Add a node once. Every other node discovers it automatically within 30 seconds.
Deterministic IPs. Each machine's VPN IP is derived from its own hardware identity. No DHCP, no allocation table, no conflicts.
Offline tolerance. Nodes keep their WireGuard peers configured when the coordination server is unreachable. Established tunnels survive coordination server restarts.
License
MIT — MeshPOP
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file meshpop_wire-2.4.2.tar.gz.
File metadata
- Download URL: meshpop_wire-2.4.2.tar.gz
- Upload date:
- Size: 31.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.12.8
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
95eb534c8041212a03b1cf7342fc01dd4b20868f8bdc877c22328b07e87b126f
|
|
| MD5 |
3ab71e4a5a3f4ae2f39d1063e052670d
|
|
| BLAKE2b-256 |
337cb3517038c57bc13d9d3ef4271e5d891b33b1a91a8925a672aae30519769c
|
File details
Details for the file meshpop_wire-2.4.2-py3-none-any.whl.
File metadata
- Download URL: meshpop_wire-2.4.2-py3-none-any.whl
- Upload date:
- Size: 33.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.12.8
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6db1e7c451f57b514b6b0ca764655d990742effe4fd5481404e9f6944aa39ef0
|
|
| MD5 |
6f23a92de2e21880baba89928d5a7750
|
|
| BLAKE2b-256 |
549ffc62127b093ab959406f599896c573e37f7ff55d4402b83fef89f372c2f3
|