Files
SimpleRemoter/docs/Mcp_Manage_Design.md
T
yuanyuanxianganddeepseek-v4-pro 9eed43bc8d Feature: Add MCP device management-control tools (kill/window/service/power)
Add P7 management-control tools behind a single McpManage flag (plus
McpReadonly=0): kill_process, operate_window, control_service and
power_action. The write tools run a ready->confirm lifecycle on their
sub-connection, using a new followUp/followUpSent field to tell the two
arrivals of the same TOKEN_*LIST apart; the write command and the follow-up
refresh command go out as two separate Send2Client calls, with a new
WaitSendIdle() (context.h / Server.h) draining the outbound buffer in between
so the same buffer is never WSASend'd concurrently.

McpSettingsDlg gains a "management operations" checkbox (IDC_MCP_MANAGE), the
main toolbar gains an MCP entry button, and en/de/zh-TW strings are synced.
power_action is fire-and-forget over the main connection (no confirmation).

Verified end-to-end against a real Windows 10/VMware client: kill_process
returns remaining:[] and operate_window/control_service return ok:true with
their confirm packet arriving after an HTTP split. Verification surfaced a
prerequisite parser bug, fixed separately in a72e6eb, where HeaderParser
latched m_bShouldUnmask FALSE on HTTP continuation chunks and dropped the
confirm packet.

Co-Authored-By: deepseek-v4-pro
2026-10-01 16:56:30 +02:00

13 KiB
Raw Blame History

MCP P7 设计:设备管理控制(kill / window / service / power)

延续 Mcp_Design.md → Mcp_Phase2_Design.md(P2)→ P3/P4/P5/P6 的阶段演进。 本文定义 P7:把 YAMA 服务端高频「控制类」操作 MCP 化,补上从「观测」到「管控」的短板, 服务「自然语言即可操作服务端、对受管设备全方位管控」的最终目标。

安全门结论(已确认):新增单一 McpManage 开关统一门控本阶段全部控制类工具, 并要求 McpReadonly=0;不再为每个工具单设开关。

1. 目标与范围

P7 交付 4 个控制类工具,覆盖服务端日常最常用的写操作:

工具 动作 命令 连接 确认方式
kill_process 结束进程(多 pid) COMMAND_KILLPROCESS 系统子连接 补发 COMMAND_PSLIST 收新列表
operate_window 关闭/显示/隐藏/最小化/最大化/还原 CMD_WINDOW_CLOSE / CMD_WINDOW_TEST 窗口子连接 close 自动回 TOKEN_WSLIST;test 补发 COMMAND_WSLIST
control_service 启动/停止/设为自动/设为手动 COMMAND_SERVICECONFIG 服务子连接 客户端每项 Sleep(500) 后自动 SendServicesList
power_action 注销/关机/重启 TOKEN_MACHINE_MANAGE 主连接 发完即走(无响应)

仅 Windows 客户端支持(clientType 为 LNX/MAC/APK 提前返回 -32005;注意 list_services 只排除了 LNX/MAC、漏掉 APK,P7 需把 APK 一并排除)。

2. 复用现状

P7 不改客户端。客户端已全部实现下列处理(MFC 界面在跑),MCP 侧只需补齐 「发命令 → 接管子连接 → 发写命令 → 收确认 → 关闭」这条链:

  • 系统子连接:COMMAND_SYSTEM(进程)/ COMMAND_WSLIST(窗口)→ 客户端开 CSystemManager 子连接(client/KernelManager.cpp 1169/1182,client/SystemManager.cpp)。
  • COMMAND_KILLPROCESS:CSystemManager::KillProcess(TerminateProcess,无回包)。
  • CMD_WINDOW_CLOSE:PostMessage(hWnd, WM_CLOSE) 后自动 SendWindowsList。
  • CMD_WINDOW_TEST:ShowWindow(hwnd, dHow)(不自动回包)。
  • COMMAND_SERVICECONFIG:CServicesManager::ServicesConfig(client/ServicesManager.cpp 213), bCmd=1 启动 / 2 停止 / 3 自动 / 4 手动,每项后 Sleep(500) + SendServicesList。
  • TOKEN_MACHINE_MANAGE:KernelManager.cpp 872,ExitWindowsEx(注销/关机/重启)。

