QuickDesk includes a built-in MCP (Model Context Protocol) server that allows AI agents to see and control remote desktops programmatically.
QuickDesk MCP supports two transport modes:
AI Agent (Claude / Cursor / GPT)
│ stdio (JSON-RPC 2.0)
▼
quickdesk-mcp (Rust binary) ← spawned by AI client
│ WebSocket
▼
QuickDesk GUI (Qt 6)
│ Native Messaging + Shared Memory
▼
Remote Desktop (Chromium Remoting / WebRTC)
The AI client spawns quickdesk-mcp as a child process and communicates via stdin/stdout. This is the simplest setup — just paste a config JSON and go.
AI Agent (Claude / Cursor / GPT)
│ HTTP (Streamable HTTP / SSE)
▼
quickdesk-mcp (Rust binary, HTTP server) ← spawned by QuickDesk
│ WebSocket
▼
QuickDesk GUI (Qt 6)
│ Native Messaging + Shared Memory
▼
Remote Desktop (Chromium Remoting / WebRTC)
QuickDesk launches quickdesk-mcp as a managed HTTP server bound to 0.0.0.0. Local AI clients connect via http://127.0.0.1:18080/mcp; remote clients should replace 127.0.0.1 with the QuickDesk machine's reachable IP address. This mode supports multiple simultaneous AI clients and network-accessible endpoints.
Launch QuickDesk normally. The WebSocket API server listens on all IPv4 interfaces and is locally reachable at ws://127.0.0.1:9600.
Create or edit .cursor/mcp.json in your project root:
{
"mcpServers": {
"quickdesk": {
"command": "/path/to/quickdesk-mcp",
"args": [],
"env": {}
}
}
}Edit claude_desktop_config.json:
- Windows:
%APPDATA%\Claude\claude_desktop_config.json - macOS:
~/Library/Application Support/Claude/claude_desktop_config.json
{
"mcpServers": {
"quickdesk": {
"command": "C:\\path\\to\\quickdesk-mcp.exe",
"args": []
}
}
}Create or edit .vscode/mcp.json in your project root:
{
"servers": {
"quickdesk": {
"type": "stdio",
"command": "/path/to/quickdesk-mcp",
"args": []
}
}
}In QuickDesk, open MCP settings → switch to HTTP/SSE mode → toggle the MCP HTTP Service ON. QuickDesk will launch the quickdesk-mcp HTTP server automatically.
{
"mcpServers": {
"quickdesk": {
"type": "sse",
"url": "http://127.0.0.1:18080/mcp"
}
}
}{
"mcpServers": {
"quickdesk": {
"type": "sse",
"url": "http://127.0.0.1:18080/mcp"
}
}
}{
"servers": {
"quickdesk": {
"type": "http",
"url": "http://127.0.0.1:18080/mcp"
}
}
}Note: VS Code uses
"type": "http"and the top-level key"servers". Other clients use"type": "sse"and"mcpServers".
quickdesk-mcp is a standard MCP stdio server. Any client that supports stdio transport can use it:
quickdesk-mcp [--ws-url ws://127.0.0.1:9600] [--token YOUR_TOKEN]| Argument | Default | Env Variable | Description |
|---|---|---|---|
--ws-url |
ws://127.0.0.1:9600 |
QUICKDESK_WS_URL |
QuickDesk WebSocket API URL |
--token |
(none) | QUICKDESK_TOKEN |
Full-control auth token |
--readonly-token |
(none) | QUICKDESK_READONLY_TOKEN |
Read-only auth token |
--allowed-devices |
(none) | QUICKDESK_ALLOWED_DEVICES |
Comma-separated device ID whitelist |
--rate-limit |
0 |
QUICKDESK_RATE_LIMIT |
Max API requests per minute (0 = unlimited) |
--session-timeout |
0 |
QUICKDESK_SESSION_TIMEOUT |
Session timeout in seconds (0 = no timeout) |
--transport |
stdio |
QUICKDESK_TRANSPORT |
Transport mode: stdio or http |
--port |
8080 |
QUICKDESK_HTTP_PORT |
HTTP server port (HTTP mode only) |
--host |
127.0.0.1 |
QUICKDESK_HTTP_HOST |
HTTP server bind address (HTTP mode only) |
--cors-origin |
(none) | QUICKDESK_CORS_ORIGIN |
Allowed CORS origin (HTTP mode only) |
--stateless |
false |
— | Use stateless HTTP sessions (HTTP mode only) |
Once configured, your AI agent can use QuickDesk tools directly. Example conversation:
You: Connect to my remote server (device ID: 123456789, access code: 888888) and check what's on screen.
AI Agent: (calls
connect_device→screenshot→ analyzes the image) I can see the Windows desktop with File Explorer open...
Single identifier — deviceId / device_id: QuickDesk’s WebSocket API and MCP tools use one external identifier: the remote device ID (the stable id you pass to connect_device or read from get_host_info). After connect_device, the returned device_id is what you pass to every subsequent tool call. There is no separate connection/session id in the public API.
| Tool | Description |
|---|---|
connect_device |
Connect to a remote device by device ID + access code. Returns device_id (the same stable device identifier). Use it for all later tool calls. Set show_window=false for headless automation. |
disconnect_device |
Disconnect the remote session for a given device_id. |
disconnect_all |
Disconnect all active remote connections. |
list_connections |
List all active connections with their device IDs and states. |
get_connection_info |
Get detailed info for a specific active connection (keyed by device_id). |
| Tool | Description |
|---|---|
screenshot |
Capture the remote screen. Returns base64 image. Use max_width to scale down for faster processing. |
get_screen_size |
Get the remote desktop resolution (width × height). |
get_screen_text |
Run OCR (PP-OCRv4) on the current remote desktop frame. Returns all recognized text blocks with bounding boxes, center coordinates, and confidence scores. Results are cached by frame hash — calling this multiple times on the same frame is free. |
find_element |
Find a UI element by its visible text using OCR. Returns all matching text blocks with bounding boxes and center coordinates. Supports partial match (default) and exact match. |
click_text |
Find text on the remote desktop and click it in one step. Equivalent to find_element + mouse_click at the text center. If multiple matches exist, clicks the first one. |
get_ui_state |
Get a unified UI state snapshot: screen resolution, OCR text blocks, and active window title. Returns structured data instead of a raw image, reducing token cost and enabling reliable text-based navigation. Use this as a lightweight alternative to screenshot when you need to understand what is on screen without visual analysis. The ocr.blocks array contains every recognised text block with its coordinates. |
wait_for_text |
Block until the specified text appears on the remote desktop screen, or until the timeout expires. Returns found=true with the matching text block when the text appears. Prefer this over polling with screenshot in a loop. |
assert_text_present |
Assert that the specified text is currently visible on the remote desktop screen. Returns present=true with the matching text block if found, or present=false if not found. Returns immediately without polling — use wait_for_text if you need to wait. |
verify_action_result |
Verify that a set of conditions are met after performing an action. Polls OCR and window state until all conditions pass or the timeout elapses. Returns allPassed=true with per-condition detail, or allPassed=false with a failure summary. For post-action verification with retries. |
screen_diff_summary |
Compare the current screen's OCR state against a previous snapshot identified by from_hash. Returns a structured diff listing text blocks that appeared or disappeared between the two frames, plus a human-readable summary. Use get_ui_state to capture the baseline frame_hash before an action. |
assert_screen_state |
Immediately assert that a set of conditions are all satisfied on the current screen without any polling. Returns allPassed=true/false with per-condition detail and a summary. Use as a pre-condition check before a risky action. |
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
device_id |
string | ✅ | — | Remote desktop device ID |
Returns: { blocks, frameHash, width, height, deviceId }
Each block in blocks:
| Field | Type | Description |
|---|---|---|
text |
string | Recognized text content |
bbox |
object | Bounding box { x, y, w, h } in pixels |
center |
object | Center point { x, y } for click targeting |
confidence |
number | OCR confidence score (0–1) |
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
device_id |
string | ✅ | — | Remote desktop device ID |
text |
string | ✅ | — | Text to search for on screen |
exact |
boolean | — | false |
If true, require exact text match; false = partial/substring match |
ignore_case |
boolean | — | true |
Case-insensitive matching |
Returns: { found, matches, query, frameHash, deviceId }
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
device_id |
string | ✅ | — | Remote desktop device ID |
text |
string | ✅ | — | Text to find and click |
exact |
boolean | — | false |
Exact match vs. partial match |
ignore_case |
boolean | — | true |
Case-insensitive matching |
button |
string | — | "left" |
Mouse button: "left", "right", or "middle" |
Returns: { success, clickedText, x, y, confidence } or { success: false, error } if text not found.
Get a unified UI state snapshot combining screen resolution, OCR text blocks, and active window title in a single call. More efficient than screenshot for text-based navigation since it avoids image encoding and AI vision processing overhead.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
device_id |
string | ✅ | — | Remote desktop device ID |
Returns:
{
"deviceId": "123456789",
"screen": { "width": 1920, "height": 1080 },
"ocr": {
"blocks": [ ... ],
"frameHash": "...",
"fromCache": true
},
"activeWindow": { "title": "Untitled - Notepad" }
}The ocr.blocks array contains every recognised text block with its coordinates (same structure as get_screen_text). fromCache indicates whether the OCR result came from the frame cache (no extra compute cost).
Block until the specified text appears on screen or the timeout elapses. Polls OCR internally — no need to call screenshot in a loop.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
device_id |
string | ✅ | — | Remote desktop device ID |
text |
string | ✅ | — | Text to wait for (supports partial match by default) |
exact |
boolean | — | false |
If true, require exact text match |
ignore_case |
boolean | — | true |
Case-insensitive matching |
timeout_ms |
integer | — | 5000 |
Maximum wait time in milliseconds |
Returns: { found: true, match: { text, bbox, center, confidence }, query, deviceId } on success, or an error string on timeout.
Immediately check if the specified text is currently visible on screen. Does not wait — returns the current state at call time.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
device_id |
string | ✅ | — | Remote desktop device ID |
text |
string | ✅ | — | Text to assert is present (supports partial match by default) |
exact |
boolean | — | false |
If true, require exact text match |
ignore_case |
boolean | — | true |
Case-insensitive matching |
Returns: { present: true, match: { text, bbox, center, confidence }, query, deviceId } if found, or { present: false, query, deviceId } if not found.
These tools give AI agents the ability to verify their own actions and detect screen changes — the foundation of reliable automation.
| Tool | Description |
|---|---|
verify_action_result |
Poll OCR + window state until all conditions pass or timeout. Use after performing an action to confirm it succeeded. |
screen_diff_summary |
Compare two OCR snapshots by frame hash. Reveals exactly which text blocks appeared or disappeared. |
assert_screen_state |
Immediate (no polling) assertion of multiple conditions. Use as a pre-flight check before risky actions. |
All three verification tools share the same condition structure ({ type, value }):
| Type | Description |
|---|---|
text_present |
Text is visible on screen (partial match, case-insensitive) |
text_absent |
Text is NOT visible on screen |
text_present_exact |
Text is visible on screen (exact, case-sensitive) |
window_title_contains |
Active window title contains the value |
window_title_equals |
Active window title exactly equals the value |
Poll until all conditions pass or the timeout elapses. Each failed poll waits 200 ms before retrying.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
device_id |
string | ✅ | — | Remote desktop device ID |
expectations |
array | ✅ | — | List of { type, value } conditions that must all pass |
timeout_ms |
integer | — | 3000 |
Maximum polling time in milliseconds |
Returns:
{
"allPassed": true,
"timedOut": false,
"results": [
{ "type": "text_present", "value": "Saved", "passed": true,
"actual": "File Saved", "reason": "Found \"Saved\" in block \"File Saved\"" }
],
"summary": "All 1 condition(s) passed"
}Compare the current frame against a previously recorded snapshot.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
device_id |
string | ✅ | — | Remote desktop device ID |
from_hash |
string | — | "" |
Frame hash from a prior get_ui_state call. Leave empty to show all current blocks as "added". |
Returns:
{
"fromHash": "abc123...",
"toHash": "def456...",
"hasChanges": true,
"added": [ { "text": "Save successful", "bbox": {...}, "center": {...}, "confidence": 0.97 } ],
"removed": [ { "text": "Saving...", "bbox": {...}, "center": {...}, "confidence": 0.95 } ],
"summary": "appeared: \"Save successful\"; disappeared: \"Saving...\""
}No polling — returns the current pass/fail status immediately.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
device_id |
string | ✅ | — | Remote desktop device ID |
expectations |
array | ✅ | — | List of { type, value } conditions to check |
Returns: Same structure as verify_action_result.
Instead of analyzing screenshots with a vision model, these tools run on-device OCR (PP-OCRv4) directly on the raw video frame — no JPEG degradation, instant results, and cached by frame hash.
| Scenario | Recommended Approach |
|---|---|
| Reading menu items, button labels, dialog text | get_screen_text or find_element |
| Clicking a button by its label | click_text |
| Understanding overall UI layout | screenshot → vision model |
| Finding a specific word on screen | find_element |
| Verifying text appeared after an action | verify_action_result (with retries) or assert_text_present (instant) |
| Waiting for a result to appear | wait_for_text (blocks until found) |
| Getting screen state without a screenshot | get_ui_state (resolution + OCR + window title) |
| Confirming an action had the expected effect | verify_action_result or screen_diff_summary |
| Pre-flight check before a risky action | assert_screen_state |
| Waiting for a result to appear | wait_for_text (blocks until found) |
| Getting screen state without a screenshot | get_ui_state (resolution + OCR + window title) |
click_text(device_id=dev_id, text="OK")
→ finds "OK" button on screen and clicks it
get_screen_text(device_id=dev_id)
→ returns all text blocks with coordinates
find_element(device_id=dev_id, text="Error", ignore_case=true)
→ check if any error message is visible
click_text(device_id=dev_id, text="Retry")
→ click the retry button
keyboard_hotkey(keys=["ctrl", "s"]) → save the file
// wait a moment, then verify
elements = find_element(device_id=dev_id, text="Saved")
if elements.found:
→ file was saved successfully
state = get_ui_state(device_id=dev_id)
→ state.screen.width / state.screen.height — resolution
→ state.ocr.blocks — all visible text + coordinates
→ state.activeWindow.title — current foreground window
keyboard_type(text="apt install nginx")
keyboard_hotkey(keys=["enter"])
wait_for_text(device_id=dev_id, text="done", timeout_ms=60000)
→ blocks until "done" appears in terminal, or 60s elapses
// Before clicking "Delete", assert the confirm dialog is showing
assert_text_present(device_id=dev_id, text="Are you sure")
→ present=true: safe to click "OK"
→ present=false: unexpected state, abort
| Tool | Description |
|---|---|
mouse_click |
Click at (x, y). Supports left/right/middle button. |
mouse_double_click |
Double-click at (x, y). |
mouse_move |
Move cursor to (x, y). |
mouse_scroll |
Scroll at (x, y) with delta_x/delta_y. |
mouse_drag |
Drag from (start_x, start_y) to (end_x, end_y). |
| Tool | Description |
|---|---|
keyboard_type |
Type text using clipboard paste for reliable unicode input. |
keyboard_hotkey |
Press a key combination, e.g. ["ctrl", "c"], ["win", "r"], ["alt", "f4"]. |
key_press |
Press and hold a key (for modifier+click scenarios). |
key_release |
Release a previously pressed key. |
| Tool | Description |
|---|---|
get_clipboard |
Get the last clipboard content synced from remote. |
set_clipboard |
Set the remote clipboard content. |
| Tool | Description |
|---|---|
get_host_info |
Get local device ID, access code, and signaling state. Use this to connect to the current machine. |
get_host_clients |
List clients connected to this host. |
get_status |
Overall system status (host/client processes, signaling). |
get_signaling_status |
Signaling server connection status. |
refresh_access_code |
Generate a new access code for the local host. |
These tools invoke skills running on the remote host machine via the QuickDesk skill host. The skill host starts automatically when the host launches and reports its available tools when a client connects. Use skill_list_tools to discover available tools before calling skill_exec.
| Tool | Description |
|---|---|
skill_list_tools |
List all tools available on the remote skill host. Returns tool names, descriptions, and input schemas. |
skill_exec |
Execute a tool on the remote skill host. Pass the tool name and arguments as a JSON object. |
The following tools are provided by built-in skills that ship with QuickDesk (zero external dependencies):
sys-info — Remote host system information
| Tool | Description | Parameters |
|---|---|---|
get_system_info |
OS version, CPU model, memory usage, disk usage, hostname, and uptime | (none) |
list_processes |
Running processes with name, PID, CPU%, and memory usage | sort_by (cpu/memory/name), limit (default 50) |
file-ops — Remote host file operations
| Tool | Description | Parameters |
|---|---|---|
read_file |
Read file contents | path (absolute path) |
write_file |
Write content to a file (creates or overwrites) | path, content |
list_directory |
List files and directories | path (absolute path) |
create_directory |
Create a directory (including parents) | path |
move_file |
Move or rename a file/directory | source, destination |
get_file_info |
File metadata: size, modified time, permissions, type | path |
shell-runner — Remote host command execution
| Tool | Description | Parameters |
|---|---|---|
run_command |
Execute a shell command. Returns stdout, stderr, and exit code. | command, timeout_secs (default 60), working_dir |
// Discover available tools on the remote host
skill_list_tools(device_id=dev_id)
→ returns all tools with descriptions and parameter schemas
// Get system information
skill_exec(device_id=dev_id, tool="get_system_info", args={})
→ OS, CPU, memory, disk, hostname, uptime
// Run a command on the remote host
skill_exec(device_id=dev_id, tool="run_command",
args={"command": "ipconfig /all"})
→ stdout, stderr, exit_code
// Read a remote file
skill_exec(device_id=dev_id, tool="read_file",
args={"path": "C:\\Users\\admin\\config.ini"})
→ file contents
Note: Skill host tools run directly on the remote host without needing a visible desktop session. They are faster and more reliable than opening a terminal via screenshot-based automation for tasks like reading files, running commands, and checking system status.
Instead of polling with repeated screenshots, these tools let AI agents efficiently wait for state changes on the remote desktop.
| Tool | Description |
|---|---|
wait_for_event |
Wait for a specific event type. Returns the matching event data, or an error on timeout. |
wait_for_connection_state |
Wait for a connection to reach a target state (e.g. connected, disconnected). |
wait_for_clipboard_change |
Wait for the remote clipboard content to change. Returns the new content. |
get_recent_events |
Get recent events from the event buffer, optionally filtered by type. |
list_event_types |
List all supported event types and their data fields. |
Generic event waiter. Use this when you need to wait for any event type, with optional data field filtering.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
event |
string | ✅ | — | Event type to wait for (see Event Types below) |
filter |
object | — | — | JSON object where each key-value pair must match the event data. E.g. {"state": "connected"} only matches events whose data.state == "connected". |
timeout_ms |
integer | — | 30000 |
Maximum wait time in milliseconds |
Returns: The matching event object {event, data, timestamp} or an error string on timeout.
Convenience wrapper for waiting on connection state transitions. Use after connect_device to wait until the connection is fully established, instead of polling screenshots.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
device_id |
string | ✅ | — | Device ID to monitor |
state |
string | ✅ | — | Target state: connected, disconnected, failed |
timeout_ms |
integer | — | 30000 |
Maximum wait time in milliseconds |
Returns: The connectionStateChanged event data, or an error on timeout.
Wait for the remote clipboard to change. Use after sending Ctrl+C on the remote desktop to get the copied text without polling.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
device_id |
string | ✅ | — | Device ID to monitor |
timeout_ms |
integer | — | 10000 |
Maximum wait time in milliseconds |
Returns: The clipboardChanged event including the new clipboard text, or an error on timeout.
Query the event ring buffer for recently received events. Useful for checking what happened while the AI was performing other operations.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
event_type |
string | — | — | Filter by event type. If omitted, returns all types. |
limit |
integer | — | 20 |
Max events to return (capped at 100) |
Returns: Array of events in chronological order (oldest first).
Returns all supported event types and their data fields. No parameters.
| Event Type | Description | Data Fields |
|---|---|---|
connectionStateChanged |
Connection state transition | deviceId, state, hostInfo |
clipboardChanged |
Remote clipboard content changed | deviceId, text |
connectionAdded |
New outgoing connection created | deviceId |
connectionRemoved |
Outgoing connection removed | deviceId |
videoLayoutChanged |
Remote desktop video resolution changed | deviceId, width, height |
hostReady |
Local host service ready to accept connections | deviceId, accessCode |
accessCodeChanged |
Local host access code refreshed | accessCode |
hostClientConnected |
Remote client connected to this host | deviceId, ... |
hostClientDisconnected |
Remote client disconnected from this host | deviceId, reason |
hostSignalingStateChanged |
Host signaling connection state changed | state, retryCount, nextRetryIn, error |
hostProcessStatusChanged |
Host process status changed | status |
clientProcessStatusChanged |
Client process status changed | status |
Resources provide read-only real-time data about system state.
| URI | Description |
|---|---|
quickdesk://host |
Local device ID, access code, signaling state |
quickdesk://status |
Overall system status |
quickdesk://device/{deviceId} |
Detailed info for a specific remote device |
Prompts are instruction templates that teach AI agents best practices for specific scenarios.
| Prompt | Description | Parameters |
|---|---|---|
operate_remote_desktop |
Complete guide for the screenshot→analyze→act→verify loop, coordinate system, and all available tools. | (none) |
find_and_click |
Step-by-step instructions for locating and clicking a specific UI element. | element_description, device_id |
run_command |
Step-by-step instructions for opening a terminal and running a command. | command, device_id |
| Prompt | Description | Parameters |
|---|---|---|
server_health_check |
Comprehensive server health check — CPU, memory, disk, processes, services, error logs. Generates a structured health report. | device_id |
batch_operation |
Guide for executing the same task across multiple devices sequentially, with error handling and summary reporting. | task_description |
| Prompt | Description | Parameters |
|---|---|---|
diagnose_system_issue |
Systematic diagnosis of system problems (slow performance, crashes, network issues, disk full) with root cause analysis and remediation suggestions. | issue_description, device_id |
| Prompt | Description | Parameters |
|---|---|---|
analyze_screen_content |
Deep analysis of screen content — OS detection, open applications, text extraction, UI element inventory, and security scan for exposed sensitive information. | device_id |
| Prompt | Description | Parameters |
|---|---|---|
multi_device_workflow |
Orchestrate complex workflows across multiple remote devices with dependency management and cross-device data transfer. | task_description |
| Prompt | Description | Parameters |
|---|---|---|
document_procedure |
Observe or perform a procedure and generate a Standard Operating Procedure (SOP) document with step-by-step instructions, screenshots, and troubleshooting. | procedure_name, device_id |
See Demo Scenarios for complete usage examples of each prompt.
The remote desktop has a coordinate space defined by get_screen_size (e.g. 1920×1080).
When you take a screenshot with max_width (e.g. 1280), the image is scaled down but the remote screen coordinates remain unchanged. To convert image coordinates to screen coordinates:
screen_x = image_x × (screen_width / image_width)
screen_y = image_y × (screen_height / image_height)
Always call get_screen_size before computing click targets from scaled screenshots.
The following key names can be used with keyboard_hotkey, key_press, and key_release:
Modifiers: ctrl, shift, alt, win (or meta)
Function keys: f1 – f12
Navigation: enter, tab, escape, backspace, delete, insert, home, end, pageup, pagedown, up, down, left, right
Special: space, capslock, numlock, scrolllock, printscreen, pause
Letters & digits: a – z, 0 – 9
Punctuation: minus, equal, leftbracket, rightbracket, backslash, semicolon, quote, backquote, comma, period, slash
1. get_host_info → get device_id + access_code
2. connect_device → get device_id
3. screenshot → see what's on screen
4. ... interact ...
5. disconnect_device → clean up
connect_device(show_window=false) → no GUI window opened
screenshot → mouse_click → keyboard_type → ...
disconnect_device
dev_a = connect_device(device_id="111222333", ...)
dev_b = connect_device(device_id="444555666", ...)
screenshot(device_id=dev_a) → see device A
screenshot(device_id=dev_b) → see device B
keyboard_type(device_id=dev_a, text="...")
keyboard_type(device_id=dev_b, text="...")
key_press(key="ctrl")
mouse_click(x=100, y=200)
mouse_click(x=300, y=400) → multi-select
key_release(key="ctrl")
mouse_drag(start_x=100, start_y=200, end_x=500, end_y=200)
keyboard_hotkey(keys=["ctrl", "c"])
get_clipboard() → get selected text
Use wait_for_connection_state instead of polling with list_connections:
dev_id = connect_device(device_id="111222333", access_code="888888")
wait_for_connection_state(device_id=dev_id, state="connected", timeout_ms=15000)
screenshot() → screen is ready
Use wait_for_clipboard_change instead of polling get_clipboard:
mouse_drag(start_x=100, start_y=200, end_x=500, end_y=200)
keyboard_hotkey(keys=["ctrl", "c"])
wait_for_clipboard_change(device_id=dev_id, timeout_ms=5000)
→ returns the copied text immediately when clipboard updates
// Wait for the remote desktop resolution to change (e.g. after window resize)
wait_for_event(event="videoLayoutChanged", filter={"deviceId": dev_id}, timeout_ms=10000)
screenshot() → capture the new layout
get_recent_events(event_type="connectionStateChanged", limit=10)
→ see recent connection state changes
get_recent_events(limit=50) → see all recent events
// Perform an action
keyboard_hotkey(keys=["ctrl", "s"])
// Poll until "Saved" appears on screen (up to 3 s)
verify_action_result(device_id=dev_id,
expectations=[{ type: "text_present", value: "Saved" }],
timeout_ms=3000)
→ allPassed=true: action confirmed
→ allPassed=false + reason: let agent decide next step
// Capture baseline before action
state = get_ui_state(device_id=dev_id) → hash = state.ocr.frameHash
// Perform action
click_text(device_id=dev_id, text="Submit")
// See what changed
screen_diff_summary(device_id=dev_id, from_hash=hash)
→ added: ["Submission confirmed"]
→ removed: ["Submit"]
→ summary: "appeared: \"Submission confirmed\"; disappeared: \"Submit\""
// Confirm delete dialog is visible before clicking OK
assert_screen_state(device_id=dev_id,
expectations=[
{ type: "text_present", value: "Are you sure" },
{ type: "window_title_contains", value: "Confirm" }
])
→ allPassed=true: safe to proceed
→ allPassed=false: unexpected state, abort and report
cd QuickDesk/quickdesk-mcp
cargo build --release
# Binary: target/release/quickdesk-mcp (or quickdesk-mcp.exe on Windows)- Rust 1.75+
- Cargo
All dependencies are managed by Cargo. Key crates:
| Crate | Purpose |
|---|---|
rmcp |
MCP SDK for Rust |
tokio-tungstenite |
WebSocket client |
serde / serde_json |
JSON serialization |
clap |
CLI argument parsing |
tracing |
Structured logging |
schemars |
JSON Schema generation for MCP tool parameters |
Make sure QuickDesk is running. The WebSocket API server must be active at ws://127.0.0.1:9600.
- Check that QuickDesk has the MCP HTTP Service toggled ON (in HTTP/SSE mode)
- Verify the URL matches the endpoint shown in QuickDesk MCP settings (default local URL:
http://127.0.0.1:18080/mcp) - Ensure no firewall is blocking the HTTP port
- For remote access, the
--hostmust be0.0.0.0(not127.0.0.1)
Ensure the remote connection is established and video frames are being received. Call list_connections to verify the connection state is "connected".
If using max_width in screenshots, remember to scale coordinates back to full screen resolution using get_screen_size.
Stop any running quickdesk-mcp process before rebuilding:
# Windows
Get-Process quickdesk-mcp -ErrorAction SilentlyContinue | Stop-Process -ForceEnable debug logging via the RUST_LOG environment variable:
RUST_LOG=debug quickdesk-mcpLogs are written to stderr and won't interfere with the stdio MCP transport.
QuickDesk MCP includes a comprehensive security system.
| Level | Token Flag | Allowed Operations |
|---|---|---|
| Full Control | --token |
All operations (screenshot, click, type, connect, etc.) |
| Read-Only | --readonly-token |
screenshot, getScreenSize, getHostInfo, getStatus, listConnections, getConnectionInfo, getClipboard, getHostClients, getSignalingStatus |
Restrict which devices the AI can connect to:
quickdesk-mcp --token SECRET --allowed-devices "111222333,444555666,777888999"Connection attempts to unlisted devices will be rejected with a 403 error.
Prevent runaway AI operations:
quickdesk-mcp --token SECRET --rate-limit 60Exceeding the limit returns HTTP 429. The limit uses a sliding 1-minute window.
Auto-disconnect idle sessions:
quickdesk-mcp --token SECRET --session-timeout 3600Sessions with no activity for the specified seconds are disconnected.
The following operations are automatically blocked via the API:
disconnectAll(mass disconnection)Alt+F4hotkey (close application)Ctrl+Alt+Deletehotkey- Typing text containing:
shutdown,reboot,format,rm -rf,del /f /s /q,mkfs
These return a 403 error with a descriptive message.
All API operations are logged to logs/quickdesk_audit.log (rotating, 10MB x 5 files):
[2026-03-01 23:45:12.345] [ALLOW] client_1 method=screenshot params={"deviceId":"111222333"}
[2026-03-01 23:45:13.456] [DENY] client_2 method=mouseClick params={"x":100,"y":200} reason=permission_denied
[2026-03-01 23:45:14.567] [DENY] client_1 method=keyboardType params={"text":"shutdown /s"} reason=dangerous_operation
{
"mcpServers": {
"quickdesk": {
"command": "quickdesk-mcp",
"args": [
"--rate-limit", "120",
"--session-timeout", "7200",
"--allowed-devices", "111222333,444555666"
],
"env": {
"QUICKDESK_TOKEN": "your-secret-token"
}
}
}
}QuickDesk includes a host-side skill host (quickdesk-skill-host) that runs structured tools directly on the remote machine, complementing the screen-based MCP tools.
| Skill | Binary | Tools |
|---|---|---|
| System Info | sys-info |
get_system_info, list_processes |
| File Operations | file-ops |
read_file, write_file, list_directory, create_directory, move_file, get_file_info |
| Shell Runner | shell-runner |
run_command |
Two MCP tools bridge AI clients to the skill host:
| Tool | Description |
|---|---|
skill_exec |
Execute a tool on the remote skill host. Parameters: device_id, tool_name, arguments |
skill_list_tools |
List all tools available on the remote skill host. Parameters: device_id |
{
"name": "skill_exec",
"arguments": {
"device_id": "123456789",
"tool_name": "run_command",
"arguments": {
"command": "systeminfo"
}
}
}Skills are loaded from per-skill subdirectories under the skills/ directory:
skills/
sys-info/
sys-info.exe
SKILL.md
file-ops/
file-ops.exe
SKILL.md
Users can add custom skills directories in Settings > AI > Skills Directories. Each skill directory must contain a SKILL.md with OpenClaw-compatible frontmatter and the corresponding binary.
The skill host can be enabled or disabled in Settings > AI > AI Agent. This setting is persisted and controls whether the quickdesk-skill-host process starts with the host. When disabled, no skill host capabilities are reported to connected clients.
QuickDesk MCP includes a persistent device memory system (SQLite) that automatically records operation history and device profiles.
| Tool | Description |
|---|---|
get_device_profile |
Get device profile (OS, hardware, connection history) |
update_device_profile |
Update a profile field |
get_device_summary |
Comprehensive device summary (success rate, top tools, common failures) |
| Tool | Description |
|---|---|
search_operation_history |
Search operation history with filters (tool, keyword, success) |
get_failure_memory |
Get failure records and pattern analysis |
Device profiles are auto-created/updated on connection. skill_exec calls are automatically logged.
Record AI operation sequences as reusable workflows with parameterized replay.
| Tool | Description |
|---|---|
start_recording |
Start recording. Params: name, device_id |
stop_recording |
Stop recording and save. Params: device_id, description, tags |
list_workflows |
List all saved workflows |
get_workflow |
Get full workflow details with all steps |
replay_workflow |
Replay a workflow with optional argument overrides |
delete_workflow |
Delete a workflow |
start_recording → execute skill_exec calls → stop_recording → get reusable workflow
replay_workflow → replay on another device
During recording, all skill_exec calls are automatically captured as workflow steps.
Risk assessment, confirmation approval, and emergency stop for high-risk operations.
| Level | Description | Behavior |
|---|---|---|
| Safe | Read-only operations (screenshot, queries) | Execute directly |
| Low | Low-risk tools | Execute directly |
| Medium | Input operations (click, type) | Configurable confirmation |
| High | Command execution, file writes | Requires user confirmation |
| Critical | Policy-blocked operations | Rejected |
| Tool | Description |
|---|---|
assess_risk |
Assess risk level of a tool call |
emergency_stop |
Activate emergency stop, halt all AI operations |
deactivate_emergency_stop |
Deactivate emergency stop |
get_emergency_status |
Get emergency stop status |
get_trust_policy |
Get current trust policy |
set_trust_policy |
Update trust policy |
get_audit_log |
Get audit log entries |
resolve_confirmation |
Respond to a pending confirmation request |
skill_exec has built-in trust layer integration:
- Check emergency stop → reject if active
- Risk assessment → reject if policy-blocked
- Confirmation required → show Qt dialog for user approval
- Audit log recorded after execution