---
name: setup-tunnel
description: Set up a Cloudflare Tunnel using deterministic MCP tools for all operations. Browser-specialist is navigation-only (screenshots + page state) — never interacts with forms or solves challenges.
---

# Set Up Cloudflare Tunnel

Create a Cloudflare Tunnel so the platform is accessible from the public internet via `admin.{domain}` and `public.{domain}`. All Cloudflare-side operations (auth, zone creation, tunnel management, DNS) are deterministic MCP tool calls — no `browser_evaluate`, no dashboard API scripts.

Authentication: `tunnel-login` (default — one-click OAuth via `cloudflared`) or `cf-set-token` (fallback — user pastes connection key directly). Browser-specialist is navigation-only — it never interacts with forms, solves challenges, or clicks buttons on Cloudflare pages.

Browser-specialist is dispatched for:
1. **Cloudflare authorization** — navigates to the `tunnel-login` auth URL so the user can sign in and click Authorize in the VNC browser. Specialist takes screenshots and reports page state only.
2. **Registrar nameserver changes** — updating nameservers at Namecheap, GoDaddy, etc. (registrar-specific, no universal API)
3. **URL verification** — confirming the tunnel URLs load after setup

## User-facing language

Never say "API token", "scoped token", "cert.pem", "Zone", "DNS", "CNAME", "ingress", or any Cloudflare-internal terminology to the user. Use plain language:

