Initial wiki pages: Home, MCP Setup, Policy Guide, Troubleshooting

megaproxy 2026-05-26 18:49:02 +01:00
parent f7b0b92903
commit 8330cb434e
4 changed files with 383 additions and 1 deletions

22
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_<version>_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.

153
MCP-Policy-Guide.md Normal file

@ -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: <reason>`. 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.<fs> /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

@ -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_<version>_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:<port>/mcp --allow-http --header "Authorization: Bearer <token>"`.
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

129
Troubleshooting.md Normal file

@ -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.