{"id":49542858,"url":"https://github.com/pragmalabs-tech/mcp-tunnel","last_synced_at":"2026-05-02T16:02:28.381Z","repository":{"id":355150417,"uuid":"1226973991","full_name":"pragmalabs-tech/mcp-tunnel","owner":"pragmalabs-tech","description":"Public Endpoint for MCP Server and Applications","archived":false,"fork":false,"pushed_at":"2026-05-02T05:43:36.000Z","size":61,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-02T06:23:51.096Z","etag":null,"topics":["mcp","rust","tunnel"],"latest_commit_sha":null,"homepage":"","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/pragmalabs-tech.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-05-02T03:35:16.000Z","updated_at":"2026-05-02T05:51:40.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/pragmalabs-tech/mcp-tunnel","commit_stats":null,"previous_names":["pragmalabs-tech/mcp-tunnel"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/pragmalabs-tech/mcp-tunnel","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pragmalabs-tech%2Fmcp-tunnel","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pragmalabs-tech%2Fmcp-tunnel/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pragmalabs-tech%2Fmcp-tunnel/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pragmalabs-tech%2Fmcp-tunnel/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/pragmalabs-tech","download_url":"https://codeload.github.com/pragmalabs-tech/mcp-tunnel/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pragmalabs-tech%2Fmcp-tunnel/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32540120,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-02T12:25:33.646Z","status":"ssl_error","status_checked_at":"2026-05-02T12:24:51.733Z","response_time":132,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["mcp","rust","tunnel"],"created_at":"2026-05-02T16:02:26.998Z","updated_at":"2026-05-02T16:02:28.375Z","avatar_url":"https://github.com/pragmalabs-tech.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"# mcp-tunnel\n\nSelf-hosted HTTP tunnel relay. Run it on a host with a wildcard DNS record, point a client at it from anywhere, and inbound traffic to `{subdomain}.{your-domain}` gets forwarded through a WebSocket to a service running on the client's `localhost`.\n\n```\nInternet -\u003e mcp-tunnel relay -\u003e WebSocket -\u003e mcp-tunnel-client -\u003e local HTTP service\n            tunnel.example.com                                     localhost:9000\n```\n\nUseful for exposing a local dev server, MCP server, webhook receiver, or any HTTP service to the internet without poking holes in firewalls.\n\n## Quick start\n\nRun the relay:\n\n```sh\ndocker run -p 8080:8080 ghcr.io/pragmalabs-tech/mcp-tunnel \\\n  --domain tunnel.example.com\n```\n\nConnect a client (Rust):\n\n```toml\n# Cargo.toml\n[dependencies]\nmcp-tunnel-client = \"0.1\"\n```\n\n```rust\nuse mcp_tunnel_client::{TunnelStatusCallback, start_tunnel_client};\n\nstruct Logger;\nimpl TunnelStatusCallback for Logger {\n    fn on_connected(\u0026self, url: \u0026str) { println!(\"public URL: {url}\"); }\n    fn on_disconnected(\u0026self) {}\n    fn on_evicted(\u0026self) {}\n}\n\nlet public_url = start_tunnel_client(\n    9000,                              // your local port\n    \"https://tunnel.example.com\",      // relay URL\n    \"tok_abc\",                         // auth token\n    Some(\"myapp\"),                     // requested subdomain\n    Logger,\n).await?;\n```\n\nSee [`examples/basic`](examples/basic) for a runnable end-to-end demo.\n\n## Relay CLI flags\n\n```\nmcp-tunnel --domain \u003cDOMAIN\u003e [OPTIONS]\n```\n\n| Flag | Default | Required | Description |\n|---|---|---|---|\n| `--domain \u003cDOMAIN\u003e` | - | yes | Base domain for tunnel subdomains |\n| `--port \u003cPORT\u003e` | `8080` | no | TCP listen port |\n| `--static-token \u003cTOKEN:SUBS\u003e` | - | no | Static token entry; repeatable. Format: `TOKEN:SUBDOMAIN[,SUBDOMAIN...]` |\n| `--auth-url \u003cURL\u003e` | - | no | Auth provider base URL (enables provider mode) |\n| `--auth-secret \u003cSECRET\u003e` | - | with `--auth-url` | Shared secret sent as `X-Relay-Secret` header |\n| `--max-request-body \u003cBYTES\u003e` | `5242880` | no | Max inbound request body in bytes (5 MB) |\n| `--max-response-body \u003cBYTES\u003e` | `10485760` | no | Max tunneled response body in bytes (10 MB) |\n\n`--static-token` and `--auth-url` are mutually exclusive. Without either, the relay runs in open mode.\n\n## Auth modes\n\n### Open\n\nNo authentication. Any client can register a tunnel. Suitable for private networks or local development.\n\n```sh\nmcp-tunnel --domain tunnel.example.com\n```\n\n### Static\n\nToken list defined at startup via repeated `--static-token` flags. Each entry maps a token to a list of allowed subdomain patterns.\n\n```sh\nmcp-tunnel --domain tunnel.example.com \\\n  --static-token tok_abc:myapp,myapp-* \\\n  --static-token tok_xyz:other-app\n```\n\nSubdomain patterns support a single `*` wildcard:\n\n| Pattern | Matches |\n|---|---|\n| `myapp` | exactly `myapp` |\n| `myapp-*` | `myapp-dev`, `myapp-feat-123` |\n| `*-preview` | `feat-preview`, `hotfix-preview` |\n| `pr-*-corp` | `pr-123-corp`, `pr-abc-corp` |\n| `*` | anything |\n\n### Provider\n\nDelegates token verification to an external HTTP endpoint (run your own auth service).\n\n```sh\nmcp-tunnel --domain tunnel.example.com \\\n  --auth-url https://auth.example.com \\\n  --auth-secret \u003cshared-secret\u003e\n```\n\nThe relay calls `POST {auth-url}/api/verify` with:\n\n```json\n{ \"token\": \"tok_abc\", \"subdomain\": \"myapp\" }\n```\n\nHeader: `X-Relay-Secret: \u003cauth-secret\u003e`\n\nExpected response:\n\n```json\n{ \"subdomains\": [\"myapp\", \"myapp-*\"] }\n```\n\nReturn `401`/`403` for invalid tokens, `5xx` for transient errors (client gets \"auth provider unavailable\").\n\n## Docker Compose example\n\n```yaml\nservices:\n  mcp-tunnel:\n    image: ghcr.io/pragmalabs-tech/mcp-tunnel:latest\n    restart: unless-stopped\n    ports:\n      - \"8080:8080\"\n    command:\n      - --domain=tunnel.example.com\n      - --auth-url=https://auth.example.com\n      - --auth-secret=${RELAY_SECRET}\n```\n\nTraffic must reach the container with the correct `Host` header. Sit a reverse proxy (nginx, Caddy, Traefik) in front and route `*.tunnel.example.com` to port 8080.\n\n### nginx example\n\n```nginx\nserver {\n    listen 443 ssl;\n    server_name *.tunnel.example.com;\n\n    ssl_certificate     /etc/ssl/tunnel.example.com/fullchain.pem;\n    ssl_certificate_key /etc/ssl/tunnel.example.com/privkey.pem;\n\n    location / {\n        proxy_pass         http://localhost:8080;\n        proxy_http_version 1.1;\n        proxy_set_header   Upgrade $http_upgrade;\n        proxy_set_header   Connection \"upgrade\";\n        proxy_set_header   Host $host;\n        proxy_read_timeout 60s;\n    }\n}\n```\n\nThe wildcard TLS certificate covers `*.tunnel.example.com`. Let's Encrypt supports wildcard certs via DNS-01 challenge.\n\n## Client library\n\nTo connect a service from Rust, use [`mcp-tunnel-client`](crates/mcp-tunnel-client) — published to crates.io.\n\n### Install\n\n```sh\ncargo new --bin mytunnel\ncd mytunnel\ncargo add mcp-tunnel-client\ncargo add tokio --features macros,rt-multi-thread,signal\ncargo add axum                                 # or whatever HTTP framework you use\n```\n\n### Use\n\n`src/main.rs`:\n\n```rust\nuse axum::{Router, routing::get};\nuse mcp_tunnel_client::{TunnelStatusCallback, start_tunnel_client};\n\nstruct Logger;\nimpl TunnelStatusCallback for Logger {\n    fn on_connected(\u0026self, url: \u0026str) { println!(\"public URL: {url}\"); }\n    fn on_disconnected(\u0026self) { println!(\"disconnected\"); }\n    fn on_evicted(\u0026self) { println!(\"evicted\"); }\n}\n\n#[tokio::main]\nasync fn main() -\u003e Result\u003c(), Box\u003cdyn std::error::Error\u003e\u003e {\n    // 1. start your local HTTP service\n    let app = Router::new().route(\"/\", get(|| async { \"hello\" }));\n    let listener = tokio::net::TcpListener::bind((\"127.0.0.1\", 9000)).await?;\n    tokio::spawn(async move { axum::serve(listener, app).await.unwrap(); });\n\n    // 2. expose it through the relay\n    let public_url = start_tunnel_client(\n        9000,                              // local port your service listens on\n        \"https://tunnel.example.com\",      // relay URL\n        \"tok_abc\",                         // auth token (use \"any\" in open mode)\n        Some(\"myapp\"),                     // requested subdomain (None lets the relay pick)\n        Logger,\n    ).await?;\n    println!(\"reachable at {public_url}\");\n\n    // 3. keep the process alive; the tunnel runs in a background task\n    tokio::signal::ctrl_c().await?;\n    Ok(())\n}\n```\n\n```sh\ncargo run\n```\n\nSee [`examples/basic`](examples/basic) for a runnable end-to-end demo and [crates/mcp-tunnel-client/README.md](crates/mcp-tunnel-client/README.md) for the full API.\n\n## Build from source\n\nRequires Rust 1.92+. This is a Cargo workspace with two crates:\n\n- `crates/mcp-tunnel` — the relay binary (this is what runs in the Docker image)\n- `crates/mcp-tunnel-client` — the client library, published to crates.io\n\n```sh\ncargo build --release\n./target/release/mcp-tunnel --domain tunnel.example.com\n```\n\n## Releasing\n\nReleases are driven by [`cargo-release`](https://github.com/crate-ci/cargo-release):\n\n```sh\ncargo install cargo-release             # one-time\ncargo release minor                     # dry run\ncargo release minor --execute           # bump, commit, tag, publish, push\n```\n\nThis bumps both crates to the same version, publishes `mcp-tunnel-client` to crates.io (the binary is `publish = false`), tags `vX.Y.Z`, and pushes. GitHub Actions then sees the tag and builds/pushes the multi-arch Docker image to ghcr.io.\n\n## How it works\n\n1. The client opens a WebSocket to `/_tunnel/register` and sends a registration message with its auth token and (optional) requested subdomain.\n2. The relay verifies the token, assigns a subdomain, and acknowledges with the public URL.\n3. Inbound HTTP requests arrive at `{subdomain}.{domain}`. The relay extracts the subdomain from the `Host` header, finds the matching WebSocket connection, and forwards the request as a JSON message (base64 body).\n4. The client receives the request, forwards it to the local service, and sends the response back through the WebSocket.\n5. If a second client registers the same subdomain, the relay evicts the previous connection (close code 4002).\n\n## License\n\nApache-2.0\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpragmalabs-tech%2Fmcp-tunnel","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpragmalabs-tech%2Fmcp-tunnel","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpragmalabs-tech%2Fmcp-tunnel/lists"}