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
This commit is contained in:
1 parent
a72e6eb391
commit
9eed43bc8d
13 files changed
+932
-10
No files matched your search
@@ -0,0 +1,219 @@
|
||||
# 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)`。
|
||||
Reference in new issue
Block a user