PluginWorld
Ru

rustdesk-mcp

MCP

Enables AI agents to drive remote desktops through the local RustDesk client on macOS, launching sessions, capturing the session window, and injecting keyboard and mouse input. Exposes session lifecycle, screenshot, input, and device book capabilities as MCP tools.

@Cocos666a · MIT License · updated today

SECURITY

B

SCORE

60

STARS

▲ 0

PLUG IN

git clone https://github.com/Cocos666a/rustdesk-mcp.git

See the README to configure this MCP server

README

rustdesk-mcp

An MCP (Model Context Protocol) server that lets AI agents control remote desktops through the local RustDesk client on macOS.

Instead of re-implementing a remote-desktop stack, rustdesk-mcp drives the RustDesk app you already have: it launches sessions, captures the session window, and injects keyboard/mouse input — exposing all of it as 21 MCP tools to any MCP-capable agent (Claude Code, Hermes Agent, …).

Agent ──stdio MCP──> rustdesk-mcp ──> local RustDesk.app ──> remote desktop

Features

  • 21 tools: session lifecycle, screenshots, hardware-level input injection, a device book, diagnostics (see Tools).
  • Capture pipeline built for macOS 14+ / 26 — where per-window CGWindowListCreateImage was removed: a bundled ScreenCaptureKit helper (tools/rds_capture.swift, source included), a cua-driver daemon fallback, and full-screen-capture + crop as last resort.
  • One coordinate contract: desktop_* coordinates are pixels on the most recent control_screenshot image, auto-converted to screen points (Retina included).
  • Device book: persist peers with alias/notes, fuzzy search, discovery from local RustDesk state.
  • Passwords are accepted per connection call and never written to disk.

How it works

  1. Connect — starts the RustDesk GUI if needed and delivers rustdesk://connect/<peer> through the --connect CLI. The session window is discovered by title and remembered (survives MCP host restarts).
  2. Capture — grabs the session window image, in order:
    • ScreenCaptureKit helper app (bin/rds-cap2.app; fast, needs its own Screen Recording grant);
    • cua-driver daemon (optional fallback);
    • full-screen capture + crop (last resort; slow under LaunchDaemon contexts).
  3. Input — posts hardware-level CGEvents (needs an Accessibility grant for the Python process) at coordinates mapped from the latest screenshot.
  4. Diagnose — rustdesk_status reports RustDesk install/version, TCC permission state, and capture-route availability with actionable hints.

Requirements

  • macOS 14+ (developed and tested on macOS 26, Apple Silicon).
  • RustDesk desktop client installed and signed in — this server drives your client, so the IDs it can reach are exactly the ones the app can reach.
  • Python 3.11+.
  • Xcode Command Line Tools (swiftc) — only to build the optional capture helper.

Install

git clone https://github.com/Cocos666a/rustdesk-mcp.git rustdesk-mcp
cd rustdesk-mcp
python3 -m venv .venv
.venv/bin/pip install -r requirements.txt
.venv/bin/pip install -e .

# Optional but recommended — build the fast capture helper:
bash tools/build_helper.sh
./bin/rds-cap2.app/Contents/MacOS/rds-capture --request   # grant Screen Recording

Permissions (macOS, one-time)

Full manual: docs/MACOS-PERMISSIONS.md. Summary:

Permission Who needs it Why
Screen Recording the Python process running this server; the capture helper screenshot the session window
Accessibility the Python process running this server post keyboard/mouse events
Accessibility RustDesk.app forward your keyboard/mouse to the remote
Input Monitoring RustDesk.app capture your local input on modern macOS — without it, video works but remote input is dead

Register with your MCP client

Generic stdio config (Claude Desktop, Cursor and other MCP clients):

{
  "mcpServers": {
    "rustdesk": {
      "command": "/absolute/path/to/rustdesk-mcp/bin/rustdesk-mcp"
    }
  }
}

Hermes Agent:

hermes mcp add rustdesk --command /absolute/path/to/rustdesk-mcp/bin/rustdesk-mcp

Quick start

