From 8330cb434ec0496d95e3d21dabcf4bb8f4990bf4 Mon Sep 17 00:00:00 2001 From: megaproxy Date: Tue, 26 May 2026 18:49:02 +0100 Subject: [PATCH] Initial wiki pages: Home, MCP Setup, Policy Guide, Troubleshooting --- Home.md | 22 +++++- MCP-Policy-Guide.md | 153 ++++++++++++++++++++++++++++++++++++ MCP-Setup-Claude-Desktop.md | 80 +++++++++++++++++++ Troubleshooting.md | 129 ++++++++++++++++++++++++++++++ 4 files changed, 383 insertions(+), 1 deletion(-) create mode 100644 MCP-Policy-Guide.md create mode 100644 MCP-Setup-Claude-Desktop.md create mode 100644 Troubleshooting.md diff --git a/Home.md b/Home.md index dcf2c80..7516497 100644 --- a/Home.md +++ b/Home.md @@ -1 +1,21 @@ -# Placeholder +# tiletopia + +A Windows desktop app for running and arranging many WSL terminals at once. Built for managing multiple `claude` sessions across projects in parallel; works for any multi-shell workflow. + +## Install + use + +- **Install**: grab the latest `tiletopia__x64-setup.exe` from the [releases page](https://git.rdx4.com/megaproxy/tiletopia/releases). Run it, accept SmartScreen ("More info → Run anyway" — not code-signed), launch from Start. +- **Daily use**: split / close / swap panes with the toolbar buttons or `Ctrl+Shift+E` / `O` / `W`. `Ctrl+K` for the jump-to-pane palette. `F1` for the in-app help overlay listing every shortcut. +- **README** in the repo has the full feature list, install requirements, and source-build instructions: [README.md](https://git.rdx4.com/megaproxy/tiletopia/src/branch/main/README.md). + +## What's in this wiki + +- **[MCP Setup for Claude Desktop](MCP-Setup-Claude-Desktop)** — one-click `.mcpb` install walkthrough. Lets a Claude Desktop chat read scrollback, send keystrokes, spawn panes, and reshape your workspace. +- **[MCP Policy Guide](MCP-Policy-Guide)** — how the three-tier `allow / ask / deny` permission model works, example rules, SSH safeguards, and the compiled-in hard-deny list (catastrophic patterns Claude is never allowed to run). +- **[Troubleshooting](Troubleshooting)** — fixes for the common pain points: MCP `401 Unauthorized`, WSL networking, Windows Firewall, panes not going idle, etc. + +## For contributors + +- Source on [Forgejo](https://git.rdx4.com/megaproxy/tiletopia). +- `memory.md` at repo root tracks decisions, session logs, and open follow-ups — read it before starting work. +- Build and run prereqs live in the [README's "Build from source"](https://git.rdx4.com/megaproxy/tiletopia/src/branch/main/README.md#build-from-source) section. TL;DR: Rust toolchain runs on Windows host only; the source dir is symlinked into WSL for editing convenience but `pnpm` and `cargo` commands must target the `D:\` (or wherever) Windows path. diff --git a/MCP-Policy-Guide.md b/MCP-Policy-Guide.md new file mode 100644 index 0000000..e08e33d --- /dev/null +++ b/MCP-Policy-Guide.md @@ -0,0 +1,153 @@ +# MCP Policy Guide + +How tiletopia's MCP permission model works, and how to tune it. + +## Three-tier model: `allow / ask / deny` + +Every MCP write call (anything that mutates state) gets matched against three rule buckets in this order. **Deny-first** precedence: a matching deny rule wins over any allow. + +| Bucket | What happens on match | +|---|---| +| **deny** | Call is refused server-side with `denied: `. Claude sees the error and can adapt or apologize. | +| **ask** | Confirm modal pops in tiletopia. You **Approve / Reject / Always-Allow** per call. | +| **allow** | Call runs silently. No modal. Audit log still records it. | + +**Default policy is empty.** No allow / ask / deny rules out of the box. Every non-hard-denied call falls through to "no matching rule → ask" — the most restrictive default. + +Configured in the panel's **Policy** tab; persisted to `%APPDATA%\com.megaproxy.tiletopia\mcp-policy.json`. + +## Rule syntax + +A rule is either a **bare tool name** or `tool_name(glob)`: + +| Example rule | What it matches | +|---|---| +| `set_label` | Any `set_label` call regardless of args | +| `write_pane` | Any `write_pane` call regardless of text | +| `write_pane(git push *)` | Only `write_pane` calls whose text starts with `git push ` | +| `write_pane(rm -rf /home/me/scratch/*)` | The exact path prefix | +| `apply_preset(two_columns)` | Only `apply_preset` calls naming the `two_columns` preset | +| `write_pane(* main)` | Anything ending in ` main` (useful for blocking `git push origin main`) | + +Globs use `*` as wildcard only — no regex, no character classes. The glob matcher is **shell-operator-aware**: a rule fires if **any subcommand** of the input matches it. So `write_pane(rm *)` matches `echo hi && rm tmp.txt`. This is intentional so a single rule guards the obvious destructive pattern without you listing every shell-operator variant. + +## SSH safeguards (three switches in the Policy tab) + +SSH gets extra gates above the regular policy because the bytes leaving tiletopia get interpreted by a remote shell — alias expansion, subshells, and `~` expansion all happen on the other side, where local hard-deny patterns can't see. + +| Switch | Default | What it gates | +|---|---|---| +| `allow_open_ssh` | **off** | `connect_host` and `spawn_pane(kind=ssh)`. When off, Claude can't open SSH sessions at all — you do it manually via the titlebar 🔑 picker. | +| `auto_allow_spawned_ssh` | **off** | Whether SSH panes Claude spawns start with the 🤖 chip on (visible to MCP). When off, you must toggle each one explicitly. | +| `allow_add_host` | **off** | `add_host` and `delete_host` — Claude editing the saved-hosts list. | + +Stacked off, Claude can do nothing SSH-related on its own. The user manages SSH through the UI; Claude only sees panes you've already opened AND chosen to allow. + +`add_host`'s `extraArgs` are **sanitised** even when `allow_add_host` is on — Claude can't sneak in `-o ProxyCommand=...`, `-o LocalCommand=...`, `-o KnownHostsCommand=...`, or `-o PermitLocalCommand=yes`. Those are all CVE-2023-51385-class local-RCE primitives that run on connect. + +## Hard-deny: the patterns you can't disable + +A compiled-in list of 14 catastrophic patterns is checked against every `write_pane` text **before** any user policy. The user can NOT disable these. They're visible (greyed out) in the **Always blocked** section at the bottom of the Policy tab. + +### POSIX shell (10) + +| Label | Catches | +|---|---| +| `rm -rf /` | `rm -rf /` and case variants (`-Rf`, `-Rrf`), with trailing shell operators or `#` comments. | +| `rm -rf ~` | `rm -rf ~`, `rm -rf $HOME`. | +| `rm -rf /*` | The glob form. | +| `fork bomb` | `:() { :\|:& }; :` and whitespace variants. | +| `mkfs on device` | `mkfs. /dev/...`. | +| `dd to raw disk` | `dd of=/dev/{sd,nvme,hd,disk}...`. | +| `overwrite system auth file` | `... > /etc/{passwd,shadow,sudoers}`. | +| `pipe to shell from network` | `curl ... \| bash`, `wget ... \| sudo sh`, `curl ... \| zsh`, etc. | +| `chmod -R 777 /` | Anchored to the literal `/` root (no false-positive on `/tmp`). | +| `find / -delete` | `find / ... -delete`. | + +### PowerShell / cmd (4) + +| Label | Catches | +|---|---| +| `Remove-Item C:\ / ~` | `Remove-Item -Recurse -Force C:\`, `del C:\`, `rd /S /Q ~`, etc. Also matches `$env:USERPROFILE` / `$env:APPDATA` targets. | +| `Format-Volume / Clear-Disk` | Bare cmdlet mentions are fine (e.g. `Get-Help Format-Volume`); presence of a flag triggers. | +| `iwr \| iex (PowerShell)` | `Invoke-WebRequest \| Invoke-Expression` pipe form. | +| `iex (irm ...) (PowerShell)` | `iex (irm ...)` parenthesised form. | + +### Important caveats + +**Hard-deny is best-effort accident prevention, not a sandbox.** Bypasses exist: + +- `\rm`, aliases (`alias rm=...`), variable indirection (`$SHELL -c 'rm -rf /'`). +- Anything that happens after the bytes reach the shell: `~` expansion, command substitution, `eval`, sourcing files. +- SSH targets — the remote shell sees only the bytes we send; nothing local can introspect what they'll become. + +The list catches honest mistakes and obvious copy-paste attacks. A determined attacker who already has write access to the shell can defeat it. + +## Worked examples + +### "Let Claude rename and reshape, ask before anything else" + +```json +{ + "allow": ["set_label", "promote_pane", "swap_panes", "apply_preset"], + "ask": [], + "deny": [] +} +``` + +Everything else falls through to the default ask. + +### "Allow Claude to send any non-destructive bash, ask before destructive" + +```json +{ + "allow": ["write_pane(*)"], + "ask": [ + "write_pane(rm *)", + "write_pane(git push *)", + "write_pane(sudo *)", + "write_pane(* --force*)" + ], + "deny": [] +} +``` + +The `ask` rules win over the broad `allow` because of deny-first → ask-second precedence. (Allow being a `*` glob doesn't override more specific asks.) + +### "Block all write_pane on production hosts" + +You can't directly key rules on shell-kind today (it's a known follow-up). Workaround: target by the literal hostname in the text: + +```json +{ + "deny": [ + "write_pane(* prod-*)", + "write_pane(* deploy *)" + ] +} +``` + +### "Trust Claude with write_pane on local panes, ask on SSH" + +Same gap. Workaround today is the broader `auto_allow_spawned_ssh` safeguard set to off, which keeps every Claude-spawned SSH pane out of MCP visibility unless you flip its chip. So Claude can't `write_pane` to SSH without you opting in per-pane. + +## Audit log + +Every tool call — denied, asked, or allowed — appears in the panel's **Audit** tab. + +- Last 200 entries, ephemeral (cleared on app restart). +- Each row: timestamp, tool name, truncated args summary (80 chars), result (ok / denied / failed), duration. +- `write_pane` args are truncated and control-chars escaped so pasted tokens don't leak into the UI verbatim. + +If Claude does something surprising, the audit log is the first place to look. + +## Files on disk + +- `%APPDATA%\com.megaproxy.tiletopia\mcp-policy.json` — your rules + safeguards. Atomic tmp+rename writes; safe to back up or sync. +- `%APPDATA%\com.megaproxy.tiletopia\mcp.json` — port + bearer token. Don't share. +- `%APPDATA%\com.megaproxy.tiletopia\hosts.json` — saved SSH hosts (no passwords; those are in Windows Credential Manager). + +## See also + +- [MCP Setup for Claude Desktop](MCP-Setup-Claude-Desktop) — getting the `.mcpb` bundle installed +- [Troubleshooting](Troubleshooting) — connectivity issues if Claude can't reach the server diff --git a/MCP-Setup-Claude-Desktop.md b/MCP-Setup-Claude-Desktop.md new file mode 100644 index 0000000..6101b43 --- /dev/null +++ b/MCP-Setup-Claude-Desktop.md @@ -0,0 +1,80 @@ +# MCP Setup for Claude Desktop + +One-click install via the bundled `.mcpb` extension. After this, a Claude Desktop chat can read your tiletopia panes, send commands, spawn new panes, etc. — subject to the policy you configure (see [MCP Policy Guide](MCP-Policy-Guide)). + +## Prerequisites + +- **tiletopia** ≥ 0.2.x installed and running ([Releases](https://git.rdx4.com/megaproxy/tiletopia/releases)) +- **Claude Desktop** installed +- **Node 18+** on your PATH (the bundle wrapper uses `npx mcp-remote`; check with `node --version` in PowerShell) +- The tiletopia MCP server **enabled once** so the per-install bearer token is written to `%APPDATA%` + +## Steps + +### 1. Start the MCP server in tiletopia + +- Click the 🤖 button in the tiletopia titlebar +- In the **Config** tab, click **Server: ON** +- The panel shows a URL like `http://127.0.0.1:47821/mcp` and a bearer token. You don't need to copy them — the `.mcpb` reads them automatically. + +### 2. Download the `.mcpb` bundle + +- In the same panel, click **Download .mcpb**. Your browser opens the Forgejo releases page. +- Download `tiletopia.mcpb` from the latest release (sits next to `tiletopia__x64-setup.exe`). + +(If you're building from source, run `pnpm run build:mcpb` from the repo root — the bundle lands at `dist-mcpb/tiletopia.mcpb`.) + +### 3. Install the bundle in Claude Desktop + +- Open Claude Desktop → **Settings → Extensions** +- **Drag and drop** the `tiletopia.mcpb` file onto the extensions panel +- Claude Desktop will show "tiletopia" with an Install / Enable button +- Restart Claude Desktop (or the extension launcher will spawn on next chat) + +### 4. Allow per-pane visibility + +By default, **no panes are visible to MCP** — even with the server running. Pane visibility is opt-in. + +- In any tiletopia pane's toolbar, click the 🤖 chip. It flips green when enabled. +- Repeat for any pane you want Claude to see / interact with. + +### 5. Test + +In a new Claude Desktop chat: + +> "list my tiletopia panes" + +Claude should call the `tiletopia/list_panes` tool (or a wrapper) and reply with your visible panes. If it says it can't reach the server, see [Troubleshooting](Troubleshooting). + +## How the bundle works (and why it's zero-config) + +The `.mcpb` ships a tiny Node wrapper as its entry point. At launch the wrapper: + +1. Reads `%APPDATA%\com.megaproxy.tiletopia\mcp.json` to get the **live** port and bearer token. +2. Execs `npx -y mcp-remote http://127.0.0.1:/mcp --allow-http --header "Authorization: Bearer "`. +3. Proxies all subsequent JSON-RPC traffic through that shim. + +Implications: + +- **No secrets in the bundle.** Same `.mcpb` works for every tiletopia install — the token is read on the user's own machine at runtime. +- **Token rotation is transparent.** If you click **Regenerate** in the panel, the next Claude Desktop launch picks up the new token automatically. No re-paste. +- **Server must be running when Claude wants to call it.** The wrapper doesn't start tiletopia; the panel does. + +## Why a wrapper / `mcp-remote` shim at all + +Claude Desktop's HTTP-MCP client doesn't currently honor static bearer auth ([anthropics/claude-code#17152](https://github.com/anthropics/claude-code/issues/17152), [#46879](https://github.com/anthropics/claude-code/issues/46879)) — it tries OAuth discovery and gives up if the server doesn't speak the dance. The `mcp-remote` package is an stdio MCP server that internally proxies to an HTTP endpoint with whatever auth headers you specify, sidestepping Claude Desktop's HTTP-MCP path entirely. Once Anthropic ships proper bearer support, the wrapper can be retired in favor of a direct config. + +## What Claude can do once installed + +The full surface is documented in the [MCP Policy Guide](MCP-Policy-Guide). High-level: + +- **Read**: scrollback (`read_pane`), wait for a pane to go quiet (`wait_for_idle`) +- **Drive**: send keystrokes (`write_pane`), spawn new local or SSH panes, close / swap / promote panes, reshape layouts, rename panes +- **Manage SSH**: add or remove saved hosts (gated by an extra Policy-tab switch) + +Every write tool is matched against your policy — by default everything pops a confirm modal you must approve. Add rules to skip prompts you trust; deny outright the ones you don't. + +## See also + +- [MCP Policy Guide](MCP-Policy-Guide) — configure what Claude can do without asking +- [Troubleshooting](Troubleshooting) — connectivity / firewall / WSL networking issues diff --git a/Troubleshooting.md b/Troubleshooting.md new file mode 100644 index 0000000..cd272d5 --- /dev/null +++ b/Troubleshooting.md @@ -0,0 +1,129 @@ +# Troubleshooting + +Common problems and fixes. Ordered by frequency. + +## MCP + +### Claude can't reach tiletopia — `401 Unauthorized` or connection refused + +Check these in order: + +1. **Is the server actually running?** Open the 🤖 panel — it should say `Server: ON` with a URL. If not, click ON. +2. **Did the dev app fully start?** Look in PowerShell for `MCP server listening on http://127.0.0.1:47821/mcp`. If only the GUI is up but no listener was started, the MCP server is off. +3. **Did you toggle the per-pane 🤖 chip?** Even with the server running, panes are default-deny. Claude sees zero panes until you flip the chip on the ones you want exposed. +4. **Bearer token mismatch?** If you regenerated the token in the panel after setting up Claude Desktop, restart the extension (close Claude Desktop, reopen). The `.mcpb` wrapper reads the token fresh at launch, but Claude has to actually relaunch the extension subprocess. +5. **Wrong port?** The default is `47821` but falls back to OS-picked if taken. Check the panel for the actual port; it's also in `%APPDATA%\com.megaproxy.tiletopia\mcp.json`. + +### Claude Desktop says "extension failed to start" or "tiletopia.mcpb invalid" + +- **Node 18+ required.** The wrapper inside the bundle uses `npx`. Open PowerShell, run `node --version` — should be ≥ 18. Install Node from [nodejs.org](https://nodejs.org) if missing. +- **`npx` not on PATH.** Same fix — installing Node puts `npx` next to it. +- **Wrong tiletopia install location.** The wrapper expects `%APPDATA%\com.megaproxy.tiletopia\mcp.json`. If you've installed tiletopia somewhere unusual or never run it once, that file won't exist. Run tiletopia once and start the MCP server to create the file. + +### Idle indicator never clears / always shows red + +If a pane stays red even after typing in it: that's a bug, file an issue. If a pane goes red after 5 s of silence even though something is "running": that's the intended behavior — tiletopia v0.2.x has no notion of "the user is reading output, don't flag idle". A claude-foreground filter was attempted and reverted; see `memory.md` for the gory details. + +### "WSL says firewall is blocking port 47821" + +Windows Defender Firewall auto-creates **Block** rules for new executables hitting the network on the Public profile. Block beats Allow. + +```powershell +# Wipe any auto-created tiletopia rules (run as admin) +Get-NetFirewallApplicationFilter | Where-Object Program -Like "*tiletopia*" | + Get-NetFirewallRule | Remove-NetFirewallRule + +# Allow the MCP port through, all profiles +New-NetFirewallRule -DisplayName "tiletopia MCP" -Direction Inbound ` + -Action Allow -Protocol TCP -LocalPort 47821 -Profile Any +``` + +(Replace `47821` with your actual port if you've customized it.) + +## WSL connectivity + +### Claude running inside WSL can't reach `127.0.0.1:47821` + +WSL2 in default NAT mode does **not** share Windows's `127.0.0.1`. Two options: + +**Option A: Use the WSL gateway IP.** + +Inside WSL: + +```sh +ip route show default | awk '{print $3}' +``` + +Use that IP (typically `172.x.x.1`) instead of `127.0.0.1` in your `.mcp.json` URL. Caveat: the gateway IP **changes after every WSL restart**. + +**Option B: Enable WSL2 mirrored networking.** Win 11 22H2+ only. Edit `%UserProfile%\.wslconfig`: + +```ini +[wsl2] +networkingMode=mirrored +``` + +Then in PowerShell: + +```powershell +wsl --shutdown +``` + +After WSL restarts, `127.0.0.1` inside WSL transparently reaches Windows listeners. This is the better long-term answer if you're on a recent Windows build. + +## Saved SSH passwords + +### "Saved password" doesn't auto-type at the SSH prompt + +- The autotype window is **30 seconds from spawn**. If the SSH handshake is slow (cold connection, network) and the prompt arrives later, the autotyper has already disarmed. +- The matcher looks for `password:` (case-insensitive) or `passphrase` in the recent output. Some custom SSH banners suppress the standard prompt — those won't trigger. +- Check the saved status: in the Manage hosts dialog, the 🔒 icon indicates a stored password. If absent, save one. + +### "Saved passwords missing after Windows reboot" + +The credential store uses DPAPI tied to your Windows account. Things that wipe it: + +- Logging in as a different Windows user +- Domain account password reset by IT (sometimes invalidates DPAPI) +- Migrating to a new machine without exporting credentials + +Saved passwords are convenience, not a sync mechanism. Source-of-truth lives in `hosts.json` for everything except the password itself. + +## Build from source + +### `pnpm install` hangs or crashes with `isDriveExFat` + +You're running pnpm against a `\\wsl.localhost\...` UNC path. pnpm 11.x crashes inside `isDriveExFat` on those. + +**Fix**: clone to a Windows-native drive (e.g. `D:\dev\tiletopia\`). If you want a WSL-side symlink for editing convenience, that's fine — just make sure your PowerShell cwd when running `pnpm` is the Windows path: + +```powershell +cd D:\dev\tiletopia +pnpm install # works +pnpm tauri dev # works +``` + +The Rust toolchain (cargo) lives on the Windows host too; cargo from inside WSL will error on the Windows-targeting deps. + +### `cargo test --lib` panics with `policy_with` field error + +Outdated checkout — the `policy_with` test helper was missing the `ssh_safeguards` field for a couple of days before PR-4 fixed it. Pull main. + +## Tauri / Windows + +### Dev window opens with broken layout / blank panes + +Hard-refresh the WebView2: + +- Right-click anywhere → **Inspect element** → **Console**, type `location.reload()`. +- Or close the window and `pnpm tauri dev` again. + +If it persists, check the console for an error from React / xterm.js and file an issue with the error text. + +### SmartScreen blocks the installer on launch + +Expected — installer isn't code-signed. Click **More info** → **Run anyway**. SmartScreen learns and stops asking after the first install. + +## Still stuck? + +[Open an issue](https://git.rdx4.com/megaproxy/tiletopia/issues/new) with: tiletopia version (`Help → About` or the title bar), Windows build (`winver`), WSL version (`wsl --version`), exact error text or screenshot, and steps to reproduce. The shorter and more reproducible the report, the faster it gets fixed.