Skip to main content
Glama

one-click-tunnel (OCT)

OCT = One Click Tunnel. Publish a port from this machine or from a LAN device to a temporary public URL, behind a password gate, with a native window + tray, a CLI and an MCP server.

CI Release License: MIT Platform: Windows

中文文档 · 更新日志

One-click temporary public tunnels for Windows, built on Cloudflare Quick Tunnels — with a password gate in front of your service, a local config page, a system tray icon, a CLI and an MCP server (so an AI agent can manage tunnels too). No admin rights, no dependencies, single-file executable.

Install

From the latest release:

Download

What it is

one-click-tunnel-setup-.exe

One-click installer (per-user, no admin). Adds Start Menu / Desktop shortcuts and an uninstall entry.

one-click-tunnel--win-x64.zip

Portable bundle: unpack and double-click install.cmd

Verify your download against SHA256SUMS.txt in the release assets.

Related MCP server: powershell-mcp

What it does

  • Publishes a local port to a temporary https://xxxx.trycloudflare.com URL that requires a password

  • The password gate binds to 127.0.0.1 only, so public traffic must come through the tunnel

  • Everything auto-closes when the TTL expires (per tunnel)

  • Run many tunnels at once, each with its own local port, gateway port, password and expiry time

  • Manage them from the config page, the tray icon, the CLI, or let an AI agent do it over MCP

Four ways to start / manage tunnels

# 1. graphical config page + tray (recommended)
oct.exe gui --open

# 2. tray only (background daemon is started automatically)
oct.exe tray

# 3. command line, many tunnels
oct.exe add --name web --port 3000 --ttl 1h --auto-start
oct.exe start web
oct.exe list
oct.exe stop --all

# 4. MCP: let an AI host drive it
oct.exe mcp

The classic single-tunnel flags still work exactly as before:

oct.exe --port 3000 --ttl 1h --password mypassword

Permanent address with your own domain (named tunnels)

Quick tunnels give you a random *.trycloudflare.com address that changes on every run. If you own a domain managed by Cloudflare, you can give a tunnel a permanent address instead:

oct.exe login                        # one browser authorization (pick the domain/zone)
oct.exe domain web app.example.com   # creates the named tunnel + the DNS record
oct.exe start web                    # now serving https://app.example.com, permanently
  • tunnel login stores the origin certificate in state/cloudflare/cert.pem (once per account).

  • tunnel domain <id> <hostname> creates the tunnel, copies its credentials into state/cloudflare/ and points the hostname at it. It also writes mode: named and ttl: forever into the config, so the address never expires.

  • The tunnel still terminates at the password gate on 127.0.0.1, so a permanent hostname is not an open door.

  • tunnel info [id] shows mode, hostname, tunnel id and anything still missing.

  • --dry-run prints the generated cloudflared config and command without starting anything.

  • Config page: a Cloudflare login button, plus a per-tunnel quick/named switch and domain binding.

  • AI hosts get the same through the MCP tools cloudflare_login, set_custom_domain and cloudflare_status.

  • Requirements: the domain's DNS must be managed by Cloudflare (the free plan is enough).

Generated state/cloudflare/<id>.cloudflared.yml:

tunnel: <tunnel-id>
credentials-file: '<...>/<id>.credentials.json'
no-autoupdate: true
ingress:
  - hostname: app.example.com
    service: http://127.0.0.1:18080      # <- the local password gate, not your app port
  - service: http_status:404

Where is the tray icon?

The tray is not part of the web page - a browser page cannot own a notification-area icon. The background daemon starts a tiny Windows process (PowerShell WinForms, no extra dependency) that shows the icon:

  • Windows 11 hides new tray icons by default: click the ^ (show hidden icons) next to the clock, or open Settings -> Personalization -> Taskbar -> Other system tray icons and switch the "one-click-tunnel / 一键隧道" entry on to keep it visible.

  • The icon only lives while the daemon runs. In 1.2.x the daemon lived in the console window, so closing that window killed the tray as well. From 1.3.0 the launcher is windowless and the daemon is detached, so closing the app window keeps the tunnels and the tray alive.

  • Start it manually any time: oct.exe tray (reuses the running daemon; exits if one is already there)

  • Turn it off: config page -> Global settings -> Tray, or tray.enabled=false in config.json.

  • If the daemon dies, the tray closes itself within ~10 seconds (watchdog), so no ghost icons are left behind.