复用现有 MCP 基建(server/2015Remote/McpServer.{h,cpp}):

  • ParseHostIdArg / FindMainContext:入参解析 + 主连接定位。
  • BeginPending / WaitPending / ClearPending / TakeMainResponse:单飞行挂起 + 响应回收。
  • OnRegeditReady:已示范「接管子连接 → 发后续命令」的范式(P3 list_registry 三阶段),P7 直接泛化。

3. 子连接生命周期(P7 核心新增)

写工具与只读工具的关键差异:只读工具「子连接回一包即完成」,写工具需要 「接管子连接 → 发写命令 → 收确认 → 关闭」。在现有 PendingRequest 上增加 followUp(接管后要发的写命令 payload,二进制)+ followUpSent(是否已发写命令)两个字段:

followUpSent == false  →  等子连接就绪(首包 TOKEN_*LIST 到达 = 就绪)
followUpSent == true   →  已发写命令,等确认包

关键事实(已核实):三个工具各自的「就绪 token」与「确认 token」是同一个 token—— 客户端子连接构造即回一包(就绪),写操作后再回一包(确认)。因此必须用 followUpSent 区分这两次到达,而不能靠 token 区分。这也是它与 list_registry(PATH/KEY 两个不同 token) 的差别。

流程(以 kill_process 为例):

  1. 工具线程 BeginPending(devId, "kill_process") 登记单飞行(false = 设备忙),并预填 followUp(COMMAND_KILLPROCESS 多 pid 包)。
  2. 主连接发 COMMAND_SYSTEM。
  3. 客户端开系统子连接,首包 TOKEN_PSLIST 到达 → MessageHandle 调新增的 OnSystemReady(devId, subCtx)(类比 OnRegeditReady):
    • 命中挂起且 followUpSent==false → 保存 subCtx、置 followUpSent=true、向子连接发 followUp,随后补发 COMMAND_PSLIST 索取新列表。
  4. 客户端杀进程后回第二个 TOKEN_PSLIST → TakeMainResponse 取走(followUpSent==true 命中)→ 关闭子连接(CancelIO)→ 唤醒工具线程。
  5. 工具线程解析新进程列表返回;超时/失败走 ClearPending(含 subCtx->CancelIO(),防孤儿子连接)。

注意:followUp(写命令)与「补发的 COMMAND_PSLIST/COMMAND_WSLIST」必须是两次独立的 Send2Client 调用,不能拼成一个 buffer——客户端 OnReceive 按 szBuffer[0] 单命令分发, 拼包会把后续字节当成 pid/hwnd 解析。

接管函数(进程/窗口共用同一个 OnSystemReady——两者都在同一个 CSystemManager 子连接上, 客户端 OnReceive 统一处理 COMMAND_PSLIST/COMMAND_KILLPROCESS/COMMAND_WSLIST/ CMD_WINDOW_CLOSE/CMD_WINDOW_TEST):

工具 开子连接命令 就绪包(首包) 接管后写命令 确认包(第二包)
kill_process COMMAND_SYSTEM TOKEN_PSLIST COMMAND_KILLPROCESS + 补 COMMAND_PSLIST TOKEN_PSLIST
operate_window COMMAND_WSLIST TOKEN_WSLIST CMD_WINDOW_CLOSE(close 自动回);CMD_WINDOW_TEST(test 补 COMMAND_WSLIST) TOKEN_WSLIST
control_service COMMAND_SERVICES TOKEN_SERVERLIST COMMAND_SERVICECONFIG(客户端写后自动 SendServicesList) TOKEN_SERVERLIST

MessageHandle 现有分派点(均已核实):

  • TOKEN_PSLIST/TOKEN_WSLIST 分支:2015RemoteDlg.cpp:6540(当前 TakeMainResponse+CancelIO)
  • TOKEN_SERVERLIST 分支:2015RemoteDlg.cpp:6585(同上)

