Files
SimpleRemoter/docs/Mcp_Manage_Design.md
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

220 lines
13 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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`
```jsonc
// 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`
```jsonc
// 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`
```jsonc
// 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`
```jsonc
// 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)`。