Skip to main content
Glama
README.md
# mango-mcp

An MCP server that gives an agent its own computer to use: a private, headless
[mango](https://github.com/mangowm/mango) desktop running next to yours.
The agent launches apps, takes screenshots, and drives the mouse and keyboard
there. None of it reaches your session: your cursor stays put, your focus is
never taken, nothing appears on your screen, and your typing never lands in the
agent's windows.

You do not need to use mango yourself. The agent's desktop is a separate
headless compositor, so this works from any desktop (GNOME, KDE, Sway,
Hyprland, ...), or none.

## Install

### Nix (any distro)

The flake bundles everything (mango, `grim`, `wtype`, `dbus-daemon`):

```sh
claude mcp add --scope user mango-desktop -- nix run github:Anti-2k/mango-mcp
```

Or build a local copy: `git clone https://github.com/Anti-2k/mango-mcp && cd
mango-mcp && nix build`, then point your client at `result/bin/mango-mcp`.

### Other distros

1. Install mango and the helper tools:

   | Distro | Command | mango |
   |---|---|---|
   | Arch | `sudo pacman -S mangowm grim wtype dbus` | official repos |
   | Fedora | `sudo dnf install mangowm grim wtype dbus-daemon` | needs the [Terra repo](https://mangowm.github.io/docs/installation#fedora) |
   | Gentoo | `sudo emerge gui-wm/mangowm gui-apps/grim gui-apps/wtype` | GURU overlay |
   | Guix | `guix install mangowm grim wtype dbus` | |
   | Debian, Ubuntu | `sudo apt install grim wtype dbus-daemon` | [build from source](https://mangowm.github.io/docs/installation#building-from-source) |
   | openSUSE | `sudo zypper install grim wtype dbus-1` | [see the guide](https://mangowm.github.io/docs/installation) |
   | Void | `sudo xbps-install grim wtype dbus` | build from source |

   Package names drift; [repology](https://repology.org/project/mangowm/versions)
   has the current ones. mango 0.12 and newer (including 0.18+) are supported.

2. Install the server (Python 3.10+) with [uv](https://docs.astral.sh/uv/) or
   pipx:

   ```sh
   uv tool install git+https://github.com/Anti-2k/mango-mcp
   # or: pipx install git+https://github.com/Anti-2k/mango-mcp
   ```

   The prebuilt `pywayland` wheels need glibc 2.34+ on x86_64 (Debian 12,
   Ubuntu 22.04, Fedora 35 and later). Elsewhere pip builds it from source,
   which needs the Wayland development headers and libffi.

3. Check that everything is found, then register it:

   ```sh
   mango-mcp check
   claude mcp add --scope user mango-desktop -- mango-mcp
   ```

Any other MCP client: run `mango-mcp` as a stdio server. The desktop starts on
the first tool call and is torn down, along with every app the agent started,
when the server exits.

### Options (environment variables)

| Variable | Default | Meaning |
|---|---|---|
| `MANGO_MCP_NAME` | `agent` | Desktop name. A second server with the same name gets `agent-2`, and so on, so two agents never share one desktop. |
| `MANGO_MCP_RESOLUTION` | `1920x1080` | Size of the headless screen. |
| `MANGO_MCP_TAGS` | `1-9` | Tags the agent may use, e.g. `1,3` or `2-4`. Other tags are refused. |
| `MANGO_MCP_SHARE_HOME` | unset | `1` runs agent apps with your real `$HOME`. See below before turning this on. |
| `MANGO_MCP_PASS_ENV` | unset | Comma-separated variables to pass through anyway, e.g. `SSH_AUTH_SOCK`. The session-pointing ones (`WAYLAND_DISPLAY`, `DISPLAY`, `DBUS_SESSION_BUS_ADDRESS`, `XDG_RUNTIME_DIR`, ...) cannot be passed. |
| `MANGO_MCP_COMPOSITOR` | bundled `mango` | Use a different mango binary. 0.12 (dwl-ipc) and 0.18 (socket IPC) are both supported. |

## Tools

| Tool | |
|---|---|
| `desktop_info` | Screen size, tags (which are visible, window counts), focused window |
| `screenshot` | Whole screen or a region; `scale` < 1 to save tokens |
| `zoom` | A region magnified (up to 4x by default) to read small text |
| `launch` | Run a shell command on the desktop, optionally on a given tag; reports the new window |
| `bash` | Run a short command in the desktop's environment and return its output (killed after `timeout`) |
| `list_windows`, `focus_window`, `close_window`, `window_action` | Window management (`toggle_floating`, `toggle_fullscreen`, `kill`) |
| `view_tags`, `move_window_to_tags` | Tags, limited to `MANGO_MCP_TAGS` |
| `mouse_move`, `click`, `drag`, `scroll` | Pointer, in screen pixels |
| `type_text`, `press_keys` | Keyboard: any Unicode text regardless of layout; combos like `ctrl+shift+t` |
| `wait`, `restart_desktop` | |

## Watching the agent

```sh
eval "$(result/bin/mango-mcp env agent)"   # point this shell at the agent desktop
grim /tmp/agent.png                         # one-off screenshot
nix run nixpkgs#wayvnc -- -d 127.0.0.1 5900 # live view in any VNC viewer
```

Keep the `-d` (`--disable-input`) on wayvnc. Without it, input from the viewer
goes to the agent's seat and you would be interfering with the agent.

## How the isolation works

The agent gets a separate compositor rather than a corner of yours. Mango, like
dwl, has one seat (one cursor, one keyboard focus), so inside a single session
any synthetic click or keystroke would have to move your cursor and take your
focus.

| Way the two sides could interfere | What prevents it |
|---|---|
| Your keyboard and mouse reaching the agent | The agent's mango runs on the headless backend with no libinput devices. It has no physical input at all. |
| Agent input reaching your windows | Input goes in through the virtual-pointer and virtual-keyboard protocols, over the agent's own Wayland socket. Nothing uses `/dev/uinput`, ydotool or portals. |
| Agent apps opening on your screen | Every process gets a freshly built environment: its own `XDG_RUNTIME_DIR` (`$XDG_RUNTIME_DIR/mango-mcp/<name>`), a distinct `WAYLAND_DISPLAY`, and the agent's own Xwayland `DISPLAY`. `TMUX`, `KITTY_*`, `NVIM`, `SWAYSOCK`, `MANGO_INSTANCE_SIGNATURE`, ssh/gpg agent variables and similar are dropped. |
| Single-instance apps handing off to your copy | A private D-Bus session bus, plus by default a separate `$HOME` (`~/.local/share/mango-mcp/<name>/home`), which holds the profile locks Chromium, Electron and Firefox use. |
| Notifications, portals and other D-Bus services showing up on your desktop | The private bus. Services activated on it inherit the agent's display. |
| mango rewriting your session's environment | mango runs `dbus-update-activation-environment` and `systemctl --user import-environment` at startup, and from 0.18 stops `graphical-session.target` when it exits. The agent's mango never gets `DBUS_SESSION_BUS_ADDRESS`, which makes it skip all of that, and it also runs with no-op `systemctl` and `dbus-update-activation-environment` shims on its `PATH`. |
| Compositor keybindings | The agent's config has no binds and no autostart (`exec-once`). VT-switch and server-kill keysyms are refused. |
| Two agents fighting over one desktop | Each server takes an exclusive lock on its desktop name. |

`tests/test_live.py` checks this end to end: it starts a desktop, drives a
terminal in it, then asserts that your `systemctl --user show-environment` and
the contents of your runtime directory are unchanged.

### Limits

This keeps the agent and you from getting in each other's way. **It is not a
security sandbox.** Agent processes run as your user, with your files and
network. A process that deliberately sets `WAYLAND_DISPLAY=wayland-0
XDG_RUNTIME_DIR=/run/user/$UID` can still reach your session. Some leaks also
bypass the environment entirely:

- `gpg` finds its agent at a fixed path, so signing inside the agent desktop can
  pop a pinentry dialog on *your* screen.
- `/tmp/.X11-unix` is shared, so an agent app explicitly told `DISPLAY=:0`
  would reach your Xwayland.

Covering those needs mount namespaces (e.g. bubblewrap). That is not done here.

With `MANGO_MCP_SHARE_HOME=1`, an app that is already running on your desktop
(Chromium, VS Code, Discord, ...) may receive the agent's launch and open the
window on your screen. To use those apps with a shared home, give them a
separate profile (`--user-data-dir`, `firefox --profile ... --new-instance`).

## License

MIT, see [LICENSE](LICENSE). The Wayland protocol XML files in
`mango_mcp/protocols/` are third-party and keep their own terms.

## Development

```sh
nix develop
python -m pytest                    # unit tests
MANGO_MCP_LIVE=1 python -m pytest   # plus the live end-to-end test (needs kitty)
MANGO_MCP_COMPOSITOR=/path/to/mango MANGO_MCP_LIVE=1 python -m pytest   # against another mango
```

Logs: `~/.local/state/mango-mcp/server.log`; per-desktop `mango.log`,
`dbus.log` and per-app logs live in the desktop's runtime directory while it
runs.