接入方式:在这两个分支的 IsPending(devId) 命中后,先按 pending.tool 判断是否为 写工具(kill/operate/control_service);是则调 OnSystemReady/OnServiceReady(返回 true = 已接管、不 CancelIO),否则沿用现有 TakeMainResponse+CancelIO。与 TOKEN_REGEDIT 分支调用 OnRegeditReady(2015RemoteDlg.cpp:6561)的写法一致,避免与人工界面抢子连接。

4. 工具 schema

4.1 kill_process

// inputSchema
{ "id": "string(必填,主机 id)",
  "pids": ["integer 数组(必填,至少 1 个进程 PID)"] }
// outputSchema
{ "remaining": ["integer 数组(杀完后仍在运行的 PID)"] }
  • payload:[COMMAND_KILLPROCESS:1][pid:4]*N(DWORD 小端,与 SystemDlg::OnPlistKill 一致)。
  • 成功判定:返回 remaining(发 COMMAND_PSLIST 后进程列表里已不含目标 pid 即视为成功)。
  • 描述需写明:TerminateProcess 强制结束、不保存进程状态,可能造成数据丢失。

4.2 operate_window

// inputSchema
{ "id": "string(必填)",
  "hwnd": "integer(必填,窗口句柄,取 list_windows 返回的 hwnd)",
  "action": "string(必填)close|show|hide|minimize|maximize|restore" }
// outputSchema
{ "ok": "boolean" }
  • payload(与 SystemDlg::OnWlistClose/OnWlistHide/OnWlistRecover/OnWlistMax/OnWlistMin 一致, 固定 20 字节零填充 BYTE lpMsgBuf[20],hwnd 一律按 DWORD(4 字节)小端写入 offset 1):
    • close → [CMD_WINDOW_CLOSE:1][hwnd:4](客户端 PostMessage WM_CLOSE)。
    • 其余 → [CMD_WINDOW_TEST:1][hwnd:4][dHow:4],dHow 映射(MFC 实测值): hide=SW_HIDE(0) / show=SW_NORMAL(1) / restore=SW_NORMAL(1) / maximize=SW_MAXIMIZE(3) / minimize=SW_MINIMIZE(6)。
  • 缓冲器必须补零到 20 字节:客户端 close 分支读 *((HWND*)(szBuffer+1)),在 x64 是 8 字节, 而服务端只写了 4 字节 hwnd——正是 20 字节零填充补齐了高 4 字节,读出的 HWND 才是正确值; 若 MCP 发最小 5 字节包,会越界读。test 分支(TestWindow)读 [hwnd:4][dHow:4] 共 9 字节, 20 字节缓冲也足够。MFC 固定发 lpMsgBuf[20] 正是为此,MCP 照搬。

4.3 control_service

// inputSchema
{ "id": "string(必填)",
  "service_name": "string(必填,服务名,取 list_services 返回的 service_name)",
  "action": "string(必填)start|stop|set_auto|set_manual" }
// outputSchema
{ "ok": "boolean" }
  • payload:COMMAND_SERVICECONFIG [cmd:1][bCmd:1][serviceName\0], bCmd:start=1 / stop=2 / set_auto=3 / set_manual=4。
  • 边界:客户端 ServicesManager 仅实现这 4 项;无 pause / continue / delete (它们属于 MachineDlg 机器管理的另一条链路,留待 P8 评估)。

4.4 power_action

// inputSchema
{ "id": "string(必填)",
  "action": "string(必填)logout|shutdown|reboot" }
// outputSchema
{ "ok": "boolean" }
  • payload:TOKEN_MACHINE_MANAGE [token:1][type:1],type:logout=0 / shutdown=1 / reboot=2 (enum MachineCommand 顺序)。MFC 发的是 32 字节缓冲(2015RemoteDlg::MachineManage), 但客户端只校验 ulLength>1 并读 szBuffer[1],故 2 字节即可。
  • 走主连接、发完即走、无响应;主机将立即离线。描述需明确「不可撤销、主机下线」。
  • 需客户端管理员权限(EnableShutdownPrivilege),无权限时客户端静默忽略(同 MFC 现状)。
  • 仅 Windows 客户端:Linux 侧文档写的是 COMMAND_SESSION(linux/README.md),但代码中 未发现对应 handler,即 Linux 实际未实现电源操作——power_action 对 LNX/MAC/APK 一律返回 -32005,不尝试发送。