With the server registered, a typical agent session calls the tools in this order (real names, real arguments):

  1. rustdesk_status() — RustDesk version, own ID, TCC state, active sessions.
  2. session_connect(peer_id="123456789", password="…") — opens a session window and returns its window_id + bounds.
  3. control_screenshot() — returns the window image plus its pixel size and scale. That image is the coordinate space for every desktop_* call.
  4. desktop_click_mouse(x=640, y=320) — click at pixel (640, 320) of that image.
  5. desktop_typing_text(text="hello") / desktop_typing_keys(keys="cmd+shift+t").
  6. session_disconnect(peer_id="123456789") — close the window (the RustDesk process keeps running).

If a screenshot fails, rustdesk_status() tells you which capture route is available and which permission is missing.

Tools

Tool Purpose
rustdesk_status Environment + permission diagnostics
session_connect / session_state / session_list / session_disconnect Session lifecycle (peer id, mode, wait, front, password)
control_portforward Open a port-forward session window
control_screenshot Capture the session window (image + geometry meta)
desktop_move_mouse / desktop_click_mouse / desktop_drag_mouse / desktop_scroll_mouse Mouse control (image-pixel coordinates)
desktop_typing_text / desktop_typing_keys / desktop_press_keys / desktop_paste_text Keyboard control (combos like cmd+shift+t, key repetition)
desktop_waiting Sleep (UI settling)
device_add / device_remove / device_update / device_search / device_info Local device book

peer_id accepts both RustDesk IDs (123456789) and direct IPs (100.x.y.z, LAN IPs) — see docs/TROUBLESHOOTING.md for direct-IP setup on the controlled side.

Runtime state is written to data/ next to the project and is gitignored; the directory is created on first use. It holds devices.json (the device book: peer ids, aliases, notes) and sessions.json (window ids and bounds) — no credentials.

Development

.venv/bin/pytest -q                          # unit tests
.venv/bin/python tests/smoke_local.py status # local smoke check

Security

This server is an input-automation bridge: anything that can call its tools can move the mouse, type, and read the screen of the remote machine. Run it only for an MCP client you trust, and consider a dedicated RustDesk peer instead of your daily-driver machine.

  • Passwords: accepted per connection call, forwarded to the RustDesk CLI, and never written to disk. They are redacted (***) in the server's stderr log. Note that the rustdesk:// URL in the tool result echoes the password you passed, so your MCP host's transcript holds it — treat that transcript as sensitive.
  • Clipboard: desktop_paste_text writes to the system clipboard, which overwrites whatever was there, then sends Cmd+V.
  • Device scan: the device book scanner reads only file names under ~/Library/Preferences/com.carriez.RustDesk; it never parses peer configs or password fields.
  • No config writes: the CLI wrapper only passes whitelisted arguments, so it cannot change RustDesk settings (--set-id, --option, --config, …).

Known limitations

  • macOS only — implemented against pyobjc/Quartz/CGEvent; no Linux/Windows backend.
  • No file transfer / command channel yet (Phase 2).
  • Does not modify RustDesk configuration; it only drives the app.
  • Public rendezvous reachability depends on your network — some networks (e.g. mainland China) cannot reach RustDesk's public servers; direct IP works fine in that case.

Alternative: driving a Windows host over RDP (tools/winexec)

When macOS refuses to deliver synthetic input (TCC judges the event sender, so a grant for RustDesk.app does not cover the Python process), the GUI path cannot type or click at all. tools/winexec/ sidesteps the macOS input layer: a headless FreeRDP client runs in a Linux container, and keystrokes are injected into its X11 window with xdotool, so they reach the RDP session without touching macOS permissions.

  • Needs Docker, a Windows host with RDP enabled, and WIN_HOST / WIN_USER / WIN_PASS in the environment (never written to disk).
  • ./winexec.py up → ./winexec.py type 'dir C:\' --enter → ./winexec.py shot → ./winexec.py down.
  • It reconnects your own Windows session, so processes started there survive down.
  • Details: tools/winexec/README.md.

This is a companion channel, not part of the MCP server — the 21 tools above remain the contract.

Contributing

Issues and pull requests are welcome.

  • Tool names and arguments are the public contract (tests/test_tools.py enforces it) — renaming or unregistering a tool is a breaking change.
  • Run the unit tests before opening a PR: .venv/bin/pytest -q. They need no TCC permissions and no running RustDesk.
  • Behaviour that only shows up on a real machine (TCC prompts, capture routes, session windows) should be stated with the macOS/RustDesk version you tested on.

License

MIT — see LICENSE.

SIMILAR PLUGINS