Give AI agents public HTTPS/TCP/UDP URLs for any localhost service. Open source, self-hostable.
io.github.joaoh82/rustunnel MCP Server
This MCP server exposes public HTTPS/TCP/UDP URLs for services running on localhost. It is open source and self-hostable, functioning as a tunneling solution for AI agents that need externally reachable endpoints. The repository is identified as io.github.joaoh82/rustunnel and is described as a reverse-proxy/tunnel alternative.
π οΈ Key Features
Public HTTPS/TCP/UDP URL publishing
Reverse-proxy / gateway tunneling
Self-hostable, open-source implementation
π Use Cases
Exposing localhost services to AI agents
Providing reachable webhook-style endpoints
Alternatives to paid tunnel services
β‘ Developer Benefits
Supports multiple protocols: HTTPS, TCP, UDP
Self-hosted deployment option
Open-source licensing (AGPLv3)
β οΈ Limitations
Limited information provided beyond URL tunneling for localhost services
No additional tool or capability details included in the available source excerpt
The open-source tunnel that scales with you. Don't pay for idle time. Secure, Rust-fast, and Pay-as-you-go.
Expose local services through a public server over encrypted WebSocket connections with TLS termination, HTTP/TCP proxying, a live dashboard, Prometheus metrics, and audit logging.
You can self-host or use our managed service.
Using an AI agent? rustunnel ships an MCP server β one-click setup for Cursor (then add your API token):
You can use rustunnel without running your own server. We operate a global fleet of public edge servers that you can connect to immediately.
Available regions
Region ID
Server
Location
Control plane
Status
eu
eu.edge.rustunnel.com
Helsinki, FI
:4040
Live
us
us.edge.rustunnel.com
Hillsboro, OR
:4040
Live
ap
ap.edge.rustunnel.com
Singapore
:4040
Live
The client auto-selects the nearest region by default. Use --region <id> to connect to a specific one. The legacy address edge.rustunnel.com is a CNAME to eu.edge.rustunnel.com and will continue to work for backward compatibility.
Getting an auth token
Sign up for a free account at rustunnel.com β no waiting list, no manual approval.
The free plan is a great way to get started. Upgrade to pay-as-you-go from your dashboard whenever you need custom subdomains or unlimited tunnels.
Quick start with the hosted server
Once you have a token, run the setup wizard:
bash
rustunnel setup
# Region [auto / eu / us / ap / self-hosted] (default: auto): (press Enter)# Selecting nearest regionβ¦ eu 12ms Β· us 143ms Β· ap 311ms Β· β eu (Helsinki, FI) 12ms# Server set to: eu.edge.rustunnel.com:4040# Auth token: <paste your token>
Then expose a local service:
bash
# HTTP tunnel β auto-selects the nearest region
rustunnel http 3000
# Connect to a specific region
rustunnel http 3000 --region eu
# Custom subdomain
rustunnel http 3000 --subdomain myapp
# TCP tunnel β e.g. expose a local database
rustunnel tcp 5432
# UDP tunnel β e.g. expose a game server
rustunnel udp 27015
# P2P tunnel β expose a service to another rustunnel client
rustunnel p2p 27015 --name my-game --secret "shared-secret"# P2P tunnel β connect to a peer's service
rustunnel p2p 8000 --target my-game --secret "shared-secret"
The client prints the public URL as soon as the tunnel is established:
code
Selecting nearest regionβ¦ eu 12ms Β· us 143ms Β· ap 311ms β eu (Helsinki, FI) 12ms
β tunnel open https://abc123.eu.edge.rustunnel.com
Wildcard DNS *.tunnel.yourdomain.com β server IP required for HTTP tunnels
Local development setup
Build
bash
# Clone the repository
git clone https://github.com/joaoh82/rustunnel.git
cd rustunnel
# Compile all workspace crates (debug mode)
cargo build --workspace
# Or use the Makefile shortcut
make build
Run tests
The integration test suite spins up a real server on random ports and exercises auth, HTTP tunnels, TCP tunnels, and reconnection logic. It requires a running PostgreSQL instance.
bash
# Start the local PostgreSQL container (once per machine, persists across reboots)
make db-start
# Full suite (unit + integration)
make test# With output visible
TEST_DATABASE_URL=postgres://rustunnel:test@localhost:5432/rustunnel_test \
cargo test --workspace -- --nocapture
# Stop PostgreSQL when you no longer need it
make db-stop
make db-start runs deploy/docker-compose.dev-deps.yml which starts a Postgres 16 container on localhost:5432. The make test target injects TEST_DATABASE_URL automatically. If you run cargo test directly, export the variable first:
A ready-made local config is checked into the repository at deploy/local/server.toml.
It points to the self-signed cert paths above and has auth disabled for convenience.
Start the server with it directly:
bash
cargo run -p rustunnel-server -- --config deploy/local/server.toml
Key settings in deploy/local/server.toml:
Setting
Value
Domain
localhost
HTTP edge
:8080
HTTPS edge
:8443
Control plane
:4040
Dashboard
:4041
Auth token
dev-secret-change-me
Auth required
false
TLS cert
/tmp/rustunnel-dev/cert.pem
TLS key
/tmp/rustunnel-dev/key.pem
Database
/tmp/rustunnel-dev/rustunnel.db
Run the client locally
With the server running, expose a local service (e.g. something on port 3000):
The tunnel URL uses a subdomain (e.g. http://abc123.localhost:8080).
Browsers won't resolve *.localhost subdomains by default, so you have two options:
Option A β curl with a Host header (no setup required)
Or use the Makefile target (runs build + install + systemd setup):
bash
sudo make deploy
5 β Set up PostgreSQL
rustunnel requires PostgreSQL for shared state (tokens, tunnel history, audit log).
bash
apt install -y postgresql postgresql-contrib
# Start and enable the service
systemctl enable --now postgresql
Create a dedicated database and user:
bash
sudo -u postgres psql <<'SQL'
CREATE USER rustunnel WITH PASSWORD 'CHANGE_ME';
CREATE DATABASE rustunnel OWNER rustunnel;
GRANT ALL PRIVILEGES ON DATABASE rustunnel TO rustunnel;
SQL
Tip: For managed PostgreSQL (e.g. AWS RDS, DigitalOcean Managed Database, Supabase) skip the
apt install step above and just note the connection URL for the config in the next step.
Schema migrations run automatically when the server starts β no manual SQL needed.
6 β Create the server config file
Create /etc/rustunnel/server.toml with the content below.
Replace your-admin-token-here with a strong random secret (e.g. openssl rand -hex 32).
toml
# /etc/rustunnel/server.toml[server]# Primary domain β must match your wildcard DNS record.domain = "edge.rustunnel.com"# Ports for incoming tunnel traffic (requires CAP_NET_BIND_SERVICE or root).http_port = 80https_port = 443# Control-plane WebSocket port β clients connect here.control_port = 4040# Dashboard UI and REST API port.dashboard_port = 8443# Allowed CORS origin for the dashboard UI.# Set to the URL where you serve the dashboard-ui (e.g. http://localhost:3000 for local dev).dashboard_origin = "http://localhost:3000"# ββ TLS βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ[tls]# Paths written by Certbot (see step 6).cert_path = "/etc/letsencrypt/live/edge.rustunnel.com/fullchain.pem"key_path = "/etc/letsencrypt/live/edge.rustunnel.com/privkey.pem"# Set acme_enabled = true only if you want rustunnel to manage certs itself# via the ACME protocol (requires Cloudflare credentials below).# When using Certbot (recommended), leave this false.acme_enabled = false# ββ Auth βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ[auth]# Strong random secret β used both as the admin token and for client auth.# Generate: openssl rand -hex 32admin_token = "your-admin-token-here"require_auth = true# Failed auth attempts per source IP per minute before rejection (0 = off).max_failed_auth_per_minute = 10# ββ Database βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ[database]# PostgreSQL connection URL β the database and user must exist before starting# the server (see the PostgreSQL setup step above). Schema migrations run# automatically on first start.url = "postgresql://rustunnel:CHANGE_ME@localhost:5432/rustunnel"# Per-region SQLite file for captured HTTP request bodies.# The directory must be writable by the rustunnel user.captured_path = "/var/lib/rustunnel/captured.db"# ββ Logging ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ[logging]level = "info"format = "json"# Optional: write an append-only audit log (JSON-lines) for auth attempts,# tunnel registrations, token creation/deletion, and admin actions.# Omit or comment out to disable.audit_log_path = "/var/lib/rustunnel/audit.log"# ββ Limits βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ[limits]# Maximum tunnels a single authenticated session may register.max_tunnels_per_session = 10# Maximum simultaneous proxied connections per tunnel (semaphore).max_connections_per_tunnel = 100# Per-tunnel request rate limit (requests/second).rate_limit_rps = 100# Per-source-IP rate limit (requests/second). Set to 0 to disable.ip_rate_limit_rps = 100# Maximum size of a proxied HTTP request body (bytes). Default: 10 MB.request_body_max_bytes = 10485760# Inclusive port range reserved for TCP tunnels.# Each active TCP tunnel consumes one port from this range.tcp_port_range = [20000, 20099]
# Inclusive port range reserved for UDP tunnels.# Each active UDP tunnel consumes one port from this range.# Must not overlap with tcp_port_range. Set to [0, 0] to disable UDP tunnels.udp_port_range = [20100, 20199]
cat > /etc/letsencrypt/cloudflare.ini <<'EOF'# Cloudflare API token with DNS:Edit permission for the zone.
dns_cloudflare_api_token = YOUR_CLOUDFLARE_API_TOKEN
EOF
chmod 600 /etc/letsencrypt/cloudflare.ini
Request a certificate covering the bare domain and the wildcard (required for HTTP subdomain tunnels):
Allow the rustunnel service user to read the certificates:
bash
# Grant read access to the live/ and archive/ directorieschmod 755 /etc/letsencrypt/{live,archive}
chmod 640 /etc/letsencrypt/live/edge.rustunnel.com/*.pem
chgrp rustunnel /etc/letsencrypt/live/edge.rustunnel.com/*.pem
chgrp rustunnel /etc/letsencrypt/archive/edge.rustunnel.com/*.pem
chmod 640 /etc/letsencrypt/archive/edge.rustunnel.com/*.pem
8 β Set up systemd service
bash
# Copy the unit file from the repository
install -Dm644 deploy/rustunnel.service /etc/systemd/system/rustunnel.service
systemctl daemon-reload
systemctl enable --now rustunnel.service
# Check it started
systemctl status rustunnel.service
journalctl -u rustunnel.service -f
9 β Open firewall ports
bash
ufw allow 80/tcp comment "rustunnel HTTP edge"
ufw allow 443/tcp comment "rustunnel HTTPS edge"
ufw allow 4040/tcp comment "rustunnel control plane"
ufw allow 8443/tcp comment "rustunnel dashboard"
ufw allow 9090/tcp comment "rustunnel Prometheus metrics"# TCP tunnel port range (must match tcp_port_range in server.toml)
ufw allow 20000:20099/tcp comment "rustunnel TCP tunnels"# UDP tunnel port range (must match udp_port_range in server.toml)
ufw allow 20100:20199/udp comment "rustunnel UDP tunnels"
10 β Verify the server is running
bash
# Health check β use dashboard_port from server.toml (default 8443 in production)
curl http://localhost:8443/api/status
# Confirm which ports the process is actually bound to
ss -tlnp | grep rustunnel-serve
# Startup banner is visible in the logs
journalctl -u rustunnel.service --no-pager | tail -30
# Prometheus metrics
curl -s http://localhost:9090/metrics
Port reminder: port 4040 is the control-plane WebSocket (clients connect here),
not the dashboard. Hitting it with plain HTTP returns HTTP/0.9 which is expected.
The dashboard is on dashboard_port β check your server.toml if unsure.
Updating the server
Pull the latest code, rebuild, install, and restart in one command:
A full Docker guide covering both local development (self-signed cert) and
production VPS (Let's Encrypt) is available in
docs/docker-deployment.md.
Pull the published image
Multi-arch images (linux/amd64 + linux/arm64) are published to GitHub
Container Registry on every stable release β no build required:
bash
# Latest stable, or pin a version (e.g. :0.8.1)
docker pull ghcr.io/joaoh82/rustunnel-server:latest
# Run it with your server config mounted at /etc/rustunnel/server.toml
docker run --rm \
-p 80:80 -p 443:443 -p 4040:4040 -p 8443:8443 \
-v "$PWD/deploy/server.toml:/etc/rustunnel/server.toml:ro" \
ghcr.io/joaoh82/rustunnel-server:latest
Quick reference (build from source)
bash
# Build the image locally (includes Next.js dashboard + Rust server)
make docker-build
# Build and push a multi-arch image to GHCR (maintainers; needs buildx + ghcr login)
make docker-push
# Local development (self-signed cert, no auth required)
docker compose -f deploy/docker-compose.local.yml up
# Production VPS (requires deploy/server.toml to be configured first)
make docker-run
# Production + Prometheus + Grafana monitoring stack
make docker-run-monitoring
# Tail server logs
make docker-logs
# Stop everything
make docker-stop
Files
File
Purpose
deploy/Dockerfile
Multi-stage build: Node.js UI β Rust server β slim runtime
deploy/docker-compose.yml
Production compose file
deploy/docker-compose.local.yml
Local development compose file
deploy/server.toml
Production server config template
deploy/server.local.toml
Local development server config
deploy/prometheus.yml
Prometheus scrape config
Client configuration
Installation
Option 1 β Homebrew (macOS and Linux, recommended)
bash
brew tap joaoh82/rustunnel
brew install rustunnel
Homebrew installs pre-built binaries β no Rust toolchain required.
The formula is updated automatically on every release. This installs
both rustunnel (the CLI client) and rustunnel-mcp (the MCP server
for AI agent integration).
Option 2 β Pre-built binary
Download the archive for your platform from the
latest GitHub Release,
extract it, and move the rustunnel binary to a directory on your $PATH:
bash
# Example for macOS Apple Silicon
curl -L https://github.com/joaoh82/rustunnel/releases/latest/download/rustunnel-<version>-aarch64-apple-darwin.tar.gz \
| tar xz
sudo install -Dm755 rustunnel /usr/local/bin/rustunnel
git clone https://github.com/joaoh82/rustunnel.git
cd rustunnel
cargo build --release -p rustunnel-client
sudo install -Dm755 target/release/rustunnel /usr/local/bin/rustunnel
# Or via make
make deploy-client
Setup wizard
The easiest way to create your config file is the interactive setup wizard:
bash
rustunnel setup
It prompts for your region and auth token, then writes ~/.rustunnel/config.yml with the correct server address and a commented tunnels: example section.
code
rustunnel setup β create ~/.rustunnel/config.yml
Region [auto / eu / us / ap / self-hosted] (default: auto):
Selecting nearest regionβ¦ eu 12ms Β· us 143ms Β· ap 311ms Β· β eu (Helsinki, FI) 12ms
Server set to: eu.edge.rustunnel.com:4040
Auth token (leave blank to skip): rt_live_abc123...
Created: /Users/you/.rustunnel/config.yml
Run `rustunnel start` to connect using this config.
Pick a specific region (eu, us, ap) to connect directly, or auto (the default) to let the client probe all regions and pick the nearest. Choose self-hosted if you run your own server β the wizard will then prompt for your server address.
After running setup, use rustunnel start to connect with all tunnels defined in the config, or use rustunnel http <port> / rustunnel tcp <port> for one-off tunnels.
Quick start (CLI flags)
bash
# Expose a local HTTP service on port 3000 (auto-selects nearest region)
rustunnel http 3000 \
--token YOUR_AUTH_TOKEN
# Connect to a specific region
rustunnel http 3000 --region eu --token YOUR_AUTH_TOKEN
# Use an explicit server address (bypasses region selection)
rustunnel http 3000 \
--server edge.rustunnel.com:4040 \
--token YOUR_AUTH_TOKEN
# Expose a local service with a custom subdomain
rustunnel http 3000 \
--token YOUR_AUTH_TOKEN \
--subdomain myapp
# Expose a local TCP service (e.g. a PostgreSQL database)
rustunnel tcp 5432 \
--token YOUR_AUTH_TOKEN
# Disable automatic reconnection
rustunnel http 3000 --no-reconnect
Machine-readable output (--json)
All tunnel-running commands (http, tcp, udp, p2p, start) and
token create accept a --json flag. With it, stdout emits NDJSON β
one JSON event object per line β instead of the human-readable startup
box, which makes the CLI easy to drive from scripts and AI agents:
code (config/auth/tunnel/connection/protocol/io), message, hint
token_created
token create --json succeeded
token, name, id
Diagnostics still go to stderr, so stdout stays valid NDJSON. For the
http, tcp, udp, and p2p commands the auth token can also be
supplied via the RUSTUNNEL_TOKEN environment variable instead of
--token (start reads tokens from the config file).
Config file
Default location: ~/.rustunnel/config.yml
yaml
# ~/.rustunnel/config.yml# Tunnel server address (host:control_port)server:edge.rustunnel.com:4040# Auth token (from server admin_token or a token created via the dashboard)auth_token:YOUR_AUTH_TOKEN# Region preference: auto (probe & pick nearest), or eu / us / ap.# Omit for self-hosted / single-server setups.region:auto# Named tunnels started with `rustunnel start`tunnels:web:proto:httplocal_port:3000subdomain:myapp# optional β server assigns one if omitteddb:proto:tcplocal_port:5432
Start all tunnels from the config file:
bash
rustunnel start
# or with an explicit path
rustunnel start --config /path/to/config.yml
Token management
Hosted service: manage tokens from the rustunnel.com dashboard under Dashboard β API Keys. You can create, label, and revoke tokens without any CLI commands.
Self-hosted: create additional tokens via the dashboard API:
Region identifier recorded in tunnel history (e.g. "eu", "us", "ap")
region.name
string
"Default"
Human-readable region name shown in the dashboard
region.location
string
""
Physical location label (e.g. "Helsinki, FI")
Monitoring
A Prometheus metrics endpoint is available at :9090/metrics:
code
rustunnel_active_sessions # gauge: connected clients
rustunnel_active_tunnels_http # gauge: active HTTP tunnels
rustunnel_active_tunnels_tcp # gauge: active TCP tunnels
Start with the full monitoring stack (Prometheus + Grafana):
bash
make docker-run-monitoring
# Grafana: http://localhost:3000 (admin / changeme)# Prometheus: http://localhost:9090
Production note: The default Grafana password is changeme. Set the GRAFANA_PASSWORD environment variable before starting the stack in production:
bash
export GRAFANA_PASSWORD=your-strong-password
make docker-run-monitoring
REST API
The dashboard port exposes a REST API for programmatic access to tunnels, tokens, captured requests, and tunnel history. All endpoints (except the health check) require an Authorization: Bearer <token> header.
/api/tunnels, /api/groups, and their per-tunnel sub-resources are scoped by caller: the admin token sees everything; a user-scoped API token sees only its own tunnels and groups; tunnels the caller can't see return 404, never 403. See docs/api-reference.md Β§ Visibility scope for the full rules.
Quick reference
Method
Path
Description
GET
/api/status
Health check (no auth)
GET
/api/tunnels
List active tunnels
GET
/api/tunnels/:id
Get a single tunnel
DELETE
/api/tunnels/:id
Force-close a tunnel
GET
/api/tunnels/:id/requests
Captured HTTP requests
POST
/api/tunnels/:id/replay/:req_id
Fetch stored request for replay
GET
/api/tokens
List API tokens
POST
/api/tokens
Create an API token
DELETE
/api/tokens/:id
Delete an API token
GET
/api/history
Paginated tunnel history
Full request/response schemas, query parameters, and examples are in
docs/api-reference.md.
A machine-readable OpenAPI 3.0 spec is served at GET /api/openapi.json (no auth required).
AI agent integration (MCP server)
rustunnel ships a rustunnel-mcp binary that implements the
Model Context Protocol over stdio,
letting AI agents (Claude, GPT-4o, custom agents) open and manage tunnels
without any manual intervention.
Works with any MCP harness. For copy-paste config for Claude Code, Claude
Desktop, Codex, Cursor, Windsurf, Cline, and custom agents β plus a one-command
installer β see the Agent Integration Guide:
bash
./integrations/install.sh # prompts for your token, writes the config
Set RUSTUNNEL_TOKEN once and the agent never has to pass a token on a tool call.
Available tools
Tool
Description
create_tunnel
Open a tunnel and return the public URL β HTTP/TCP/UDP, P2P, and load-balanced pools with health checks
list_tunnels
List all active tunnels
close_tunnel
Force-close a tunnel by ID
list_regions
List available server regions
get_connection_info
Return the CLI command for cloud/sandbox agents
get_tunnel_history
Retrieve past tunnel activity
Installation
Homebrew (macOS and Linux) β installs rustunnel-mcp alongside the CLI:
bash
brew tap joaoh82/rustunnel
brew install rustunnel
Build from source:
bash
make release-mcp
sudo install -m755 target/release/rustunnel-mcp /usr/local/bin/rustunnel-mcp
Full setup guide, configuration options, and workflow examples are in
docs/mcp-server.md.
Claude Code plugin
The easiest way to use rustunnel with Claude Code.
Install the plugin and it handles all MCP configuration automatically β just enter
your token once and start asking Claude to expose ports.
code
/plugin install rustunnel
The plugin prompts for your server address and API token at enable time, stores
them securely, and starts the MCP server in the background. No .mcp.json editing
or manual setup needed.
rustunnel ships an OpenClaw skill that gives any
OpenClaw-compatible AI agent first-class knowledge of rustunnel β config file
format, authentication, tool signatures, and common workflows β without you
having to explain it.
Webhook testing, demo sharing, cloud sandbox (no subprocess), named tunnels
Security
Token handling, file permissions, HTTPS-only transport
To load the skill in Claude Code:
bash
/skills load skills/rustunnel/SKILL.md
Once loaded, you can ask the agent things like:
"Expose my local port 3000 as an HTTPS tunnel using rustunnel."
"List my active tunnels and close the one forwarding port 5432."
"Set up my rustunnel config file with my token."
The skill instructs the agent to read credentials from ~/.rustunnel/config.yml
automatically, so you won't be prompted for your token on every invocation.
Roadmap
A detailed list of shipped features and planned future work is maintained in
docs/ROADMAP.md.
Contributing
Contributions are welcome! Please follow these steps:
Fork the repository and create a feature branch from main.
Run make install-hooks once after cloning to activate the pre-push quality gate.
Make your changes. Ensure make check (fmt + Clippy) and make test pass locally.
Open a Pull Request with a clear description of what changed and why.
A maintainer will review and merge once CI is green.
Guidelines
Keep PRs focused β one logical change per PR.
Add or update tests for any new behaviour.
Follow the existing code style; cargo fmt is enforced by CI.
For larger changes or new features, open an issue first to discuss the approach.
License
This project is licensed under the GNU AGPLv3 License β see the LICENSE file for details.