1 unstable release
Uses new Rust 2024
| 0.1.0 | May 3, 2026 |
|---|
#211 in Unix APIs
99KB
2K
SLoC
AppMaker MCP
An MCP server that lets agents see and control native GUI applications inside an isolated, nested X11 display.
The agent gets its own private desktop. It launches apps there, takes screenshots, moves the pointer, types, drags, scrolls — without touching your real session, and without the apps ever knowing they aren't on a real screen.
Index
Why
LLM agents are great at reasoning about UIs, but they need a sandbox to act in. AppMaker MCP gives them one: a nested X11 display where they can spawn arbitrary apps, observe the pixels, and drive input — all isolated from your real desktop, all reversible by killing one process.
Features
- Isolated display. Each session runs inside its own Xephyr window. Tearing it down kills every app spawned in it.
- Visible by default. You can watch the agent work in real time. A software cursor makes pointer movement easy to follow.
- Real input, paced. Mouse movement is interpolated over time and clicks honor configurable hold/pre-press delays, so apps see input the way a human would produce it.
- Screenshots with cursor markers. Optional synthetic cursor overlay shows where the pointer is and whether a button is currently held.
- Window awareness. List, focus, and kill top-level windows by ID or title.
- Opaque interface. Tools speak in displays, coordinates, and window titles — no leaking
xdotoolorXephyrflags into agent prompts.
Quick start
Prerequisites
Linux with the following packages available on PATH:
Xephyr(usually packaged asxserver-xephyr)fluxboxxdotoolscrotor ImageMagick'simport
On Debian/Ubuntu:
sudo apt install xserver-xephyr fluxbox xdotool scrot
Install
cargo install app-maker-mcp
Build
git clone https://github.com/mii-nipah/app-maker-mcp
cd app-maker-mcp
cargo build --release
The binary lands at target/release/app-maker-mcp. It speaks MCP over stdio.
Wire it up to your MCP client
Example configuration snippet:
{
"mcpServers": {
"app-maker": {
"command": "app-maker-mcp"
}
}
}
First session
From the agent's side, a typical flow looks like this:
appmaker-start— spin up a nested display (returns something like:99).appmaker-runwithcommand: "xterm"— launch an app inside it.appmaker-screenshotwithshow_cursor: true— see what's on screen.appmaker-send-click,appmaker-send-keyboard, ... — interact.appmaker-stop— tear everything down.
Tools
| Tool | Purpose |
|---|---|
appmaker-start |
Start the nested display + window manager. Returns the DISPLAY value. |
appmaker-stop |
Stop the display and kill every app inside it. |
appmaker-run |
Run a command inside the nested display. |
appmaker-screenshot |
Save a screenshot, optionally overlay a cursor marker, and return the image to the client. |
appmaker-list-windows |
List visible top-level windows. |
appmaker-focus-window |
Focus a window by ID or title. |
appmaker-kill-window |
Kill a window by ID or title. |
appmaker-send-keyboard |
Type text or send key expressions (Return, ctrl+s, ...). |
appmaker-send-move |
Smoothly move the pointer to a position. |
appmaker-send-click |
Move and click at a position. |
appmaker-send-double-click |
Move and double-click at a position. |
appmaker-send-mouse-down / -up |
Press / release a button (for held interactions). |
appmaker-send-drag |
Drag from one point to another with a held button. |
appmaker-send-scroll |
Send scroll wheel events. |
Every motion-bearing tool exposes timing knobs (move duration, pre-click delay, hold duration, ...) so the agent can match an app's input expectations.
How it works
┌──────────────────┐ stdio (MCP) ┌──────────────────────┐
│ MCP client │ ◄───────────────► │ app-maker-mcp │
│ (agent / IDE) │ │ (Rust, this crate) │
└──────────────────┘ └──────────┬───────────┘
│ spawns + supervises
▼
┌──────────────────────────────────┐
│ Xephyr :N (nested X11 server) │
│ ├── fluxbox (window manager) │
│ └── user apps (DISPLAY=:N) │
└──────────────────────────────────┘
▲
xdotool / scrot │ input + screenshots
AppSessionManager owns the Xephyr + fluxbox lifecycle and tracks every child process spawned through appmaker-run, so appmaker-stop can reliably reap the whole tree. Pointer movement is interpolated in small steps to produce realistic motion, and the screenshot path optionally rasterizes a synthetic cursor (idle vs. pressed style) onto PNG output.
Limitations
- Linux + X11 only. Pure Wayland-only apps won't run. Most toolkits can be coaxed into X11 mode (
QT_QPA_PLATFORM=xcb,GDK_BACKEND=x11,--ozone-platform=x11). - Visible nested window. Xephyr opens a window on your real display. That's by design — humans need to be able to watch — but it means there is no headless mode yet.
- One session per server process. The current
AppSessionManagermanages a single nested display at a time.
Contributing
Issues and PRs welcome. A few guidelines:
- Keep tool surfaces opaque. Agents shouldn't need to learn about
xdotoolflags to use a tool — translate at the boundary. - Prefer adding timing knobs over hard-coding behavior; different apps need different pacing.
- Run
cargo fmtandcargo clippybefore sending a PR.
For larger changes, open an issue first to discuss the design.
License
BSD-3-Clause. See LICENSE.
Dependencies
~27–38MB
~621K SLoC