Screen Control
by Xeakaes
README.md
# ๐ฅ๏ธ Screen Control
<p align="center">
<a href="LICENSE"><img alt="License: MIT" src="https://img.shields.io/badge/License-MIT-yellow.svg"></a>
<img alt="Platform" src="https://img.shields.io/badge/platform-Windows%2010%2F11-blue">
<img alt="Python" src="https://img.shields.io/badge/python-3.10%2B-informational">
<a href="https://github.com/features/actions"><img alt="CI: security tests" src="https://img.shields.io/badge/CI-security%20tests-brightgreen"></a>
<a href="#mcp-support-one-click-cloud-agents"><img alt="MCP" src="https://img.shields.io/badge/MCP-compatible-9370DB"></a>
</p>
> A local remote-control system for AI agents: watch your computer's screen
> **live** and send **mouse/keyboard commands** to it. Everything runs on your
> own machine โ no data ever leaves it, no cloud middleman.
---
## Why Screen Control?
AI agents today can write code and call APIs โ but they can't *see* or *touch*
your desktop. Screen Control gives any agent general-purpose computer use over
a clean, safety-gated HTTP/MCP interface:
- **Perceive** โ OCR for text, single frames or a live MJPEG stream for
vision-capable models, and a text-only diff endpoint for models that can't
consume images at all.
- **Act** โ absolute and relative mouse, Unicode-safe keyboard, window
management, background (focus-free) control, virtual desktops.
- **Stay safe** โ token auth, blocked deadly shortcuts, focus guard, a
stuck-input watchdog and an emergency failsafe are all enforced server-side,
no matter how confused the agent gets.
One process, zero configuration, works with any language that can speak HTTP โ
or natively through MCP in Claude Desktop, Cursor, VS Code and cloud agents.
### Performance Is Agent-Bound
Screen Control is the **perception and actuation layer** โ the eyes and hands.
The effective speed and capability of any agent using it are bounded by that
agent itself and by the environment it runs in:
- **Thinking speed** โ one action per agent "turn": the perceive โ plan โ
act โ verify loop lives in the agent, so model inference latency and
reasoning depth directly set the pace. The API itself adds only
milliseconds per call.
- **Context capacity** โ screen readings (OCR text, frames, diffs) consume
the agent's context window; a larger window means more situational
awareness before verification degrades.
- **Runtime environment** โ network latency, MCP/HTTP round-trip overhead,
tool-call limits and hosting constraints all stack on top of the loop.
In practice this means: the same repo makes a fast reasoning model fast and
capable, and makes a slow model slow โ the toolchain is not the bottleneck.
Real-time or action-heavy tasks need an agent with fast inference and tight
tool-loop latency; slower agents should prefer deliberate, verification-heavy
tasks.
---
## Table of Contents
- [Why Screen Control?](#why-screen-control)
- [Features](#features)
- [Architecture](#architecture)
- [Installation](#installation)
- [Quick Start](#quick-start)
- [API Reference](#api-reference)
- [Authentication](#authentication)
- [Screen Capture](#screen-capture)
- [Mouse Control](#mouse-control)
- [Keyboard Control](#keyboard-control)
- [OCR (Screen Reading)](#ocr-screen-reading)
- [Vision Access (Image Models)](#vision-access-image-models)
- [Window Management](#window-management)
- [Focus-Free (Background) Control](#focus-free-background-control)
- [Virtual Desktops](#virtual-desktops)
- [Game Mode](#game-mode)
- [Safety Endpoints](#safety-endpoints)
- [๐ค For AI Agents](#-for-ai-agents)
- [MCP Support (One-Click Cloud Agents)](#mcp-support-one-click-cloud-agents)
- [Security Model](#security-model)
- [Game Mode Guide](#game-mode-guide)
- [Vision Access Guide](#vision-access-guide)
- [Troubleshooting](#troubleshooting)
- [Testing](#testing)
- [Contributing](#contributing)
- [License](#license)
---
## Features
| Feature | Description |
|---|---|
| ๐ผ๏ธ **Live screen feed** | Continuously refreshing screenshot in the browser |
| ๐ฑ๏ธ **Mouse control** | Click, right-click, double-click, scroll, drag & drop via live screenshot |
| โจ๏ธ **Keyboard control** | Text typing (Unicode/Turkish included, layout-independent), keys and shortcuts (Ctrl+C, Alt+Tabโฆ) |
| ๐๏ธ **OCR** | Converts on-screen text to machine-readable format |
| ๐ท **Vision access** | Raw-pixel paths for image-capable models: single frames, MJPEG stream, text-based motion detection |
| ๐ช **Window management** | List, focus, safe close (WM_CLOSE), kill (task-manager style) |
| ๐ฅ๏ธ **Focus-free control** | Read/write background windows via PostMessage without stealing focus |
| ๐ฎ **Game mode** | Camera look via relative mouse movement, hold-to-move keys |
| ๐ **Token auth** | Every request requires `X-Auth-Token` (CSRF protection) |
| ๐ฆบ **Stuck-input watchdog** | Auto-releases held keys after 30 s of inactivity |
| ๐ **Failsafe** | Cursor to top-left corner aborts all commands (disabled in game mode) |
---
## Architecture
```
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Browser (Web UI) โ
โ โโโโโโโโโโโโ โโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ Live โ โ Control โ โ Windows / Game Mode โ โ
โ โ View โ โ Panel โ โ Panel โ โ
โ โโโโโโฌโโโโโโ โโโโโโฌโโโโโโ โโโโโโโโโโโโโฌโโโโโโโโโโโโโ โ
โ โ โ โ โ
โโโโโโโโโผโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโ
โ โ โ
โผ โผ โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ HTTP API (Flask) โ
โ 127.0.0.1:8745 โ
โ โ
โ /api/screenshot /api/mouse /api/key โ
โ /api/vision/* /api/ocr /api/window โ
โ /api/game /api/held /api/release_all โ
โ /api/windows /api/desktops /api/desktop โ
โ โ
โ โโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโ โ
โ โ Auth Layer โ โ Watchdog โ โ OCR Engine โ โ
โ โ (token) โ โ (30s auto) โ โ (RapidOCR) โ โ
โ โโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ โ โ
โผ โผ โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ control.py (Core) โ
โ โ
โ Screen: mss (fast capture), PIL (processing) โ
โ Mouse: pyautogui (absolute), SendInput (relative) โ
โ Keyboard: pyautogui + SendInput+KEYEVENTF_UNICODE โ
โ Windows: Win32 API (EnumWindows, SetForegroundWindow) โ
โ Background: PrintWindow (capture), PostMessage (input) โ
โ Virtual Desktops: pyvda โ
โ Game Mode: ClipCursor + MOUSE_MOVE_RELATIVE โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
```
### Coordinates & Concurrency
**Per-Monitor DPI awareness.** `control.py` calls
`SetProcessDpiAwarenessContext(PER_MONITOR_AWARE_V2)` at import time โ
**before** the `pyautogui` import, because pyautogui touches coordinate APIs
during import and would otherwise lock the process to the interpreter
manifest's default (system-aware). With PMv2 active, every coordinate in the
system is a **physical pixel** end to end: mss capture, OCR bounding boxes,
pyautogui/SendInput clicks, ClipCursor. On High-DPI displays (125%/150%
scaling) nothing drifts between what OCR reports and where the mouse clicks.
**Lock architecture.** The server uses two independent locks instead of one
global lock:
| Lock | Protects | Endpoints |
|---|---|---|
| `_input_lock` | mouse, keyboard, game mode, window ops | `/api/mouse`, `/api/key`, `/api/game`, `/api/window/post`, ... |
| `_read_lock` | capture, OCR, vision, enumeration | `/api/screenshot`, `/api/ocr`, `/api/vision/*`, `/api/windows`, ... |
A slow OCR (3โ5 s on a busy screen) no longer freezes concurrent screenshot
or vision reads โ reads queue behind reads, inputs behind inputs.
### Live-Loop Working Principle
This system is designed for a **live perceive-act loop**, not pre-written
command chains:
1. **READ** โ OCR or vision reads the screen before and after every action
2. **ONE ACTION** โ each round sends a single command
3. **VERIFY** โ acceptance is "it appeared on screen", not "I sent it"
4. **ADAPT** โ if verification fails, the next step changes based on what
is actually seen
This is enforced by the `expect_hwnd` guard: typing is **refused (409)** if
the foreground window doesn't match the target.
---
## Installation
```bash
cd screen-control
pip install -r requirements.txt
```
### Requirements
| Package | Purpose | Required? |
|---|---|---|
| `mss` | Fast screen capture | โ
Yes |
| `pyautogui` | Mouse/keyboard control | โ
Yes |
| `pyvda` | Virtual desktop management | โ
Yes |
| `flask` | HTTP server | โ
Yes |
| `Pillow` | Image processing | โ
Yes |
| `rapidocr-onnxruntime` | OCR (screen text reading) | โ ๏ธ Optional |
> **Note:** The OCR package is large and may take a while to install. If it
> fails, everything else still works โ only the OCR feature is unavailable.
### System Requirements
- **OS:** Windows 10/11 (x64)
- **Python:** 3.10+
- **Display:** Any resolution; the system adapts automatically
---
## Quick Start
```bash
# 1. Start the server
cd screen-control
python server.py
# 2. Open in browser
# http://127.0.0.1:8745
# 3. Or control via API
TOKEN=$(cat .token)
curl -H "X-Auth-Token: $TOKEN" http://127.0.0.1:8745/api/screenshot -o screen.jpg
```
---
## API Reference
### Authentication
Every request **must** include the `X-Auth-Token` header. The token is
generated on each server start and written to `.token`.
```bash
TOKEN=$(cat .token)
```
| Code | Meaning |
|---|---|
| 401 | Missing or invalid token |
| 415 | POST without `Content-Type: application/json` |
**Token bootstrap** (for the bundled web UI):
```http
GET /token
โ {"ok": true, "token": "abc123..."}
```
> The `/token` endpoint is safe: Same-Origin Policy prevents foreign pages
> from reading it.
---
### Screen Capture
#### `GET /api/screenshot`
Returns a JPEG screenshot.
| Parameter | Type | Default | Description |
|---|---|---|---|
| `monitor` | int | 1 | Monitor index |
| `region` | string | โ | `x,y,w,h` sub-region |
```bash
curl -H "X-Auth-Token: $TOKEN" -o screen.jpg http://127.0.0.1:8745/api/screenshot
curl -H "X-Auth-Token: $TOKEN" "http://127.0.0.1:8745/api/screenshot?region=0,0,800,600"
```
#### `GET /api/info`
Returns screen dimensions and system state.
```json
{"ok": true, "width": 1920, "height": 1080, "ocr_available": true,
"failsafe": true, "game_mode": false}
```
---
### Mouse Control
#### `POST /api/mouse`
| action | Required params | Optional params | Description |
|---|---|---|---|
| `move` | `x`, `y` | `duration` (default 0.15) | Move cursor to absolute position |
| `click` | `x`, `y` | `button` (left/right), `clicks` (default 1) | Click at position |
| `scroll` | `clicks` | `x`, `y` | Scroll wheel (positive=up) |
| `drag` | `x1`, `y1`, `x`, `y` | `duration`, `button` | Drag between two points |
| `down` | `button` (default "left") | โ | Press and hold mouse button |
| `up` | `button` (default "left") | โ | Release held mouse button |
```bash
# Click at center of screen
curl -X POST http://127.0.0.1:8745/api/mouse -H "X-Auth-Token: $TOKEN" \
-H "Content-Type: application/json" \
-d '{"action":"click","x":960,"y":540,"button":"left"}'
# Right-click
curl -X POST http://127.0.0.1:8745/api/mouse -H "X-Auth-Token: $TOKEN" \
-H "Content-Type: application/json" \
-d '{"action":"click","button":"right","x":960,"y":540}'
# Scroll down
curl -X POST http://127.0.0.1:8745/api/mouse -H "X-Auth-Token: $TOKEN" \
-H "Content-Type: application/json" \
-d '{"action":"scroll","clicks":-3}'
```
---
### Keyboard Control
#### `POST /api/key`
| action | Required params | Description |
|---|---|---|
| `press` | `key` | Press and release a key |
| `down` | `key` | Hold a key down (tracked for watchdog) |
| `up` | `key` | Release a held key |
| `hotkey` | `keys` (array) | Key combination (e.g. `["ctrl","c"]`) |
| `type` | `text` | Type text (Unicode, layout-independent) |
| Optional param | Default | Description |
|---|---|---|
| `expect_hwnd` | โ | Window handle to verify focus (409 if mismatch) |
| `interval` | 0.03 | Delay between characters for `type` |
```bash
# Press Enter
curl -X POST http://127.0.0.1:8745/api/key -H "X-Auth-Token: $TOKEN" \
-H "Content-Type: application/json" -d '{"action":"press","key":"enter"}'
# Ctrl+C
curl -X POST http://127.0.0.1:8745/api/key -H "X-Auth-Token: $TOKEN" \
-H "Content-Type: application/json" -d '{"action":"hotkey","keys":["ctrl","c"]}'
# Type text (Turkish characters supported)
curl -X POST http://127.0.0.1:8745/api/key -H "X-Auth-Token: $TOKEN" \
-H "Content-Type: application/json" -d '{"action":"type","text":"Merhaba dรผnya"}'
# Hold W key down (for walking in games)
curl -X POST http://127.0.0.1:8745/api/key -H "X-Auth-Token: $TOKEN" \
-H "Content-Type: application/json" -d '{"action":"down","key":"w"}'
```
---
### OCR (Screen Reading)
#### `POST /api/ocr`
Converts on-screen text to machine-readable format.
| Param | Type | Default | Description |
|---|---|---|---|
| `region` | array | โ | `[x, y, w, h]` sub-region (faster) |
```json
{
"ok": true,
"text": "Hello World\nFile Edit View",
"lines": ["Hello World", "File Edit View"],
"items": [
{"text": "Hello World", "x": 960, "y": 40},
{"text": "File Edit View", "x": 100, "y": 15}
]
}
```
```bash
# Full screen OCR
curl -X POST http://127.0.0.1:8745/api/ocr -H "X-Auth-Token: $TOKEN" \
-H "Content-Type: application/json" -d '{}'
# Region-only (faster, ~10x for small regions)
curl -X POST http://127.0.0.1:8745/api/ocr -H "X-Auth-Token: $TOKEN" \
-H "Content-Type: application/json" -d '{"region":[0,0,800,100]}'
```
---
### Vision Access (Image Models)
Three endpoints for models that can consume images:
| Endpoint | Description |
|---|---|
| `GET /api/vision/frame` | Single JPEG frame (raw or base64) |
| `GET /api/stream` | MJPEG live stream |
| `POST /api/vision/diff` | Text-based motion detection (no vision needed) |
#### `GET /api/vision/frame`
| Param | Default | Description |
|---|---|---|
| `scale` | 1.0 | Downscale factor (0.5 = half size) |
| `gray` | 0 | 1 for greyscale |
| `quality` | 80 | JPEG quality (20-95) |
| `format` | โ | `base64` for JSON response |
| `region` | โ | `x,y,w,h` sub-region |
```bash
# Half-size greyscale frame as base64 (for text-only models)
curl "http://127.0.0.1:8745/api/vision/frame?scale=0.5&gray=1&format=base64" \
-H "X-Auth-Token: $TOKEN"
```
#### `GET /api/stream`
MJPEG live stream. Drop into `<img src>` or consume frame-by-frame.
| Param | Default | Description |
|---|---|---|
| `fps` | 10 | Frames per second (1-30) |
| `quality` | 70 | JPEG quality |
| `scale` | 1.0 | Downscale factor |
| `region` | โ | `x,y,w,h` sub-region |
#### `POST /api/vision/diff`
Text-based motion detection โ **no vision model required**.
| Body | Description |
|---|---|
| `{}` | Compare against last stored frame |
| `{"grab":"gray"}` | Store current frame for next comparison |
| `{"b64_prev":"..."}` | Compare against provided previous frame |
```json
{
"ok": true,
"changed": true,
"changed_pct": 12.5,
"bbox": [100, 200, 400, 350],
"tiles": [
{"row": 2, "col": 4, "pct": 35.2, "center": [1000, 390]}
]
}
```
---
### Window Management
#### `GET /api/windows`
List all visible windows.
```json
{
"ok": true,
"windows": [
{
"hwnd": 123456,
"title": "My Application",
"process": "app.exe",
"pid": 7890,
"focused": true,
"rect": [0, 0, 1920, 1080],
"desktop": 1
}
]
}
```
#### `POST /api/window`
| action | Required | Optional | Description |
|---|---|---|---|
| `focus` | `hwnd` | โ | Bring window to foreground |
| `close` | `hwnd` | `expect_title`, `expect_process` | Safe close via WM_CLOSE |
| `kill` | `hwnd`, `pid` | โ | Force kill (task-manager style) |
| `topmost` | `hwnd` | โ | Set always-on-top |
| `untopmost` | `hwnd` | โ | Remove always-on-top |
| `maximize` | `hwnd` | โ | Maximise window |
```bash
# Focus a window
curl -X POST http://127.0.0.1:8745/api/window -H "X-Auth-Token: $TOKEN" \
-H "Content-Type: application/json" -d '{"hwnd":12345,"action":"focus"}'
# Safe close (with title verification)
curl -X POST http://127.0.0.1:8745/api/window -H "X-Auth-Token: $TOKEN" \
-H "Content-Type: application/json" \
-d '{"action":"close","hwnd":12345,"expect_title":"Notepad"}'
# Kill process
curl -X POST http://127.0.0.1:8745/api/window -H "X-Auth-Token: $TOKEN" \
-H "Content-Type: application/json" -d '{"action":"kill","hwnd":12345,"pid":7890}'
```
---
### Focus-Free (Background) Control
Read and control windows **without stealing focus** โ the user keeps working
on their main desktop.
#### `GET /api/window/capture`
Capture a window via PrintWindow (works even on another virtual desktop).
| Param | Description |
|---|---|
| `hwnd` (required) | Window handle |
| `client` | 1 = client area only |
| `ocr` | 1 = return OCR text instead of image |
```bash
# Capture window as PNG
curl "http://127.0.0.1:8745/api/window/capture?hwnd=12345" \
-H "X-Auth-Token: $TOKEN" -o window.png
# Capture + OCR in one call
curl "http://127.0.0.1:8745/api/window/capture?hwnd=12345&ocr=1" \
-H "X-Auth-Token: $TOKEN"
```
#### `POST /api/window/post`
Send input to a window, choosing the delivery path automatically.
| action | Description |
|---|---|
| `type` | Type text (Unicode-safe) |
| `key` | Send a key press |
| `hotkey` | Send a key combination |
| `click` | Click at client coordinates |
| `scroll` | Scroll the window |
| `drag` | Drag inside the window |
Optional `mode` parameter controls routing:
| mode | Behavior |
|---|---|
| `auto` (default) | Decided by input-mode probe (see below) |
| `background` | Force PostMessage path (window keeps focus/z-order) |
| `focused` | Force focus + SendInput path |
```bash
# Type into a background Notepad
curl -X POST http://127.0.0.1:8745/api/window/post -H "X-Auth-Token: $TOKEN" \
-H "Content-Type: application/json" \
-d '{"hwnd":12345,"action":"type","text":"Hello from background!"}'
```
Routing rules (`mode=auto`):
- **`postmessage`** โ classic Win32 app: background PostMessage, no focus change.
- **`uia`** โ WinUI/UWP/XAML surface (single DirectX canvas, no Win32 child
controls): posted messages are silently swallowed, so the window is focused
and the action is replayed through SendInput (client coords converted to
screen). This is the documented fallback for modern apps.
- **`focused`** โ window is already foreground: focused SendInput path.
- **`invalid`** โ HTTP 409; not a reachable top-level window.
#### `GET /api/window/input-mode`
Classify how a window receives input **before** posting to it. Returns one of
`focused` | `postmessage` | `uia` | `invalid`.
```bash
curl "http://127.0.0.1:8745/api/window/input-mode?hwnd=12345" \
-H "X-Auth-Token: $TOKEN"
```
> **WinUI note:** New Notepad (and other XAML-hosted apps) has no classic
> child Edit control to post to โ the whole UI is one DirectX surface.
> `input-mode` reports `uia` for these; `/api/window/post` then automatically
> uses the focused SendInput path. `/api/window/children` remains useful for
> classic apps with real child controls.
#### `GET /api/window/children`
List child controls of a window (class name + title + hwnd).
```bash
curl "http://127.0.0.1:8745/api/window/children?hwnd=12345" -H "X-Auth-Token: $TOKEN"
```
---
### Virtual Desktops
#### `GET /api/desktops`
List all virtual desktops.
#### `POST /api/desktop`
| action | Params | Description |
|---|---|---|
| `switch` | `number` | Switch to desktop N |
| `create` | โ | Create a new desktop |
```bash
curl http://127.0.0.1:8745/api/desktops -H "X-Auth-Token: $TOKEN"
curl -X POST http://127.0.0.1:8745/api/desktop -H "X-Auth-Token: $TOKEN" \
-H "Content-Type: application/json" -d '{"action":"switch","number":2}'
```
---
### Game Mode
| action | Params | Description |
|---|---|---|
| `start` | `sensitivity` (default 12) | Lock cursor to center, enable game input |
| `move` | `dx`, `dy`, `sensitivity` | Rotate camera (relative mouse) |
| `stop` | โ | Release cursor + all held input |
| `heartbeat` | โ | Keep-alive for long holds |
```bash
# Start game mode
curl -X POST http://127.0.0.1:8745/api/game -H "X-Auth-Token: $TOKEN" \
-H "Content-Type: application/json" -d '{"action":"start","sensitivity":12}'
# Look right
curl -X POST http://127.0.0.1:8745/api/game -H "X-Auth-Token: $TOKEN" \
-H "Content-Type: application/json" -d '{"action":"move","dx":50,"dy":0}'
# Hold W to walk forward
curl -X POST http://127.0.0.1:8745/api/key -H "X-Auth-Token: $TOKEN" \
-H "Content-Type: application/json" -d '{"action":"down","key":"w"}'
# ... later ...
curl -X POST http://127.0.0.1:8745/api/key -H "X-Auth-Token: $TOKEN" \
-H "Content-Type: application/json" -d '{"action":"up","key":"w"}'
# Stop game mode
curl -X POST http://127.0.0.1:8745/api/game -H "X-Auth-Token: $TOKEN" \
-H "Content-Type: application/json" -d '{"action":"stop"}'
```
---
### Safety Endpoints
#### `GET /api/held`
Returns currently held keys/buttons and watchdog status.
```json
{
"ok": true,
"keys": ["w", "shift"],
"buttons": ["left"],
"game_mode": true,
"idle_seconds": 5.2,
"watchdog_count": 0,
"last_watchdog": null
}
```
#### `POST /api/release_all`
Emergency: release **everything** (held keys, mouse buttons, game-mode cursor
lock).
```bash
curl -X POST http://127.0.0.1:8745/api/release_all -H "X-Auth-Token: $TOKEN" \
-H "Content-Type: application/json" -d '{}'
```
---
---
## ๐ค For AI Agents
A dedicated, comprehensive guide for AI agents (LLMs, vision models, automation
frameworks) is available in **[AGENT_GUIDE.md](AGENT_GUIDE.md)**.
It covers:
- Perceive-act loop (read โ plan โ act โ verify)
- Focus guard (`expect_hwnd`) to prevent wrong-window accidents
- App automation and game control workflows
- Vision access for image-capable models
- Text-based motion detection
- Bandwidth optimization
- Complete curl examples
---
## MCP Support (One-Click Cloud Agents)
**Model Context Protocol (MCP)** turns this project into a plug-and-play
toolbox for any MCP-capable agent: Claude Desktop, Claude Code, Cursor,
VS Code Copilot Agent mode, custom cloud agents โ no custom glue code,
no curl scripts. The agent discovers and calls the tools natively.
### How it works
```
MCP agent (cloud or desktop)
โ MCP protocol (stdio or streamable-HTTP)
โผ
mcp_server.py โ thin wrapper: tools โ HTTP calls, token auto-read
โ REST + X-Auth-Token (localhost only)
โผ
server.py โ the single source of truth:
auth, locks, watchdog, focus guard, all safety rules
```
`mcp_server.py` adds **no new powers** โ every safety mechanism
(auth token, input/read locks, watchdog, Alt+F4 block, focus guard,
failsafe) stays enforced by `server.py`.
### Setup
```bash
pip install mcp # optional dependency (see requirements.txt)
python server.py # start the REST server first (it writes .token)
```
The MCP server auto-reads the token from `.token` (or the
`SCREEN_CONTROL_TOKEN` env var) โ zero configuration.
### Desktop agents (stdio transport)
Claude Desktop โ `claude_desktop_config.json`:
```json
{
"mcpServers": {
"screen-control": {
"command": "python",
"args": ["C:/path/to/screen-control/mcp_server.py"]
}
}
}
```
Claude Code: `claude mcp add screen-control -- python C:/path/to/screen-control/mcp_server.py`
Cursor / VS Code: add the same entry to their MCP config files.
### Remote / cloud agents (streamable-HTTP transport)
```bash
python mcp_server.py --http --port 8751
# MCP endpoint: http://127.0.0.1:8751/mcp
```
The HTTP transport is **token-protected**: every request must carry the
`X-Auth-Token` header (same token as the REST server) or `?token=...` as a
fallback for clients that cannot send custom headers. Only `GET /health` is
open, for liveness probes. DNS-rebinding protection is disabled on this
transport deliberately โ tunneled requests arrive with a foreign `Host`
header, and the rebinding threat is already covered by the token guard.
For a cloud agent, expose it through a tunnel:
```bash
cloudflared tunnel --url http://127.0.0.1:8751
# โ prints a https://<random>.trycloudflare.com URL
```
Then configure the agent's MCP connection with `<tunnel-url>/mcp` plus the
token from `.token` as a header (`X-Auth-Token`) โ or `?token=...` in the
URL if the connector cannot send headers.
> โ ๏ธ A tunnel exposes PC control to the internet. Keep the token secret,
> prefer short-lived tunnels, and stop the server when not in use.
### One-Command Startup (launcher + auto-tunnel)
`start-server.bat` automates the whole cloud setup and prints everything
your cloud agent needs, ready to paste:
1. Downloads `cloudflared.exe` if missing (portable, no admin required)
2. Stops leftover instances from a previous run
3. Starts the REST server (port 8745) and the MCP HTTP server (port 8751)
4. Waits until both are healthy (`/token` and `/health` probes)
5. Starts a cloudflared quick tunnel, extracts its public URL from
`tunnel.log`, and prints the summary:
```
============================================================
ALL SYSTEMS RUNNING
============================================================
Local REST API : http://127.0.0.1:8745
Local MCP : http://127.0.0.1:8751/mcp
Public MCP URL : https://<random>.trycloudflare.com/mcp
------------------------------------------------------------
PASTE INTO YOUR CLOUD AGENT (MCP connector settings)
------------------------------------------------------------
Endpoint : https://<random>.trycloudflare.com/mcp
Header : X-Auth-Token: <token>
URL form : https://<random>.trycloudflare.com/mcp?token=<token>
(only if the connector cannot send headers)
------------------------------------------------------------
```
`stop-server.bat` stops all three (REST, MCP, tunnel) in one go.
### Available tools (16)
| Category | Tools |
|---|---|
| Perception | `get_info`, `ocr_screen`, `screenshot` (real image block for vision models), `motion_diff` |
| Mouse / keyboard | `mouse`, `keyboard` (with `expect_hwnd`), `get_held`, `release_all` |
| Windows | `list_windows`, `focus_window`, `window_children`, `window_input_mode`, `window_post`, `window_capture_ocr`, `close_window` |
| Game mode | `game` (start / move / stop / heartbeat) |
### Which transport for whom
| Consumer | Transport | Command |
|---|---|---|
| Claude Desktop / Cursor / VS Code (local) | stdio | `python mcp_server.py` |
| Claude Code | stdio | `claude mcp add ...` (above) |
| Cloud / remote agents | streamable-HTTP | `start-server.bat` (recommended) or `python mcp_server.py --http --port 8751` + `cloudflared tunnel --url http://127.0.0.1:8751` |
> **Note:** This project targets MCP Python SDK 2.x (`MCPServer` API).
> With SDK 1.x, replace the import with `from mcp.server.fastmcp import FastMCP, Image`
> and `MCPServer` with `FastMCP`.
---
## Security Model
### Threat: Malicious Web Pages (CSRF)
Even bound to `127.0.0.1`, a malicious page in the browser can trigger
non-preflighted requests (text/plain fetch, HTML form POST) to localhost.
The browser blocks the **response** but not the **request** โ the server
would still execute the command.
**Mitigation:** Every request requires `X-Auth-Token`. A foreign page
cannot read this token (Same-Origin Policy), so it cannot authenticate.
Additional layers:
- POST requests **must** use `Content-Type: application/json` (415 otherwise)
- This blocks form-encoded and text-plain POSTs even if the token leaked
### Threat: Stuck Keys / Game Mode Lock
In game mode, `ClipCursor` pins the cursor to a 2ร2 box โ the classic
pyautogui failsafe (cursor to top-left) **does not work**.
**Mitigations:**
1. **Physical `Esc` / `Alt+Tab`** โ real hardware input; this API cannot
block it, and it always works
2. `POST /api/release_all` โ instant release of everything
3. **Watchdog (automatic)** โ 30 s of server-side inactivity with held input
triggers automatic release
### Threat: Wrong Window Typing
**Mitigation:** `expect_hwnd` guard on `/api/key` โ if the foreground window
doesn't match, typing is refused with 409.
### Threat: Dangerous Key Combos
**Mitigation:** Blocked at the API level (403):
- `Alt+F4` โ the only banned Alt combo (Alt+Tab, Alt+menu are legitimate)
- Win key โ prevents Start menu, task switching
- `Ctrl+Alt+Del` โ system security screen
- `Shift+Delete` style โ prevents permanent deletion
### Threat: Killing System Processes
**Mitigation:** Critical system processes are blacklisted:
`winlogon.exe`, `csrss.exe`, `smss.exe`, `services.exe`, `lsass.exe`,
`svchost.exe`, `system`, `registry`, `dwm.exe`
### Network Access
The server binds to `127.0.0.1` by default. To expose it to the network:
```bash
python server.py --host 0.0.0.0 # โ ๏ธ anyone on the network can control this machine
```
---
## Game Mode Guide
### Setup
```bash
# 1. Focus the game window
curl -X POST http://127.0.0.1:8745/api/window -H "X-Auth-Token: $TOKEN" \
-H "Content-Type: application/json" -d '{"hwnd":GAME_HWND,"action":"focus"}'
# 2. Start game mode
curl -X POST http://127.0.0.1:8745/api/game -H "X-Auth-Token: $TOKEN" \
-H "Content-Type: application/json" -d '{"action":"start","sensitivity":12}'
```
### Camera Look
```bash
# Look right
curl -X POST http://127.0.0.1:8745/api/game -H "X-Auth-Token: $TOKEN" \
-H "Content-Type: application/json" -d '{"action":"move","dx":50,"dy":0}'
# Look down
curl -X POST http://127.0.0.1:8745/api/game -H "X-Auth-Token: $TOKEN" \
-H "Content-Type: application/json" -d '{"action":"move","dx":0,"dy":30}'
```
### Movement
```bash
# Walk forward (hold W)
curl -X POST http://127.0.0.1:8745/api/key -H "X-Auth-Token: $TOKEN" \
-H "Content-Type: application/json" -d '{"action":"down","key":"w"}'
# ... walk for a while ...
# Release W
curl -X POST http://127.0.0.1:8745/api/key -H "X-Auth-Token: $TOKEN" \
-H "Content-Type: application/json" -d '{"action":"up","key":"w"}'
```
### Minecraft-Specific
```bash
# Place block (right-click)
curl -X POST http://127.0.0.1:8745/api/mouse -H "X-Auth-Token: $TOKEN" \
-H "Content-Type: application/json" \
-d '{"action":"click","button":"right","x":960,"y":540}'
# Break block (hold left-click)
curl -X POST http://127.0.0.1:8745/api/mouse -H "X-Auth-Token: $TOKEN" \
-H "Content-Type: application/json" -d '{"action":"down","button":"left"}'
# ... after breaking ...
curl -X POST http://127.0.0.1:8745/api/mouse -H "X-Auth-Token: $TOKEN" \
-H "Content-Type: application/json" -d '{"action":"up","button":"left"}'
# Select hotbar slot
curl -X POST http://127.0.0.1:8745/api/key -H "X-Auth-Token: $TOKEN" \
-H "Content-Type: application/json" -d '{"action":"press","key":"1"}'
# Open inventory
curl -X POST http://127.0.0.1:8745/api/key -H "X-Auth-Token: $TOKEN" \
-H "Content-Type: application/json" -d '{"action":"press","key":"e"}'
```
### Suitability
| Game Type | Suitable? | Notes |
|---|---|---|
| Minecraft (building) | โ
Yes | Place blocks, walk, mine |
| Minecraft (PvP) | โ No | Too slow for fast combat |
| Turn-based games | โ
Yes | Ample time for readโactโverify |
| RPG / adventure | โ
Yes | Inventory, dialogue, exploration |
| Fast FPS | โ No | Reaction time insufficient |
| Puzzle games | โ
Yes | Click-based, read-heavy |
---
## Vision Access Guide
### For Image-Capable Models
If the consuming model can process images, use the vision endpoints directly:
```
GET /api/vision/frame?scale=0.5&gray=1&quality=70
```
This returns a single JPEG that the model can analyze for:
- Game HUD elements (health, mana, inventory)
- On-screen text (menus, chat, tooltips)
- Visual scene understanding (blocks, entities, terrain)
### For Text-Only Models
Use the diff endpoint for motion detection without vision:
```
POST /api/vision/diff {"grab":"gray"} โ first call: stores frame
POST /api/vision/diff โ subsequent calls: returns diff
```
The response tells you **where** things changed (tile coordinates) and **how
much** (percentage), which is sufficient for:
- Detecting that an action had an effect
- Locating moving elements on screen
- Tracking animation state changes
### Bandwidth Optimization
| Approach | Payload | Use Case |
|---|---|---|
| `scale=1.0, gray=0` | ~500 KB | Full detail |
| `scale=0.5, gray=1` | ~50 KB | Good for most vision models |
| `scale=0.25, gray=1` | ~10 KB | Maximum compression |
| `diff` (text) | ~1 KB | Text-only agents |
| `region=...` | Variable | Focus on specific area |
---
## Troubleshooting
### "OCR engine not installed"
```bash
pip install rapidocr-onnxruntime
```
### Server won't start (port in use)
```bash
# Find the process using port 8745
netstat -ano | findstr ":8745"
# Kill it
taskkill /PID <pid> /F
```
### "Focus mismatch" (409) when typing
The foreground window changed between the focus call and the type call.
Solution: always pass `expect_hwnd` and verify focus before typing.
### Window not found
The window may have been closed or may be a system window that
`EnumWindows` doesn't expose. Try:
```bash
curl http://127.0.0.1:8745/api/windows -H "X-Auth-Token: $TOKEN"
```
### Game mode cursor stuck
Use `POST /api/release_all` or press `Esc` / `Alt+Tab` physically.
### High OCR latency
OCR on a full 1920ร1080 screen can take from a few seconds up to ~30 s
depending on your CPU and on-screen complexity. Use a region โ small crops
are typically 10ร faster:
```json
{"region": [0, 0, 800, 100]}
```
### Turkish characters not appearing
The system uses `SendInput + KEYEVENTF_UNICODE` which is layout-independent.
If characters still don't appear, the target app may not support Unicode
input โ try `POST /api/window/post` with `action: "type"` instead.
---
## Project Structure
```
screen-control/
โโโ server.py # Flask HTTP server + all API endpoints
โโโ control.py # Core: screen capture, mouse, keyboard, windows, game mode
โโโ mcp_server.py # MCP server (stdio + streamable-HTTP) โ thin wrapper over the API
โโโ sdk/
โ โโโ screen_control.py # Python SDK client (pip-installable style)
โโโ index.html # Bundled web UI (live view + control panels)
โโโ requirements.txt # Python dependencies
โโโ start-server.bat # One command: REST + MCP + cloud tunnel (Windows)
โโโ stop-server.bat # Stop all three processes
โโโ test-security.py # Security + game-mode test suite (19 checks)
โโโ test-game.py # Live game-mechanics test (app launch โ draw โ safe close)
โโโ test-endtoend.py # End-to-end test: open Notepad โ type โ save โ verify
โโโ .github/workflows/ # CI: runs the security suite on every push
โโโ AGENT_GUIDE.md # AI agent integration guide (separate from this file)
โโโ README.md # This file
โโโ .token # Auto-generated auth token (gitignored)
```
---
## Testing
### Prerequisites
The server must be running:
```bash
cd screen-control
python server.py
```
### Security test suite
Tests authentication, blocked key combos, window management, safe close,
critical process protection, and game mode โ all non-destructive.
```bash
cd screen-control
python test-security.py
```
Expected output:
```
== Token Authentication ==
โ Missing token -> 401
โ Wrong token -> 401
โ Correct token -> 200
โ Non-JSON POST -> 415
== Blocked Key Combos ==
โ Alt+F4 blocked (403)
โ Win key blocked (403)
โ Win+D blocked (403)
โ Delete blocked (403)
== Window List ==
โ Windows list requires GET
โ Window list is non-empty โ 8 windows
โ Exactly one focused window
== Safe Close Verification ==
โ Wrong title aborts close
== Critical Process Protection ==
โ System process (pid 4) rejected (403)
โ pid 0 rejected (403)
== Game Mode ==
โ Game mode started
โ Relative camera look
โ Game mode stopped
== Watchdog (dry run) ==
โ Held state returns ok
โ Watchdog count reported
========================================
RESULT: 19 passed, 0 failed
```
### Live game-mechanics test
Launches a real application (mspaint or notepad), performs hold-to-draw
game mechanics, verifies via pixel analysis, then safely closes with
"Don't Save" dialog handling.
```bash
cd screen-control
python test-game.py
```
> **Note:** This test launches a real application. It handles cleanup
> automatically (sends WM_CLOSE and clicks "Don't Save" if a dialog appears).
---
## Contributing
1. Fork the repository
2. Create a feature branch
3. Make your changes
4. Test on a Windows machine
5. Submit a pull request
### Code Style
- **Python:** PEP 8, type hints, docstrings on all public functions
- **Docstrings:** English, Google style
- **Error messages:** English, descriptive
- **Comments:** English, explain *why* not *what*
---
## License
MIT License. See [LICENSE](LICENSE) for details.
---
*Built with โค๏ธ for local automation and AI agent research.*
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues