Files
SimpleRemoter/docs/Mcp_RemoteControl_Design.md
yuanyuanxiang 84e3565de1 doc: Add remote control MCP design doc
Add docs/Mcp_RemoteControl_Design.md, the reference design for an
screenshot-driven AI remote-control feature: the existing get_screenshot for
observation plus remote_open/remote_close/remote_mouse/remote_keyboard for
input injection, reusing COMMAND_SCREEN_PREVIEW_REQ and COMMAND_SCREEN_CONTROL
+ MSG64. Coordinates are normalized (0..1) and mapped server-side to physical
pixels; injection runs over the screen sub-connection opened by
WebService::StartRemoteDesktop's hidden CScreenSpyDlg. Includes a
chat-on-behalf experiment case with clipboard-encoding and window-capture
notes.

Also fold in the get_audit_log encoding correction for
docs/Mcp_Terminal_Design.md, so the two doc changes ship as one commit.

Co-Authored-By: deepseek-v4-pro
2026-08-24 21:51:12 +02:00

35 KiB
Raw Permalink Blame History

MCP 远程控制Remote Control设计文档

版本v1设计基线 状态待开发 关联docs/Mcp_Design.mddocs/Mcp_Terminal_Design.md 定位本文件是「AI 远程控制一台 Windows 客户端(像人一样点鼠标、敲键盘、看画面完成任务)」功能的唯一参照设计,后续开发一律以本文为准,改需求先改本文。


1. 背景与目标

1.1 问题

现有 MCP 工具只能做「文本/命令行」层面的操作(exec_command 一次性命令、terminal_* 持久终端、list_files/get_screenshot 等只读)。很多真实任务只能在 GUI 上完成:点按钮、填表单、拖拽窗口、操作只有图形界面的软件。

AI 无法「看懂」视频流。把 25fps 的远程画面直接丢给视觉模型是行不通的——每秒 25 张、每张数十万像素,海量且绝大部分帧对决策无用。

1.2 目标

提供一组 MCP 工具,让 AI 以**「截图 → 观察 → 决策 → 注入单个输入动作 → 等待 → 重复」**的闭环驱动远程桌面:

  • Observe按需取一帧解码后的位图JPEG按视觉模型能力缩放到合适尺寸。
  • Act:注入鼠标/键盘事件,复用现有远程桌面的输入注入链路。
  • Decide:由 AI/宿主Claude Code / 任何 MCP 客户端)在工具返回结果之上编排循环——YAMA 只提供原语,不提供黑盒 perform_task(与终端设计同一哲学)。

1.3 关键设计决策(已确认)

# 决策 理由
D1 截图驱动,非视频流 每次决策一张图38s/步;视觉模型天然面向静态图
D2 原语 + AI 编排,非黑盒任务 可控、可审、可中断AI 在循环外可插判断/确认
D3 归一化坐标0..1 彻底规避「截图缩放 vs 注入物理像素」的比例错位(最佳实践第一大坑)
D4 复用现有注入链路 COMMAND_SCREEN_CONTROL + MSG64 + 客户端 SendInput 已成熟
D5 Observe 复用 get_screenshot 主连接按需抓 JPEG无需常驻子连接最省
D6 独立开关 + 全程审计 与终端同级:McpRemoteControl=1 && McpReadonly=0,默认关

2. 行业最佳实践借鉴

本节是「科学设计」的依据,逐条落到下文对应章节。主要参考 Anthropic 官方 Computer Use / Browser Use 最佳实践与参考实现(见文末「参考」)。

最佳实践 本设计落地
视觉模型是「规划者+眼睛」,宿主是「手」;工具调用=动作,返回=截图 §3 循环归属YAMA=手AI=规划者
客户端先自行缩图,绝不让模型 API 静默缩放(否则坐标系统性偏移) §5客户端按 max_width 缩图后 JPEG 回传,模型见到的就是声明尺寸
声明 display_width/height 必须 == 实际发送的图尺寸 §7归一化坐标服务器侧映射物理像素不依赖声明尺寸
动作原语集合move/click/drag/scroll/type/key/…) §4remote_mouse/remote_keyboard 的动作枚举对齐
文本指令放在截图之前(内容顺序) §8/宿主侧建议:由 MCP 客户端保证,本文仅记录
缩图上限:长边 ~1568px、总像素 ~1.15MPClaude 4.6 系);推荐 1280×720 §5.3max_width 默认建议 1280可配
输入注入 vs 高危动作不可逆操作应人工确认human-in-the-loop §9.4:审计 + 宿主确认YAMA 不做每动作确认(防延迟)
提示注入是首要风险(模型处理不可信 UI 文本) §9.5:全审计 + 只读关才可用 + 建议宿主隔离
长会话上下文管理(滚动缓冲/压缩) §3宿主职责本文提示

3. 总体架构

┌──────────────────── 宿主Claude Code / MCP 客户端)────────────────────┐
│  系统提示(含远程控制约定) + 任务文本                                    │
│        ↓                                                              │
│  ┌──────────────────────── 智能体循环 ─────────────────────────┐      │
│  │ get_screenshot(max_width) ──→ 视觉模型看图 ──→ 决策 ──→      │      │
│  │ remote_mouse/remote_keyboard(归一化坐标/文本) ──→ 等待0.52s │      │
│  │ 循环,直到任务完成或模型判定需用户介入                        │      │
│  └────────────────────────────────────────────────────────────┘      │
└───────────────────────────────┬───────────────────────────────────────┘
                                │ JSON-RPC over HTTP POST /mcp (Bearer)
┌───────────────────────────────▼───────────────────────────────────────┐
│  YAMA 服务器McpServer.cpp                                          │
│  · get_screenshotCOMMAND_SCREEN_PREVIEW_REQ → 客户端缩图 JPEG        │
│  · remote_openCOMMAND_SCREEN_SPY → 建立屏幕子连接(控制会话)        │
│  · remote_mouse/remote_keyboard归一化→物理像素 → MSG64 →             │
│    COMMAND_SCREEN_CONTROL经屏幕子连接                              │
└───────────────────────────────┬───────────────────────────────────────┘
                                │ 主连接(控制)+ 屏幕子连接(注入)
┌───────────────────────────────▼───────────────────────────────────────┐
│  客户端ClientDll / ScreenManager / ScreenPreview                   │
│  · CaptureAndEncodePreview抓主屏 + StretchBlt 缩图 + JPEG            │
│  · ScreenManager.ProcessCommand解码 MSG64 → SendInput               │
└───────────────────────────────────────────────────────────────────────┘

核心事实(已核实,见 §13 源码索引)

  • Observe主连接、无子连接、无需控制模式:COMMAND_SCREEN_PREVIEW_REQ(247) → 客户端抓主屏缩图 JPEG → TOKEN_SCREEN_PREVIEW_RSP(248)。现有 get_screenshot 已完整实现。
  • Act注入必须走屏幕子连接COMMAND_SCREEN_CONTROL(20) 只能经 COMMAND_SCREEN_SPY(16) 建立的子连接发送(hub.go 注释input events MUST go through the sub-connection。故需要一个 remote_open 建立/持有该子连接。
  • 服务器端同时维护一张原始 BGRA 32bpp DIBm_BitmapData_Full),但 MCP 的 get_screenshot 不使用它——它拿的是客户端已编码的 JPEG。两者坐标空间不同见 §7。

4. 工具集设计

4.1 工具清单

工具 作用 入参required 标注) 出参
get_screenshot已有,复用 Observe id;可选 max_width(64..1920) JPEG 图片(见 §5
remote_open 建立控制会话(屏幕子连接) id;可选 timeout_ms session_id(32hex)、screen_w/screen_h(物理虚拟桌面像素)
remote_close 关闭会话 idsession_id closed(bool)
remote_mouse 注入鼠标 idsession_idactionxy+ 按 action 需 x2/y2/button/delta/clicks {}(成功即空对象)
remote_keyboard 注入键盘 idsession_idaction+ 按 action 需 text/key/modifiers {}

命名对齐现有 terminal_*open/exec/closeObserve 沿用 get_screenshot 避免重复造轮子。鼠标/键盘各一个工具、用 action 枚举区分具体动作——既对齐 Anthropic 的 computer 工具动作集,又贴合现有 WebService::HandleMouse/HandleKey 的「鼠标/键盘分家」实现。

4.2 remote_mouse 动作枚举

action 参数 语义
move x,y 移动光标(不按键)
down x,y,button(left/middle/right) 按下
up x,y,button 抬起
click x,y,button(默认 left)、clicks(1/2/3默认 1) 单击/双击/三击
right_click x,y 右键(click 的便捷别名)
middle_click x,y 中键
drag x,y,x2,y2,button(默认 left) 从 (x,y) 拖到 (x2,y2)
scroll x,y,delta(+/- 滚动量)、button(可选,垂直默认) 滚轮

4.3 remote_keyboard 动作枚举

action 参数 语义
type text 输入一串文本(字符级映射,非粘贴)
key_down keymodifiers(可选) 按下组合键(如 Ctrl+C
key_up keymodifiers(可选) 抬起
key_press keymodifiers(可选) 按下并抬起(最常用)

key 取值对齐 Windows 虚拟键名(VK_* 去掉前缀,如 ENTER/TAB/F5/LEFT)与常见修饰键 CTRL/ALT/SHIFT/WINtype 文本中的字符经客户端 VkKeyScan/Unicode 注入映射为键盘事件(非剪贴板粘贴,保证焦点内输入)。

4.4 可选/扩展工具(remote_clipboardremote_focus_window 为 §17 代聊实验所需,建议随阶段一落地)

  • remote_clipboard写远程剪贴板UTF-8→Unicode非 ASCII 文本输入的关键路径;复用 COMMAND_C2C_TEXT(90) 的 UTF-8 逻辑或新增 UTF-8 设剪贴板命令§6.4)。
  • remote_focus_windowCOMMAND_SCREEN_WINDOW(153) / 客户端 SetForegroundWindow,把焦点给到指定窗口(配合 list_windowshwnd);代聊第一步。
  • remote_cursor_position:读当前光标(TOKEN_NEXTSCREEN 帧头已带 POINT cursor)。
  • remote_screenshot(会话内/窗口抓屏):从子连接 DIB 服务端缩图,或按窗口抓取(COMMAND_SCREEN_WINDOW供高分辨率读聊天文字与多显示器场景§12 阶段二、§17

5. 屏幕捕获与编码Observe

5.1 现有链路(复用)

get_screenshotMcpServer.cpp BuildGetScreenshot~1597已实现

  1. ParseHostIdArgFindMainContext → 能力门 ctx->SupportsScreenPreview()CLIENT_CAP_SCREEN_PREVIEW,不支持 → -32005)。
  2. BeginPending(devId,"get_screenshot") 设备级互斥(忙 → -32003)。
  3. reqId = NextPreviewReqId()SetPendingReqId(devId, reqId)stale-drop 机制)。
  4. parent->ChooseScreenPreviewParams(...) 按 RTT/FRP 自适应缩图档位 + max_width 覆盖clamp 64..1920)。
  5. SendScreenPreviewRequestCOMMAND_SCREEN_PREVIEW_REQ
  6. WaitPending[ScreenPreviewRspHeader][JPEG],校验 status==OK && format==JPEG
  7. base64 JPEG 返回:structuredContent.image = {mimeType:"image/jpeg", width, height, bytes}content=[{type:"image", data:<base64>, mimeType:"image/jpeg"}]

客户端 CaptureAndEncodePreviewclient/ScreenPreview.cpp:107)抓主屏 + StretchBlt(HALFTONE) 缩图 + GDI+ Bitmap::Save JPEG——位图永不离开客户端,只回传 JPEG

5.2 缩放策略(对齐最佳实践 D5

  • 缩图在客户端完成(max_width 长边约束),服务器/MCP 宿主不再二次缩放,模型看到的就是声明尺寸——避免「模型 API 静默缩放导致坐标漂移」这个头号坑。
  • max_width 由宿主按视觉模型能力显式传入YAMA 提供默认建议 1280(长边),上限维持 1920。

5.3 推荐参数

建议值 说明
max_width(长边) 1280默认/ 1568上限 1280×720 ≈ 80% 像素预算、训练常见分辨率
编码 JPEGquality ~7085 客户端 jpegQuality 已有,按 ChooseScreenPreviewParams 自适应
返回 base64 image/jpeg 已实现

6. 输入注入Act

6.1 协议(复用,已核实)

  • 包格式[cmd:1][MSG64:48],可批量重复(commands.h COMMAND_SCREEN_CONTROL=20MSG64 15811623
  • MSG64 布局{uint64 hwnd, message, wParam, lParam, time; POINT pt;} = 48 字节(_WIN64MYMSGMSG;客户端亦接受 28 字节 MSG32,由 ulLength % 28/48 判定)。
  • 客户端解码ScreenManager.cpp:1175ProcessCommand(1727)SendInput
    • 鼠标:坐标取 LOWORD/HIWORD(lParam)WM_MOUSEMOVEMOUSEEVENTF_MOVEWM_LBUTTONDOWN/UPLEFTDOWN/LEFTUPWM_LBUTTONDBLCLK→额外 LEFTDOWNWM_MBUTTONDOWN/UPWM_MOUSEWHEELMOUSEEVENTF_WHEEL(mouseData=GET_WHEEL_DELTA_WPARAM)。
    • 键盘:wVk=(WORD)wParamwScan=(lParam>>16)&0xFFKEYEVENTF_EXTENDEDKEY(lParam>>24)&1

6.2 服务端可复用实现

WebService::HandleMouseWebService.cpp:773-865)与 HandleKey867-952)已从 JSONtype/x/y/button/deltakeyCode/down/altKey)构造 MSG64[COMMAND_SCREEN_CONTROL][MSG64] 发送。其中 HandleKey 已正确处理 lParamrepeat=1、MapVirtualKey 扫瞄码、扩展键表、Alt 上下文位 29、keyup 位 3031。

结论remote_mouse/remote_keyboard 是这两个函数的 MCP 薄封装——先归一化→物理像素,再套用其 MSG64 构造逻辑。不要新写注入协议

6.3 子连接前置条件(关键约束)

注入必须经屏幕子连接:

  1. 主连接发 COMMAND_SCREEN_SPY(16)(载荷 {cmd, USING_DXGI|2, ALGORITHM_*, MultiScreen},见 2015RemoteDlg.cpp:8443-8470)→ 客户端开子连接(ClientDll.cpp:89-94)。
  2. 客户端回 TOKEN_BITMAPINFO(含物理 BITMAPINFOHEADERbiWidth/biHeight)——这是归一化坐标映射所需的物理分辨率来源
  3. 服务器发 COMMAND_NEXT(30) 开始流/允许控制。
  4. GetScreenContext(device_id)WebService.cpp:2084)从 m_ScreenContexts 取该子连接上下文。

实现提示:上述 14 的建立/持有逻辑耦合在 CScreenSpyDlg(隐藏对话框)里——子连接、帧 DIB、TOKEN_BITMAPINFO 处理都由它完成。MCP 的 remote_open 应复用 WebService::StartRemoteDesktop(1725) 的「隐藏对话框」路径而非另造无对话框持有者§13.2)。

remote_open 即上述 14 的封装;screen_w/screen_h 取自 TOKEN_BITMAPINFO

6.4 文本输入ASCII 直注 vs 非 ASCII 走剪贴板

remote_keyboardtype 走物理键事件(wVk/wScanSendInput),只能可靠注入 ASCII/ANSI 字符——中文等非 ASCII 文本无法靠模拟键盘键入(需 IME 组合,键事件层面做不到)。

非 ASCII 文本的可靠路径是剪贴板 + 粘贴

  • 服务器设远程剪贴板命令 COMMAND_SCREEN_SET_CLIPBOARD(25),包 [cmd:1][text:N],经屏幕子连接发送;客户端 UpdateClientClipboardScreenManager.cpp:1223/1599)落到剪贴板。
  • 随后 remote_keyboard 注入 Ctrl+V 粘贴、Enter 发送。
  • 编码注意(已核实)UpdateClientClipboardSetClipboardData(CF_TEXT,...)——ANSI/GBK 格式(CF_TEXT,非 CF_UNICODETEXT。MVP 下中文可经 ToAnsi(utf8, 936) 转 GBK 后直发(与终端命令同模式);但 emoji/非 GBK 字符会丢、部分新式聊天软件粘贴偏好 Unicode。更稳做法是复用 COMMAND_C2C_TEXT(90) 的 UTF-8→Unicode→CF_UNICODETEXT 逻辑(KernelManager.cpp:1573-1587,且已内置「设完剪贴板后自动模拟 Ctrl+V」或新增一个 UTF-8 设剪贴板变体。见 §17 实验案例。

7. 坐标约定与映射

7.1 归一化坐标D3

  • AI 在所有鼠标动作里输出 0..1 浮点x = 目标横向比例y = 目标纵向比例,相对它看到的那张截图
  • 服务器侧映射:phys_x = round(norm_x * screen_w)phys_y = round(norm_y * screen_h),其中 screen_w/hremote_open 时从 TOKEN_BITMAPINFO 得到的物理捕获分辨率
  • 好处:无论 max_width 怎么缩、显示器多大AI 永远只说「在图的 40%/60% 处点一下」,比例错位风险为零。

7.2 坐标空间一致性(最重要正确性点)

Observeget_screenshot主屏)与 ActSendInput 绝对坐标在虚拟桌面空间)默认不同源。阶段一只支持单显示器:主屏 == 虚拟桌面,两者重合,映射严格正确。

多显示器(MultiScreen)在阶段二处理:届时 Observe 必须改抓整个虚拟桌面(而非主屏),screen_w/h 用虚拟桌面尺寸,归一化坐标才继续成立。在阶段二落地前,remote_open 对多显示器配置应返回显式错误或降级为主屏(见 §12

7.3 边界与钳制

  • 归一化值越界(<0>1)→ 钳制到 [0,1] 或返回 -32602(建议钳制,模型偶发 1.0001)。
  • phys_x/y 不得越过 screen_w/h,注入前 clamp(0, screen_w-1)

8. 控制会话状态机

8.1 生命周期

remote_open ──► 建立子连接 ──► [remote_mouse/remote_keyboard × N + get_screenshot × M]
     │                                              │
     └──busy 互斥 / idle 回收 / 断线清理)──────────┴──► remote_close幂等

8.2 状态与不变量(镜像 docs/Mcp_Terminal_Design.md §并发)

  • 单设备单控制会话remote_open 对已存在会话 → -32003(忙)。
  • 互斥同一设备上MCP 远程控制会话与人类远程桌面观看m_ScreenContexts 已被占用)互斥——remote_open 发现已有屏幕子连接 → -32003,避免 AI 向人类正在观看/控制的画面注入。remote_mouse/keyboard 要求 session_id 匹配且会话未关闭。
  • busy 标志:一条注入在飞时 busy=true,保证等待线程是唯一擦除者(沿用终端的 m_TermMutex+busy 模式;远程控制建议独立 m_ScreenCtrlMutex/m_ScreenCtrlCv)。
  • idle 回收:会话闲置超时(默认 300s自动关闭并 CancelIO(镜像 SweepIdleTerminals)。
  • 断线清理OfflineProc/OnTerminalClosed 同位置加 McpServer().OnScreenControlClosed(subCtx),防悬空 subCtx 被连接池复用(复刻终端设计里发现的第 2 个修复点)。
  • 锁外 CancelIO:所有取消动作在锁外执行(终端的 P2 教训)。

8.3 并发P5 教训)

httplib 多线程并发处理请求;remote_mouse 会高频调用。会话状态一律 m_ScreenCtrlMutex 串行化,不要假定请求串行。高频注入可加节流(沿用 SendScaledMouseMessage 的鼠标移动节流思路,ScreenSpyDlg.cpp:2728-2743)。


9. 安全模型

延续既有「可控、可关、可审、可隔离」原则(与终端同级):

9.1 开关与门控

  • 新增 McpRemoteControl(默认 0=关)。
  • remote_* 生效条件:McpRemoteControl=1 && McpReadonly=0,否则 -32006
  • get_screenshot 是只读工具,不受此门控,仍仅受现有能力门(供纯观察场景)。

9.2 全程审计

每条 remote_open/close/mouse/keyboard 调用 → PostMessageA(WM_SHOWERRORMSG, ...)m_MessageLogget_audit_log 可查(复用 McpServer.cpp:2050 的审计模式,标题 MCP远程控制,标记「不可关闭」)。审计内容含:设备 id、动作、归一化坐标与换算后的物理坐标、session_id、时间戳。

9.3 会话级鉴权

  • session_idGenerateRandomToken()32hex每次 remote_open 独立随机,防串用(镜像终端 P6
  • 注入工具校验 session_id 归属与设备匹配,不匹配 → -32002

9.4 不可逆操作与人工确认

  • YAMA 不做逐动作确认(会引入每步 0.5s+ 延迟,破坏闭环节奏)。
  • 高危动作(提交表单、删除、付款、改系统设置)的确认由宿主/AI 编排层负责Claude Code 的权限系统 / 系统提示约定「不可逆操作前暂停询问用户」。本文记录此分工YAMA 提供审计留痕兜底。

9.5 提示注入Prompt Injection

视觉模型会读到客户端屏幕上的不可信 UI 文本,存在注入风险。缓解:远程控制本身即高危能力(默认关 + 只读关才开 + 全审计建议宿主将远程控制会话隔离在独立权限域并对「屏幕文本里的指令」保持怀疑。YAMA 侧不做内容分类(无意义且加延迟),记录即可。


10. 错误码

沿用现有约定(BuildError(id, code, msg)

code 含义 触发
-32602 参数非法 action 未知、必填参数缺失、max_width 越界、归一化值格式错
-32000 通用失败 未分类错误
-32001 超时 remote_open 等子连接/get_screenshot 等帧超时
-32002 设备/会话不存在 id 无在线主机、session_id 不存在或不匹配
-32003 设备/会话忙 已有控制会话、人类远程桌面占用、get_screenshot 在飞
-32004 发送失败 注入命令发送失败
-32005 能力不支持 客户端无 CLIENT_CAP_SCREEN_PREVIEWObserve/ 不支持屏幕控制Act
-32006 被开关禁用 McpRemoteControl=0McpReadonly=1
-32008 非法/被拒参数 多显示器未支持时的 remote_open 降级拒绝(阶段一)

11. 配置项

新增 1 项(THIS_CFG settings 节,键名沿用蛇形):

默认 说明
McpRemoteControl 0 远程控制开关;要求 McpReadonly=0 才生效

其余沿用:McpEnabled/McpPort/McpBind/McpToken/McpReadonly/McpTerminal/McpCmdWhitelist。改动需重启生效(与终端一致)。


12. 已知限制与后续扩展

12.1 阶段一(本期)

  • 单显示器Observe 抓主屏,坐标空间与虚拟桌面重合;多显示器 remote_open 显式报错(-32008)。
  • 注入基于绝对坐标 SendInput,要求客户端处于可注入的桌面会话(已登录、非锁屏/非 UAC 安全桌面);锁屏/UAC 提示场景注入无效(客户端 SendInput 限制),设计文档记录为已知限制。
  • 代聊实验使能项:窗口抓屏(COMMAND_SCREEN_WINDOW、UTF-8 剪贴板、窗口聚焦随阶段一落地§17

12.2 阶段二(后续)

  1. 多显示器虚拟桌面捕获get_screenshot 增加虚拟桌面抓屏,或新增 remote_screenshot 从子连接 DIB 服务端缩图(复用 BmpToJpeg/CacheThumbnail GDI+ 路径),使 Observe/Act 同源。
  2. 窗口定向remote_focus_window(复用 COMMAND_SCREEN_WINDOW + 客户端 SetForegroundWindow)。
  3. 光标读取 / 剪贴板
  4. 服务端缩图:从 m_BitmapData_FullBGRA DIB直接按 API 上限缩图编码,进一步省客户端往返。

13. 改动清单(供实现参照)

行号以当前树为准(实现前请 grep 复核)。新增/改动尽量「复用」而非「重写」。

13.1 server/2015Remote/McpServer.h

  • 新增 struct ScreenCtrlSession(仿 TermSessionsessionIddeviceIdbusystartedclosedlastActiveAtscreenW/screenH(来自 TOKEN_BITMAPINFO)、subCtx 引用。
  • 新增方法:SetRemoteControlEnabled/IsRemoteControlEnabledBeginScreenCtrlOpen/…/CloseScreenCtrlSession/SweepIdleScreenCtrlOnScreenControlClosed(context*)ResolveScreenCtrlSessionHost
  • 新增成员:bool m_remoteControlEnabled=false;std::mutex m_ScreenCtrlMutex; std::condition_variable m_ScreenCtrlCv; std::map<context*,uint64_t> m_ScreenCtrlContextToDevice; std::map<uint64_t,ScreenCtrlSession> m_ScreenCtrlSessions;(或复用 WebService 的 m_ScreenContexts 生命周期,见下)。

13.2 server/2015Remote/McpServer.cpp

  • 新增 4 个 schema 构建器 + 3 个 handlerBuildRemoteOpen/Close/Mouse/Keyboard(镜像 BuildTerminalOpen 等,BuildToolsListResult ~1278 后加条目,BuildToolsCall ~2124 加分派)。
  • remote_openSweepIdleScreenCtrlResolveScreenCtrlSessionHost → 已有会话 -32003GenerateRandomToken复用 Web 远程桌面的会话建立路径 WebService::StartRemoteDesktop(1725)(发 COMMAND_SCREEN_SPY → 以 SW_HIDE 打开隐藏 CScreenSpyDlg 持有子连接 → 客户端子连接到达 → RegisterScreenContext 记入 m_ScreenContexts)→ 等 TOKEN_BITMAPINFOscreenW/HCOMMAND_NEXT → 审计 → 返回 {session_id, screen_w, screen_h}注意:屏幕子连接/帧 DIB/TOKEN_BITMAPINFO 处理都与 CScreenSpyDlg 耦合,不要另造无对话框持有者——沿 Web 的隐藏对话框路径是低风险正解。
  • remote_mouse/keyboardResolveScreenCtrlSessionHost → 校验 session_id/busy → 归一化→物理像素(round(norm * screenW/H) + clamp复用 WebService::HandleMouse/HandleKeyMSG64 构造(或抽取成共享 helper BuildMouseMsg64/BuildKeyMsg64,避免 MCP 与 Web 两处重复)→ subCtx->Send2Client([COMMAND_SCREEN_CONTROL][MSG64]) → 审计。
  • remote_close:幂等 CloseScreenCtrlSession(不存在 → {closed:true}sid 不匹配 → -32002)→ 锁外 CancelIO → 审计。
  • OnScreenControlClosed:镜像终端修复点——空闲会话直接擦路由+会话busy 会话唤醒等待线程由其清理。
  • SweepIdleScreenCtrl:镜像 SweepIdleTerminalsdifftime 防时钟回拨)。

13.3 server/2015Remote/McpSettingsDlg.h/.cpp

  • 新增复选框 IDC_MCP_REMOTECONTROL = 1008,文案 _TR("启用远程控制AI 操控桌面)");回填/落盘 McpRemoteControl;对话框高度 +30。

13.4 server/2015Remote/2015RemoteDlg.cpp

  • 启动读配置处加 McpServer().SetRemoteControlEnabled(THIS_CFG.GetInt("settings","McpRemoteControl",0)!=0);
  • OfflineProcif (McpServer().IsRunning() && McpServer().IsScreenCtrlContext(ContextObject)) McpServer().OnScreenControlClosed(ContextObject);(复刻终端修复点)。

13.5 语言文件

  • 沿用终端约定MCP 相关文案本就在 lang/*.ini 无条目(_TR 对未命中键原样返回中文)。若日后翻译,用 GBK 工具lang/{en_US,zh_TW}.iniANSI/GBK不能用 Write/Edit 直接写)。

14. 关键点(实现时务必遵守)

  • P1坐标一致性最重要:归一化坐标映射所用 screenW/H 必须与当前截图来源的分辨率一致。阶段一单屏下 TOKEN_BITMAPINFO 的主屏尺寸 == 截图主屏物理尺寸,才成立;任何「用虚拟桌面尺寸映射主屏截图」都会造成系统性偏移(最佳实践第一大坑)。
  • P2注入走子连接COMMAND_SCREEN_CONTROL 必须GetScreenContext 取得的子连接发送,主连接无效。
  • P3锁外 CancelIO:所有 CancelIOm_ScreenCtrlMutex 之外(终端 P2 教训)。
  • P4复用勿重写MSG64 构造逻辑复用 WebService::HandleMouse/HandleKey;理想做法抽共享 helper否则未来两处漂移。
  • P5并发串行化remote_mouse 高频并发,状态全部走锁;勿假定请求串行(终端 P5 教训)。
  • P6session_id 独立随机 + 互斥):会话 token 用 GenerateRandomToken;单设备单会话;与人类远程桌面观看互斥(-32003)。
  • P7非黑盒:不实现 perform_task;循环归宿主/AIYAMA 只出原语D2

15. 验证端到端127.0.0.1:6544Bearer a2176213eddcf0acfdb285940ec4eb2a

前置:设置开 McpEnabled=1McpReadonly=0McpRemoteControl=1;目标 Windows 客户端在线、已登录桌面。idlist_online_hosts 的十进制主机 id。

B='Authorization: Bearer a2176213eddcf0acfdb285940ec4eb2a'
U=http://127.0.0.1:6544/mcp

# 1) tools/list 应出现 remote_open/close/mouse/keyboard 四个工具
curl -s $U -H "$B" -H 'Content-Type: application/json' -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'

# 2) 打开会话 → session_id + screen_w/h
curl -s $U -H "$B" -H 'Content-Type: application/json' -d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"remote_open","arguments":{"id":"<HOSTID>"}}}'

# 3) Observe截图复用
curl -s $U -H "$B" -H 'Content-Type: application/json' -d '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"get_screenshot","arguments":{"id":"<HOSTID>","max_width":1280}}}'

# 4) Act移动 + 单击归一化坐标0.5,0.5 = 屏幕中心)
curl -s $U -H "$B" -H 'Content-Type: application/json' -d '{"jsonrpc":"2.0","id":4,"method":"tools/call","params":{"name":"remote_mouse","arguments":{"id":"<HOSTID>","session_id":"<SID>","action":"move","x":0.5,"y":0.5}}}'
curl -s $U -H "$B" -H 'Content-Type: application/json' -d '{"jsonrpc":"2.0","id":5,"method":"tools/call","params":{"name":"remote_mouse","arguments":{"id":"<HOSTID>","session_id":"<SID>","action":"click","x":0.5,"y":0.5,"button":"left"}}}'

# 5) 键盘:按下并抬起 Ctrl+Esc 或输入文本
curl -s $U -H "$B" -H 'Content-Type: application/json' -d '{"jsonrpc":"2.0","id":6,"method":"tools/call","params":{"name":"remote_keyboard","arguments":{"id":"<HOSTID>","session_id":"<SID>","action":"key_press","key":"WIN"}}}'

# 6) 负例:未开会话/错误 sid → -32002已开会话再 open → -32003只读/开关关 → -32006
# 7) 关闭 + 幂等关闭
curl -s $U -H "$B" -H 'Content-Type: application/json' -d '{"jsonrpc":"2.0","id":9,"method":"tools/call","params":{"name":"remote_close","arguments":{"id":"<HOSTID>","session_id":"<SID>"}}}'

# 8) 审计链
curl -s $U -H "$B" -H 'Content-Type: application/json' -d '{"jsonrpc":"2.0","id":12,"method":"tools/call","params":{"name":"get_audit_log","arguments":{}}}'

人工核对:注入后目标机光标/焦点确实变化审计列表出现标题「MCP远程控制」的 open/close/mouse/keyboard 条目;闲置 >300s 后旧 session_id 调用 → -32002。编译由你以 VS2019MSBuild v143完成我侧仅代码审查工具集 v142无法在此构建


16. 涉及文件

  • server/2015Remote/McpServer.h / .cpp(会话状态机 + schema + handler + 分派)
  • server/2015Remote/McpSettingsDlg.h / .cpp(新开关)
  • server/2015Remote/2015RemoteDlg.cpp(启动读配置 + OfflineProc 清理钩子)
  • common/commands.h只读参照COMMAND_SCREEN_SPY/COMMAND_SCREEN_CONTROL/MSG64/COMMAND_SCREEN_PREVIEW_REQ 等,无需改)
  • server/2015Remote/WebService.cpp复用参照HandleMouse/HandleKey/GetScreenContext
  • lang/{en_US,zh_TW}.ini可选GBK 工具改)

17. 实验案例AI 代聊chat-on-behalf

目标以「AI 通过远程桌面,在聊天软件里代表你与朋友聊天」为第一个实验用例,验证整条「看 → 想 → 动 → 确认」链路。低风险、目标清晰、信号明确(有新消息 / 已发出)。

17.1 闭环

  1. list_windows 找到聊天窗口(hwnd/title)。
  2. remote_focus_window(或 remote_mouse 点击输入框)聚焦聊天窗。
  3. 聊天窗口高分辨率截图 → 视觉模型读出朋友最新消息。
  4. 模型生成回复。
  5. remote_clipboard 把回复写入远程剪贴板UTF-8
  6. remote_keyboard 注入 Ctrl+V(粘贴)+ Enter(发送)。
  7. 再截图确认已发出,等待下一条。

17.2 两个已验证的关键依赖

  • 中文输入(走剪贴板,已解决):见 §6.4。COMMAND_SCREEN_SET_CLIPBOARD(25) 为 ANSI/GBKCF_TEXT),中文可经 ToAnsi(utf8, 936) 直发跑通 MVP要支持 emoji/完整 Unicode复用 COMMAND_C2C_TEXT(90) 的 UTF-8→CF_UNICODETEXT 逻辑或新增 UTF-8 变体。客户端 KernelManager.cpp:1590 已内置「设剪贴板后自动模拟 Ctrl+V」。
  • 读消息(需窗口抓屏)get_screenshot 抓整屏,缩到 1280 后聊天文字过小、OCR 易错。用 COMMAND_SCREEN_WINDOW(153) 按窗口抓取聊天窗(更高有效分辨率),或对该区域用更高 max_width

17.3 使能清单(相对现有代码的增量)

能力 状态 复用点
get_screenshot / list_windows 已有
剪贴板写入 ⚠️ 客户端已有GBK 直发可跑通),缺 MCP 封装 COMMAND_SCREEN_SET_CLIPBOARD(25) / COMMAND_C2C_TEXT(90)
remote_open / remote_close(屏幕子连接) 待建 COMMAND_SCREEN_SPY(16) 建立逻辑 2015RemoteDlg.cpp:8443
remote_mouse / remote_keyboard 待建 WebService::HandleMouse/HandleKey
remote_focus_window 待建 COMMAND_SCREEN_WINDOW(153)
remote_clipboard 待建 见 §6.4

17.4 最小 MVP 实施顺序(按依赖)

  1. remote_open / remote_close(屏幕子连接 + TOKEN_BITMAPINFO 取分辨率)。
  2. remote_keyboard(先只做 key_press/type + 修饰键,够发 Ctrl+V/Enter)。
  3. remote_mouseclick/move,用于点输入框;聚焦可用点击替代)。
  4. remote_clipboard(先 GBK 直发跑通,再上 UTF-8 变体)。
  5. remote_focus_window(或用步骤 3 的点击替代,二选一即可跑通)。

1→2→3 已足以发出一条消息(点输入框 + 粘贴 + 回车4 是中文关键路径5 是体验优化,可后置。

17.5 实验注意事项

  • 目标机必须已登录且未锁屏(锁屏/UAC 安全桌面下抓屏与注入均失效§12.1)。
  • 「代聊」会让朋友误以为是真人在聊——实验前建议告知对方;避免让 AI 在承诺/隐私等话术上自由发挥§9.4 的人工确认原则同样适用)。
  • 新消息检测先用「轮询截图」即可MVP 接受),后续用未读角标/窗口通知优化§17.6)。

17.6 后续优化(非 MVP

  • 新消息触发:从「定时截图」改为「未读角标/窗口通知驱动」,大幅省 token。
  • 窗口定向抓屏(COMMAND_SCREEN_WINDOW)并入 remote_screenshot
  • 会话历史AI 保留最近几轮聊天上下文避免回复脱节宿主侧滚动缓冲§3

参考(行业最佳实践)