5. 安全门(单一 McpManage 开关)

  • 新增配置键 settings/McpManage(默认 0,关闭)。
  • 生效条件:McpManage=1 且 McpReadonly=0;二者任一不满足即隐藏 4 个工具。
  • 双保险:tools/list 隐藏 + 每个 BuildXxx 入口二次校验(与 exec_command 的 IsReadonly() 双保险一致)。
  • McpSettingsDlg 增加「管理操作(进程/窗口/服务/电源)」复选框,写入 THIS_CFG。
  • 审计:每次写操作写入服务端审计日志(get_audit_log 可见),记录 tool + host id + 参数摘要 + 结果。

6. 单飞行与并发约束

  • 沿用现有约束:同 host 同一时刻只允许一个 MCP 挂起请求(BeginPending 保证)。
  • 写工具与只读工具共用 m_Pending,天然互斥;与 MFC 对话框并存(各用独立子连接,互不干扰)。
  • 超时沿用 kMcpToolTimeoutMs = 20000;power_action 因无响应、发完即返回成功。

7. 边界与风险

  1. 杀进程/关机/重启不可逆:描述与审计都需显著标注;不提供「确认对话框」, 确认责任在调用方(MCP 客户端/LLM agent)。
  2. kill 无客户端回执:成功与否靠「新进程列表不含目标 pid」推断,TerminateProcess 失败(权限不足等)时进程仍在 remaining,由调用方判断。
  3. 服务操作仅 4 项:pause/continue/delete 缺省,如需需评估 MachineDlg 链路。
  4. 窗口句柄时效:hwnd 可能已失效(窗口已关闭),客户端 PostMessage/ShowWindow 对失效句柄静默无操作,operate_window 的 ok=true 只表示「已下发」。
  5. 子连接竞态:写工具接管子连接后若客户端断线,OfflineProc 调 OnManageSubClosed 清理挂起(置 done 并清 subCtx、唤醒等待线程;不擦表,由 WaitPendingWrite 自行擦除, 防迭代器失效)。清 subCtx 是关键——context 对象被连接池回收复用,若超时路径仍对旧指针 CancelIO 会误伤新连接(与 OnTerminalClosed/OnScreenControlClosed 同理)。

8. P8 / P9 路线图(后续)

  • P8 文件与信息面:delete_file/delete_directory、create_folder、rename_file、 open_file;list_network_connections、list_software、get/set_hosts、 list_scheduled_tasks(+增删启停)、get_keylog。
  • P9 进阶与 NL 增强:set/delete_registry、screen_blank/lock_input/get_clipboard、 open_url/send_message;批量操作(ids[]/group 维度);离线主机枚举 list_all_hosts。

9. 实现与验证记录(2026-10-01)

已实现并端到端验证(真实客户端 DESKTOP-A55C0OI / Windows 10 / VMware,id=3441387693643366974):

  • kill_process → remaining:[];operate_window(restore) → ok:true; control_service(set_manual AJRouter) → ok:true;power_action(reboot) → ok:true(发完即走)。 三个写工具的确认包(第二个 TOKEN_*LIST)均正常到达,列表均 >1460 字节触发 HTTP 分片。
  • 落地:McpManage 开关 + McpReadonly=0 双保险;McpSettingsDlg 增「管理操作」复选框; 主工具栏增 MCP 入口按钮(ID_MCP_SETTINGS);en/de/zh-TW 三语文案同步。
  • 前置解析 bug(验证中暴露,已单独提交 a72e6eb):HeaderParser::Parse 的 m_bShouldUnmask 在 HTTP 分片续包上被误置 FALSE,导致写工具的确认包被 PARSER_FAILED 丢弃(「首包能到、后续包到不了」);修复为 else if (m_bShouldUnmask != TRUE)。