Desktop app (no console window)

The shortcut starts the background daemon hidden and opens the config page inside a chromeless desktop window (Edge/Chrome app mode: no address bar, no tabs, its own taskbar entry and icon). Closing that window does not stop the tunnels - the tray icon stays, and the tray menu (or the "close everything" shortcut, which runs stop-all.vbs) quits everything. No cmd window appears: the shortcut runs a .vbs launcher and the daemon is spawned detached and hidden.

oct.exe app             # open the desktop window (starts the daemon when needed)
oct.exe gui --browser   # use a normal browser tab instead of the app window

Config file (config.json, next to the exe)

{
  "version": 1,
  "gui":  { "port": 18400, "token": "<auto generated>", "openBrowser": true },
  "mcp":  { "enabled": true, "allowStart": true, "allowStop": true },
  "tray": { "enabled": true },
  "defaults": { "ttl": "1h", "passwordMode": "random", "gatewayStart": 18080, "rateLimit": 3000 },
  "update": { "enabled": true, "repo": "Thunderunruly/one-click-tunnel",
              "apiBase": "https://api.github.com", "checkIntervalHours": 6,
              "autoDownload": false, "includePrerelease": false,
              "downloadMirror": "", "ignoredVersion": "" },
  "profiles": [
    { "id": "web", "name": "web 3000", "enabled": true, "autoStart": false,
      "port": 3000, "gateway": 18080, "ttl": "30m",
      "passwordMode": "random", "password": "", "host": "127.0.0.1" }
  ]
}

One profile = one local port + one gateway port + one password + one TTL. Each profile runs as its own child process, so session keys, rate limits, lockouts and timers are isolated per tunnel.

MCP (AI integration)

{
  "mcpServers": {
    "one-click-tunnel": {
      "command": "C:\\Users\\<you>\\AppData\\Local\\one-click-tunnel\\oct.exe",
      "args": ["mcp"]
    }
  }
}

Tools exposed: list_tunnels, create_tunnel, start_tunnel, stop_tunnel, update_tunnel, delete_tunnel, set_password, tunnel_status, stop_all. Tunnels created by the AI show up in the config page and can be stopped by hand at any time. Use mcp.allowStart / mcp.allowStop to limit what the AI may do.

Updates

The program checks the GitHub release channel for a newer version (default: every 6 hours, configurable) and shows a banner on the config page when one exists. You can also check on demand:

oct.exe update               # check now
oct.exe update --notes       # print the release notes
oct.exe update --download    # download the installer into updates\ (SHA-256 verified)
oct.exe update --install     # silently run the downloaded setup.exe
oct.exe version              # print the current version
  • Downloads are verified against the release SHA256SUMS.txt; a mismatch deletes the file and fails loudly.

  • Auto-download (never auto-install) can be enabled with update.autoDownload.

  • Behind a blocked network, point update.apiBase at a GitHub API mirror and/or set update.downloadMirror to something like https://your-mirror.example/{url} -- the {url} placeholder is replaced with the real asset URL.

  • Ignore a version from the banner (it is stored as update.ignoredVersion).

  • AI hosts get the same capability through the MCP tool check_update.

Security

  • Password gate on 127.0.0.1 only; constant-time password comparison; per-IP lockout (8 failures / 5 min) and rate limit

  • Session cookie: HMAC signature, random per-session id, HttpOnly, SameSite=Lax, Secure over https, server-side revocation on logout

  • Requests are sanitised before proxying: hop-by-hop headers stripped, Host rewritten unless allow-listed, X-Forwarded-For / X-Real-IP overwritten with the real client IP, gateway credentials never forwarded upstream

  • TRACE/TRACK, CONNECT and absolute-form request lines rejected; unauthenticated requests never reach your service

  • Config page: loopback only, token required, same-origin enforced on writes, strict CSP, no external resources

  • TTL auto-shutdown, process-tree cleanup, orphan cloudflared reaping, logs never contain the password

Verified by re-runnable suites (offline unless noted):

