Skip to main content
Glama
adkdev200

Universal Computer Control MCP

by adkdev200
README.md
<p align="center">
  <img src="images/logo.png" alt="Universal Computer Control MCP Logo" width="400">
</p>

# Universal Computer Control MCP

> **Give your AI agent eyes and hands on any computer.**
> Observe → understand → act → verify. No browser MCPs. No Playwright. Just real control.

![Platform](https://img.shields.io/badge/platform-Windows%20%7C%20Linux-blue)
![Python](https://img.shields.io/badge/python-3.11%2B-blue)
![License](https://img.shields.io/badge/license-MIT-green)
![Tests](https://img.shields.io/badge/tests-158%20passing-brightgreen)
![MCP](https://img.shields.io/badge/MCP-29%20tools-8A2BE2)

A cross-platform **computer control MCP server** that lets an AI agent operate
a Windows or Linux computer like a human: *observe the screen → understand the
UI → choose an action → perform it → observe again → verify → recover if
needed*.

The system deliberately has **no dependency on Chrome MCP, browser MCP,
Playwright MCP or any other external MCP server**. Browsers, IDEs, office
suites, terminals, file managers, Electron apps and canvas-heavy applications
are all just GUI applications. Multiple backends live *inside* the server and
the best available mechanism is chosen automatically per action.

<p align="center">
  <img src="images/architecture.png" alt="Universal Computer Control Architecture" width="800">
</p>

```
                AI Agent / LLM
                       | MCP (stdio)
                       v
        +------------------------------+
        | Universal Computer MCP Server |
        +---------------+--------------+
                        v
        +------------------------------+
        |   Computer Control Engine    |  <- usable WITHOUT MCP
        +---------------+--------------+
            |            |             |
            v            v             v
     Accessibility   Vision/OCR    Physical Input
       Layer            Layer          Layer
      |      |         |      |       |       |
     UIA   AT-SPI     OCR  OpenCV   Mouse  Keyboard
    (Windows)(Linux) Tesseract  VLM  PyAutoGUI  OS APIs
```

## Highlights

- **29 stable MCP tools** under the `computer.` namespace — capabilities, not
  implementation details. No `pyautogui_click`, no `ocr_click`, no
  `playwright_click`.
- **Interaction hierarchy** per action: Windows UIA → Linux AT-SPI → semantic
  element → OCR text → image template → vision-language model → coordinates
  → PyAutoGUI. Every fallback is automatic.

<p align="center">
  <img src="images/fallback_pipeline.png" alt="Fallback Pipeline" width="800">
</p>

- **`computer.observe` is the core tool**: one structured snapshot with
  screen geometry, active window, cursor, OCR'd text and normalized UI
  elements (stable `element_N` ids within a cycle).

<p align="center">
  <img src="images/control_loop.png" alt="Control Loop" width="800">
</p>
- **Smart targets**: `computer.click("Continue")`, `computer.click("element_17")`,
  `computer.click({"x": 500, "y": 300})` — text matching is fuzzy
  (`"Continue"` matches `"continue"` and `"Continue →"`).
- **Verification & recovery**: before/after state comparison (screen dhash,
  OCR set, active window), strict mode that refuses silent no-ops, duplicate
  action suppression ("don't click twice because nothing seemed to happen"),
  ordered fallback attempts.
- **Graceful degradation**: missing UIA/AT-SPI/OCR/OpenCV/vision produce
  warnings, not crashes. PyAutoGUI alone = fully functional server.
- **Multi-monitor + DPI**: virtual-desktop coordinate space, per-monitor
  enumeration, Windows per-monitor-v2 DPI awareness.
- **Security**: permissive/standard/strict modes, command and application
  allowlists, rate limiting, confirmation gates for dangerous actions, safe
  logging (no passwords/tokens/clipboard contents), **global emergency stop**
  (tool + persistent stop-file).
- **Persistence**: `~/.universal-computer/` holds config, state, logs,
  screenshots, observations, sessions — independent of any MCP connection,
  with retention/cleanup.

## Requirements

| | Mandatory | Optional (recommended) |
|---|---|---|
| Python | 3.11+ | |
| Core | `mcp`, `pydantic`, `PyYAML`, `Pillow` | |
| Input | | `PyAutoGUI` (`[input]`) |
| Windows | | `pywinauto`, `pygetwindow`, `psutil` (`[windows]`) |
| Linux | | `psutil` (`[linux]`), system `wmctrl`, `xdotool`, `xclip` |
| Screenshots | | `mss` (`[screenshot]`), `scrot` on X11 |
| OCR | | `pytesseract` + system `tesseract-ocr` (`[ocr]`) |
| Vision | | `opencv-python`, `numpy` (`[vision]`) |
| VLM | | `httpx` (`[vlm]`) + an OpenAI-compatible endpoint |

## Installation

### Windows 10/11

```powershell
git clone https://github.com/adkdev200/universal-computer-control.git && cd universal-computer-control
powershell -ExecutionPolicy Bypass -File scripts\install_windows.ps1
```

Manual steps:

1. Install Python 3.11+ (`winget install -e --id Python.Python.3.12`), tick
   *Add to PATH*.
2. `python -m venv .venv && .venv\Scripts\pip install -e ".[input,windows,ocr,vision,screenshot,vlm]"`
3. Install Tesseract OCR: `winget install -e --id UB-Mannheim.TesseractOCR`
   (set `ocr.tesseract_cmd` in `config.yaml` if it is not on `PATH`).
4. Run: `.venv\Scripts\universal-computer-control` (alias: `.venv\Scripts\ucc`)

Windows UI Automation needs no extra permissions. If you run the server
elevated, non-elevated windows may refuse automation (UIA integrity levels).

### Linux (X11 or XWayland)

```bash
git clone https://github.com/adkdev200/universal-computer-control.git && cd universal-computer-control
bash scripts/install_linux.sh --yes     # add --no-sudo to skip system packages
./.venv/bin/ucc-doctor                  # verify every backend, exact fix hints
```

The installer is fully non-interactive with `--yes`: it installs system
packages (wmctrl/xdotool/xclip/scrot/tesseract/at-spi2-core), all Python
extras, and a ready `~/.universal-computer/config/config.yaml` with OCR,
template matching and the VLM provider enabled. `ucc-doctor` then probes every
backend live and prints copy-paste fix commands for anything missing. Note:
even on machines with **no** wmctrl/xdotool/xclip installed, the Linux window
backend now works through a built-in python-Xlib (EWMH/ICCCM) fallback - the
system tools only make it more robust.

Manual steps:

1. Python 3.11+ and system packages:

   ```bash
   sudo apt install python3 python3-venv python3-pip \
                    wmctrl xdotool xclip scrot \
                    tesseract-ocr at-spi2-core
   ```

2. `python3 -m venv .venv && ./.venv/bin/pip install -e ".[input,linux,ocr,vision,screenshot,vlm]"`
3. Run from a graphical session: `./.venv/bin/universal-computer-control` (alias: `./.venv/bin/ucc`)

**AT-SPI requirements** (accessibility): `at-spi2-core` must be running (it is
on desktop distros) and for GNOME/GTK apps enable
`gsettings set org.gnome.desktop.interface toolkit-accessibility true`.

**X11 vs Wayland**: X11 is fully supported. On Wayland-with-XWayland, input
and screenshots work through XWayland but foreign window management may be
restricted by the compositor. On pure Wayland (no `DISPLAY`), the window
backend disables itself with a warning; AT-SPI still works for cooperating
toolkits. See `docs/TROUBLESHOOTING.md`.

### Registering the server with an MCP client

`claude_desktop_config.json` (see `examples/mcp-config.example*.json`):

```json
{
  "mcpServers": {
    "universal-computer": {
      "command": "/absolute/path/to/universal-computer-control/.venv/bin/python",
      "args": ["-m", "universal_computer"],
      "env": {}
    }
  }
}
```

Any MCP client that speaks stdio works the same way. The engine is also a
plain library — no MCP required:

```python
from universal_computer import ComputerControlEngine, load_config

computer = ComputerControlEngine(load_config())
obs = await computer.observe("normal")
await computer.click("Continue")
await computer.type("hello")
```

## Tools (MCP API)

| Group | Tools |
|---|---|
| Observation | `computer.observe` (levels: minimal/normal/full) · `computer.screenshot` · `computer.get_ui_tree` · `computer.get_active_window` · `computer.find_text` · `computer.find_visual` · `computer.wait_for` |
| Mouse | `computer.click` · `computer.double_click` · `computer.right_click` · `computer.move_mouse` · `computer.drag` · `computer.scroll` |
| Keyboard | `computer.type` · `computer.press` · `computer.hotkey` |
| Windows | `computer.list_windows` · `computer.focus_window` · `computer.minimize_window` · `computer.maximize_window` · `computer.restore_window` · `computer.close_window` (needs `confirm=true`) |
| Clipboard | `computer.get_clipboard` · `computer.set_clipboard` |
| System | `computer.launch_application` · `computer.run_command` (allowlist + `confirm=true`) |
| Admin | `computer.backend_status` · `computer.emergency_stop` · `computer.reset_emergency_stop` |

`computer.observe` returns a normalized snapshot:

```json
{
  "ok": true,
  "id": "obs_17",
  "level": "normal",
  "screen": {"width": 1920, "height": 1080, "monitors": [...]},
  "active_window": {"title": "Google Chrome", "application": "chrome.exe", "process_id": 1234},
  "cursor": {"x": 812, "y": 530},
  "elements": [
    {"id": "element_1", "role": "button", "name": "Continue",
     "bbox": [1050, 700, 1170, 750], "source": "uia", "confidence": 0.99}
  ],
  "ocr": [
    {"text": "Continue", "bbox": [1050, 700, 1170, 750], "confidence": 0.97}
  ]
}
```

`computer.backend_status` reports per-backend health:

```json
{
  "pyautogui": {"available": true, "healthy": true},
  "uia":       {"available": true, "healthy": true},
  "atspi":     {"available": false, "healthy": false,
                "error": "pyatspi import failed (...); install python3-pyatspi"},
  "ocr":       {"available": true, "healthy": true}
}
```

## Configuration

`config.yaml` (see `config.example.yaml`) plus `UCC_` environment overrides
with `__` nesting:

```bash
UCC_ENGINE__VERIFICATION_MODE=strict
UCC_SECURITY__MODE=standard
UCC_SECURITY__ALLOWED_COMMANDS='["ls", "git status", "xdotool"]'
UCC_LOGGING__LEVEL=DEBUG
UCC_VISION__VLM__ENABLED=true
```

Configurable: observation level & caching, verification mode
(off/basic/strict), PyAutoGUI pauses/failsafe, OCR provider & language,
vision threshold, template directory, VLM endpoint (key read from the
environment variable named in `vision.vlm.api_key_env`), security mode,
allowlists, rate limits, timeouts, persistence retention, log levels and
per-backend priorities (`backends.priorities: {pyautogui: 5}`).

## Security model

| Mode | `run_command` | `launch_application` |
|---|---|---|
| `permissive` | allowed (still rate-limited) | allowed |
| `standard` (default) | requires `security.allowed_commands` **and** `confirm=true` | allowed unless a non-empty `allowed_applications` says otherwise |
| `strict` | only explicitly allowlisted; shell strings off unless `allow_shell` | exact allowlist match required |

Always on: rate limiting (`max_actions_per_minute`), command timeouts,
credential redaction in logs and command output, no clipboard/typed-text
logging, `close_window`/`run_command` confirmation gates, and the
**emergency stop** — `computer.emergency_stop()` halts every subsequent
action (checked before each attempt) and is persisted as a stop file so it
survives restarts and can be triggered externally.

## Extending

- **New OCR**: implement `universal_computer.vision.base.OCRProvider`
  (`detect_text(image) -> list[OCRResult]`) and register it in
  `VisionServices.from_config`.
- **New vision model**: implement `VisionLanguageModel.locate_element`
  (async, description → `VisionResult` with bbox). The OpenAI-compatible
  implementation shows the JSON contract.
- **New platform (macOS)**: subclass the `backends.base` ABCs
  (`InputBackend`, `ScreenshotBackend`, `AccessibilityBackend`,
  `WindowManagementBackend`, `ClipboardBackend`, `ApplicationBackend`) and
  register them in `core/bootstrap.py`. Nothing else changes — the engine
  only knows capabilities.
- **Template images**: put PNGs in `vision.template_dir`; refer to them by
  name from `computer.find_visual` or as click targets.

## Development

```bash
pip install -e ".[dev]"
pytest tests            # 160 unit + integration tests, no display needed
ruff check src tests
```

`scripts/e2e_live_test.py` is an end-to-end harness that boots its own
Xvfb + openbox session, creates real X11 windows, and exercises every
backend live (screenshots, OCR, window ops via both the tool paths and the
python-Xlib fallback, OpenCV template matching, the VLM pipeline against a
mock OpenAI-compatible endpoint, clipboard, input, launch, emergency stop):

```bash
python scripts/e2e_live_test.py             # full stack
python scripts/e2e_live_test.py --no-tools  # zero X11 tools: Xlib fallback
```

Unit tests run on any machine (OS-specific pieces are mocked). The
integration test boots the real server over stdio. See
`docs/ARCHITECTURE.md` for the module map and `docs/TROUBLESHOOTING.md` for
platform-specific fixes.

## Contributors & Acknowledgments

- **[Z.ai](https://z.ai)** (GLM) — co-developer: MCP server, cross-platform
  backends, vision pipeline, python-Xlib window fallback and `ucc-doctor`
  diagnostics
- **Claude Code** (Anthropic) — co-developer: code generation, testing and
  plug-and-play hardening
- **[@adkdev200](https://github.com/adkdev200)** — creator & maintainer

This project is built collaboratively with AI pair-programming: the majority
of the codebase was written and reviewed by Z.ai's GLM and Anthropic's Claude
Code under the direction of the maintainer.

TDQS

B3.4/5.0

Scored across 29 tools

Disambiguation4/5

Most tools target a single, distinct action or resource, and the shared target resolution rules do not create real confusion. The main overlap is that observe subsumes screenshot and get_ui_tree, while find_text and find_visual both locate on-screen targets, but their text-vs-visual distinction keeps them usable.

Naming Consistency4/5

The majority of tool names follow a clear action_object snake_case pattern, such as focus_window, get_clipboard, and list_windows. A few outliers like observe, screenshot, backend_status, and emergency_stop break the verb_noun pattern but remain readable and stylistically consistent.

Tool Count2/5

At 29 tools, the namespace is too large for a single agent-facing surface; the selection cost is significant even though the tools cluster into clear functional groups. Some observation tools such as screenshot, observe, and get_ui_tree could be consolidated, which would reduce count without losing capability.

Completeness5/5

The set covers the full GUI automation loop: observing state, searching for targets, input, window lifecycle, clipboard, launching applications, shell commands, and safety controls. There are no obvious dead ends, and wait_for plus backend_status close the verification and error-handling gaps.

Maintenance

ActivityMaintained
ResponsivenessNo issues