| Internal term | Say instead |
|---------------|-------------|
| API token / cert.pem | connection |
| Zone | domain |
| DNS records | domain routing |
| Nameservers | domain settings |
| CNAME | routing entry |
| Tunnel ID | (don't mention) |
| Ingress rules | (don't mention) |

Technical details are for tool calls and specialist briefs only — never in messages to the user.

## Prerequisites

Before starting, call `tunnel-status` to assess the current state.

- **Tunnel already running with no DNS warnings:** Tunnel is set up. Confirm to the user and stop.
- **Tunnel running but DNS NOT RESOLVING warning:** The tunnel process works but URLs are dead because nameservers don't point to Cloudflare. Proceed to "Check domain status."
- **cloudflared not installed:** Call `tunnel-install`. If it fails, tell the user and stop.
- **`tokenValid: true`, not running:** Token is valid — skip auth. Proceed to "Check domain status."
- **`hasCert: true` but `hasToken: false`:** cert.pem exists but credentials haven't been derived. Call `tunnel-login` — it will derive credentials from the cert automatically.
- **`hasCert: false` and `hasToken: false`:** Full flow required. Proceed to "Connect to Cloudflare."

## Connect to Cloudflare

The platform needs to connect to the user's Cloudflare account. The default path is `tunnel-login` — one-click browser authorization via `cloudflared`. All API work uses MCP tools. No `browser_evaluate`, no dashboard API calls via the browser.

### Flow

1. **Show the browser.** Call `render-component` with `name: "browser-viewer"` and `data: { title: "Cloudflare" }`.

2. **Start the login.** Call `tunnel-login`. It returns an auth URL.

3. **Navigate to the auth URL.** Dispatch `specialists:browser-specialist`:

   > Navigate to {auth_url}. Take a screenshot. Report whether a sign-in page or authorization prompt is shown. Do NOT interact with any forms, checkboxes, or challenges — report what you see and stop.

4. **User signs in and authorizes.** If a sign-in page is shown, tell the user: "Sign in to your Cloudflare account in the browser. If you don't have one, create one at cloudflare.com. Let me know when you're signed in." After sign-in, ask the specialist for another screenshot to confirm the authorization prompt is visible. The user clicks Authorize themselves.

5. **Derive credentials.** After the user confirms they clicked Authorize, call `tunnel-login` again. It detects the authorization and derives credentials automatically.

6. **Handle the result.**
   - Success ("Credentials derived"): tell the user "Connected to your Cloudflare account." Proceed to "Check domain status."
   - Authorization not yet complete: ask the user to check the browser and click Authorize. Maximum 3 attempts before falling back to `cf-set-token` (see Fallback below).

## Check domain status

9. **Call `cf-zone-status`** to list all domains on the account with their activation status and nameservers.

10. **Handle the domain status.**
    - **Domain is Active:** Proceed to "Create the tunnel."
    - **Domain is Pending:** Nameservers haven't been updated yet. Proceed to "Update nameservers."
    - **No domain on the account:** The user needs to add their domain to Cloudflare. Proceed to "Add domain."

### Add domain

If no domain is listed on the Cloudflare account, add it via the official API.

11. **Ask which domain.** "Which domain would you like to use? (e.g., mybusiness.com)"

12. **Add the domain via API.** Call `cf-add-zone` with the domain. The tool creates the zone on Cloudflare's Free plan and returns the activation status and assigned nameservers.

13. **Handle the result.**
    - Zone added (status: pending): report the Cloudflare-assigned nameservers and proceed to "Update nameservers."
    - Zone already exists (idempotent): the domain was already on the account. Check its status and proceed accordingly.
    - 403 permission error: the connection lacks the `Zone:Zone:Edit` scope. Re-run `tunnel-login` to get fresh credentials with full permissions, or ask the user to paste a connection key with the correct scopes and use `cf-set-token`.
    - Other error: report the error. Tell the user: "Adding the domain automatically didn't work. Please add it through your Cloudflare account and let me know when it's done." Then re-run `cf-zone-status` to confirm.

### Update nameservers

If the domain shows Pending status, the nameservers at the registrar still point elsewhere.

14. **Get the Cloudflare nameservers** from the `cf-zone-status` output (two hostnames ending in `.ns.cloudflare.com`).

15. **Ask the user which registrar they use.** "Your domain needs to be pointed to Cloudflare. Which company did you register your domain with? (e.g., Namecheap, GoDaddy, Porkbun)"

16. **Show the browser** (if title changed). Call `render-component` with:
    - `name: "browser-viewer"`
    - `data: { title: "Registrar" }`

17. **Navigate to the registrar in the VNC browser.** Dispatch browser-specialist:

    > Navigate to {registrar_url}. Take a screenshot. Report whether a sign-in page is shown or the user is already signed in.

18. **User signs in to their registrar.** Tell the user: "Sign in to your {registrar} account in the browser. Let me know when you're signed in."

19. **Navigate to nameserver settings.** Dispatch browser-specialist:

    > Find the domain "{domain}" in the registrar dashboard. Navigate to its DNS or nameserver settings. Take a screenshot. Report the current nameservers and whether there is an option to set custom nameservers.

20. Tell the user to update the nameservers:

    > Update the nameservers in the browser to:
    > - `{cf_nameserver_1}`
    > - `{cf_nameserver_2}`
    >
    > Replace the existing nameservers and save. Let me know when done.

21. Wait for confirmation. Re-run `cf-zone-status` to check the domain status. If still Pending: "The settings are updated but it can take a few minutes to take effect. We can continue setting up the tunnel — everything will start working once the change propagates."

    Proceed to "Create the tunnel."

## Create the tunnel

22. **Call `tunnel-create`** with the domain. `tunnel-create` is idempotent — safe to call again. It creates the tunnel on Cloudflare's edge, configures ingress rules via the API, and creates CNAME records for `admin.{domain}` and `public.{domain}`.

23. **Check the response for DNS warnings.** If the domain isn't Active on Cloudflare, return to "Update nameservers." If DNS propagation is in progress, wait and re-check.

24. **Tell the user.** "Your tunnel is created and your domain is configured. One more step — setting a password for remote access."

## Enable the tunnel

25. **Set remote password.** The remote password gates external access to the admin interface — `tunnel-enable` refuses to start without it. The password is set by the user in their own browser, never in chat.

    **Derive paths from the platform.** Call `system-status` (admin MCP tool) to get the device hostname, brand info, and port. The response includes a `hostname` field (the device's OS hostname — use this for URLs), a `brand` field (product identity), and port information. Use these to construct the file path and URL — never hardcode them.

    The `hostname` field is the device's actual network name (e.g., `muvin`, `realagent`). The brand hostname in the `brand` field may differ from the device hostname — always use the `hostname` field for URL construction.

    Check: `test -f ~/{configDir}/.remote-password && echo "exists" || echo "missing"` via Bash (substituting the actual configDir derived from brand info in system-status).

    - If exists: proceed to enable.
    - If missing: tell the user to open the setup page in their own browser (not the VNC browser) and set a remote access password. This password protects admin access over the internet — separate from the local PIN.

      Construct the URL from system-status output: `http://{hostname from system-status}.local:{port}/__remote-auth/setup`

      > Open this URL in your browser to set your remote access password:
      > **{constructed URL}**
      >
      > Choose a strong password — at least 8 characters, with a number and a special character. You'll use this password when accessing your platform remotely.

    - **Verification gate:** After the user confirms, poll for the file — scrypt hashing and file write on the Pi take several seconds. Check `test -f ~/{configDir}/.remote-password && echo "exists" || echo "missing"` via Bash every 3 seconds, up to 5 attempts (15 seconds total). When the file exists, confirm and proceed. If the file still doesn't exist after all attempts, tell the user: "Still waiting — this can take a moment on slower devices. Please check the browser to make sure the form submitted, then let me know to try again."

    - **The agent never collects, sees, or handles the password.** It only verifies the file exists.

26. **Enable the tunnel.** Call `tunnel-enable` with the tunnel ID and domain.

27. **Verify the URLs work.** Call `tunnel-status` to confirm `running: true`. Then dispatch browser-specialist:

    > Navigate to https://public.{domain}. Take a screenshot. Report whether the chat interface loads or a connection error appears.

    - Chat loads: tell the user both URLs are live and working.
    - Connection error after 2 retries: "The tunnel is running but the URLs are not responding yet. This usually takes a few minutes. Try visiting the link shortly."
    - **Do NOT use `web-fetch`** for verification — use browser-specialist only.
    - If browser-specialist is unavailable: tell the user explicitly to check the URL in their own browser.
    - **Never report URLs as working without verification.**

## Fallback

1. **For Cloudflare authorization:** If `tunnel-login` fails (authorization not completed, credential derivation fails, or browser-specialist unavailable), fall back to `cf-set-token`. Ask the user: "Do you have an existing connection key you can paste?" If they do, call `cf-set-token` with it.
2. **For domain addition:** `cf-add-zone` is a direct API call — no browser needed. If `cf-add-zone` itself fails, tell the user: "Adding the domain automatically didn't work. Please add it through your Cloudflare account and let me know when it's done."
3. **For registrar nameserver changes:** tell the user to update their nameservers manually. "Go to your domain registrar and change the nameservers to the two addresses I'll give you. Let me know when done."
4. **For URL verification:** tell the user to check `https://public.{domain}` in their own browser and confirm it loads.

Even in fallback mode, never use internal terminology. Guide in plain English.

## Add alias domain

An alias domain (e.g. `maxy.chat`) serves the public chat directly — the URL stays as the alias domain in the browser bar. No redirect.

### Prerequisites

- A working tunnel (the main setup flow above must be complete)
- The alias domain must be an Active zone on the Cloudflare account (same as the primary domain — added via the dashboard or purchased through Cloudflare)
- The tunnel ID (from `tunnel-status`)

### Flow

1. **Confirm the alias domain.** The user says something like "attach maxy.chat to my public chat" or "add maxy.chat as an alias." Extract the alias domain.

2. **Check the alias domain is on Cloudflare.** Call `cf-zone-status`. The alias domain must appear as Active. If it's not listed, guide the user through adding it (same as "Add domain" in the main flow). If it's Pending, guide nameserver changes.

3. **Get the tunnel ID.** Call `tunnel-status`. The tunnel must be running or at least have a known tunnel ID.

4. **Add the alias hostname.** Call `tunnel-add-hostname` with the alias domain and tunnel ID. The tool handles everything: adds the hostname to the tunnel ingress, creates a CNAME in the alias domain's zone pointing to the tunnel, and registers the alias so the web server recognises it. The web server hot-reloads alias domains automatically — no restart needed.

5. **Verify the alias URL works.** Wait a few seconds for the web server to pick up the change, then dispatch browser-specialist to navigate to `https://{alias_domain}` and take a screenshot. The public chat should load directly.
   - If it loads: "Your public chat is now live at https://{alias_domain}. The URL stays as {alias_domain} — no redirect."
   - If it fails: check DNS propagation (1-5 minutes for CNAME). Retry after waiting. If still failing, use `dns-lookup` to diagnose.

### User-facing language

Same rules as the main flow — no technical terminology. Say "alias domain" or "custom domain" when talking to the user. Never say CNAME, ingress, or zone.

### Idempotency

`tunnel-add-hostname` is idempotent — safe to call multiple times with the same hostname. If the alias is already configured, it reports that and does nothing.

## Reinstalling / upgrading the platform

When re-running the installer (upgrade, reconfiguration, or recovery), preserve the device's current identity by passing explicit flags. The installer treats `--hostname` and `--port` as the single authority when provided — no detection, no fallback.

1. Call `system-status` to get the current `hostname` and `port` values.
2. Derive the brand package name from the `brand` field in the response (e.g., `Maxy` → `@rubytech/create-maxy`, `Real Agent` → `@rubytech/create-realagent`).
3. Run the installer with both flags:

```bash
npx -y @rubytech/create-realagent --hostname muvin --port 19400
```

Without these flags, the installer falls back to detection (reads the current OS hostname and port from `.env` or the service file). Detection is reliable for simple cases but fails when the OS hostname was previously reset to the brand default — which is the problem `--hostname` exists to solve.

## Important

- **Everything in the chat.** Authorization happens in the VNC browser (user signs in and clicks Authorize — specialist only navigates and takes screenshots). Domain checks use `cf-zone-status`. Tunnel creation and routing use the SDK. The user never navigates to external websites on their own.
- **Never claim URLs are working without testing them.** Every setup ends with browser-specialist verification or an explicit "I could not verify" message.
- **`render-component` before any specialist dispatch.** Without it, automation runs invisibly.
- **The user signs in themselves** — the specialist does not fill credentials at any site.
- **`tunnel-create` is idempotent** — safe to call multiple times with the same domain.
- **`tunnel-login` is idempotent** — safe to call multiple times. It detects state and does the right thing.
- **`tunnel-add-hostname` is idempotent** — safe to call multiple times with the same hostname. No duplicates.
- **DNS propagation takes 1-5 minutes** after tunnel creation. Nameserver propagation takes minutes to 24 hours.