npm test                      # 72 + 25 + 29 + 27 checks
npm run test:exe              # packaged exe: CLI / GUI / self-spawn
npm run test:installer        # installer end-to-end
npm run test:live             # real Cloudflare tunnel from the public side (needs internet)

Build from source

git clone https://github.com/Thunderunruly/one-click-tunnel.git
cd one-click-tunnel
node build/make-exe.mjs       # dist/oct.exe (single file, Node SEA)
node build/make-zip.mjs       # release/one-click-tunnel-<version>-win-x64.zip
node build/make-setup.mjs     # release/one-click-tunnel-setup-<version>.exe (needs Inno Setup 6)

Releases are produced automatically by GitHub Actions: push a tag like v1.1.0 and the Release workflow runs the test suites, builds the exe, the portable zip and the Inno Setup installer, then publishes them to the GitHub release.

MIT licensed. cloudflared is a separate binary by Cloudflare (Apache-2.0).

Why not Go? (measured, not guessed)

The password gate is I/O bound, not CPU bound. On a local loopback benchmark (build/bench.mjs, 4000 requests, 40 concurrent, keep-alive) it adds +0.62 ms p50 and keeps ~86% of the raw throughput (14.0k vs 16.3k req/s), while the real request path - Internet -> Cloudflare edge -> cloudflared -> gate -> your app - is dominated by the network and by cloudflared, which is itself written in Go.

metric

measured

gate overhead

+0.62 ms p50, +1.25 ms p95

throughput

14,035 req/s gated vs 16,260 req/s direct

per-tunnel memory

72 MB gate process + 46 MB cloudflared

cold start

55 ms

artifacts

exe 83.4 MB, cloudflared 52.4 MB, portable zip 49.2 MB

A Go rewrite would mostly buy a smaller binary (~10 MB) and less memory per tunnel - packaging wins, not throughput - so the Node implementation stays. Re-measure any time with node build/bench.mjs (or npm run bench); if a real workload ever reaches these numbers, revisit the decision with data.

Launcher: no console window, no VBScript

The desktop/Start-Menu shortcut points at one-click-tunnel.exe - a ~7 KB native launcher compiled from build/launcher/Launcher.cs with the csc.exe that ships with Windows. It is a GUI-subsystem binary (PE Subsystem=2), so it starts oct.exe app hidden and exits immediately: no console window flashes. It also removed the old VBScript launcher, which matters because Microsoft is deprecating VBScript (optional feature since Windows 11 24H2). Rebuild it with node build/make-launcher.mjs.

Expose a LAN device, not just localhost

By default the tunnel points at 127.0.0.1:<port>. To publish another machine on your network - a NAS, a camera, a printer web UI, a server on the LAN - set the target explicitly:

oct.exe --target 192.168.1.50:8080 --ttl 1h
oct.exe add --name nas --port 5000 --target 192.168.1.50:5000
  • Allowed by default: loopback, 10/8, 172.16/12, 192.168/16, fc00::/7 and hostnames that resolve to them.

  • Refused by default: public IPs, 0.0.0.0/::, multicast and link-local (169.254.x.x, incl. 169.254.169.254), so the tunnel can never be turned into an open proxy. Override with --allow-public-target if you really mean it.

  • The password gate applies exactly the same way, and the Host header seen by the device is its own address (override with --upstream-host).

  • HTTPS upstreams: --target-secure (+ --target-insecure-tls for self-signed certificates).

Related MCP Connectors

Related MCP Servers

  • A
    license
    A
    quality
    A
    maintenance
    Enables AI agents to run PowerShell commands and manage Windows services without intrusive console windows, while also supporting remote execution via SSH and WinRM.
    10
    123 npm
    MIT
  • F
    license
    Not graded
    quality
    C
    maintenance
    Enables AI assistants to securely access and control a Windows machine remotely, running commands and managing files via an MCP server protected by Bearer-token authentication and exposed through a Cloudflare Tunnel.
    -
  • A
    license
    Not graded
    quality
    C
    maintenance
    Enables AI assistants to perform local development tasks on Windows by reading and editing files, running commands, and controlling browser and desktop tools, all within isolated workspaces. Supports secure remote access via an optional tunnel for ChatGPT clients.
    MIT