Feature: Add list_services and get_client_log MCP tools

Add the two P2d read-only MCP tools, both one-shot sub-links (mode A') on
the existing single-flight pending registry.

list_services sends COMMAND_SERVICES; the client's CServicesManager emits
TOKEN_SERVERLIST on sub-link creation, parsed as 5 null-terminated fields per
record (display_name/service_name/binary_path/status/start_type) with
zero-padding termination. Services are Windows-only, so LNX/MAC hosts get
-32005 up front instead of a 20s timeout.

get_client_log sends COMMAND_QUERY_LOG; the client's CClientLogManager dumps
its full in-memory Logger ring buffer once, then pushes deltas every 3s. The
MCP path takes the first (full) TOKEN_REPORT_LOG and cancels the sub-link so
later deltas stop, while the MFC log dialog keeps receiving deltas on the
non-pending branch. Log text is client ANSI on Windows, decoded by clientType
like process/file names. MessageHandle intercepts both with
IsPending -> TakeMainResponse + CancelIO. Sync the design doc (P2d section +
verification notes; Linux get_client_log timeout is a client-version gap).

Co-Authored-By: deepseek-v4-pro
This commit is contained in:
yuanyuanxiang
2026-08-19 14:28:10 +02:00
parent c6c6e1d5ef
commit 5611ba621c
3 changed files with 282 additions and 4 deletions

View File

@@ -1,6 +1,6 @@
# YAMA MCP 功能开发技术书Phase 2 及后续)
> **状态**设计定稿已按两轮评审修订P2a、P2b 已实现并经真实主机验证(`search_hosts` / `get_host_detail` / `list_processes` / `list_windows` / `get_activity_history`P2c 已实现、待实机验证`get_screenshot` / `list_files`)。
> **状态**设计定稿已按两轮评审修订P2a、P2b 已实现并经真实主机验证(`search_hosts` / `get_host_detail` / `list_processes` / `list_windows` / `get_activity_history`P2c 已实现(`get_screenshot` / `list_files`P2d 已实现并验证(`list_services` / `get_client_log`
> **读者**MCP 后续功能的研发/评审人员。
> **关联文档**[Mcp_Design.md](./Mcp_Design.md)Phase 1 的协议、架构、配置与菜单设计)。
> **核心目标**:让 MCP 从「单个只读工具」平滑演进为「分阶段、可回滚、影响面可控」的工具集,**不一次性大改现有功能**。
@@ -196,6 +196,8 @@ Web 是长连接子链接可长驻MCP 是**一次性 request/response**。
| P2b已实现并验证 | `get_activity_history` | 单机历史活动(文本快照) | `COMMAND_QUERY_ACTIVITY`(159)→`TOKEN_REPORT_ACTIVITY`(160) | **A** | 只读 |
| P2c已实现 | `get_screenshot` | 单帧屏幕截图JPEG | `COMMAND_SCREEN_PREVIEW_REQ``TOKEN_SCREEN_PREVIEW_RSP` | **A** | 只读 |
| P2c已实现 | `list_files` | 目录列举 | `COMMAND_LIST_DRIVE``TOKEN_DRIVE_LIST`(列盘)/ 同子链接再 `COMMAND_LIST_FILES``TOKEN_FILE_LIST`(列目录) | **A** | 只读 |
| P2d已实现 | `list_services` | Windows 服务列表 | `COMMAND_SERVICES`(218)→(子链接)`TOKEN_SERVERLIST` | **A** | 只读 |
| P2d已实现 | `get_client_log` | 客户端运行日志快照 | `COMMAND_QUERY_LOG`(155)→(子链接)`TOKEN_REPORT_LOG`(156) | **A** | 只读 |
| P3 | `exec_command` | 执行命令、返回 stdout | `COMMAND_SHELL``TOKEN_SHELL_START`/`TOKEN_SHELL_DATA` | **B** | **写** |
| P3 | `send_message` | 给被控端弹消息 | `COMMAND_TALK`(220) | **A0** | 写(低危) |
| P3 | `kill_process` | 结束进程 | `COMMAND_KILLPROCESS` | **A0** | **写** |
@@ -203,7 +205,7 @@ Web 是长连接子链接可长驻MCP 是**一次性 request/response**。
### 5.2 Phase 2只读观测低风险无写操作
全部只读不改变被控端任何状态token 模型不变(仍单一静态 token。P2a 纯内存、零改动P2b/P2c 引入模式 A/A 无头接管,但只读、可回滚。
全部只读不改变被控端任何状态token 模型不变(仍单一静态 token。P2a 纯内存、零改动P2b/P2c/P2d 引入模式 A/A 无头接管,但只读、可回滚。
### 5.3 Phase 3安全写操作需单独评审
@@ -307,7 +309,52 @@ Web 是长连接子链接可长驻MCP 是**一次性 request/response**。
**回滚**revert `McpServer.h` + `McpServer.cpp` + `MessageHandle` 三处拦截。
### 6.4 P3a`exec_command`(模式 B写操作评审后实施
### 6.4 P2d`list_services` + `get_client_log`(模式 A只读感知扩展✅ 已实现并验证
> 本期新增两个**只读**感知工具,均走**一次性子链接**(模式 A复用 §4.4 挂起注册表,无新增机制、无写操作。二者与 P2b 的进程/窗口、P2c 的列盘同构:主连接下发命令 → 客户端建子链接回传 → 取走即 `CancelIO`。
**`list_services`(服务管理链路,一次性子链接,仅 Windows**
- 复用经典服务链路 `COMMAND_SERVICES`(218)→`TOKEN_SERVERLIST`。客户端 `KernelManager::OnReceive` 收到 `COMMAND_SERVICES` 建子链接 `LoopServicesManager``CServicesManager` **构造函数即** `SendServicesList()`(无需再下 `COMMAND_SERVICELIST` 刷新命令),在子链接上一次性回传 `TOKEN_SERVERLIST`
- **记录格式**`ServicesManager.cpp::GetServicesList``[TOKEN_SERVERLIST:1]` 后接 N 条 `[displayName\0][serviceName\0][binaryPath\0][runWay\0][autoRun\0]`,尾部 `LocalAlloc(LPTR)` 零填充终止。**字段顺序以代码为准**`displayName` 在前、`serviceName` 在后;源码注释「真实名称/显示名称」标注相反,勿据注释判断)。`runWay`/`autoRun` 为英文枚举串(`Stopped/Running/Paused/…``Boot-Start/Auto-Start/Demand-Start/Disabled/…`)。
- **编码**:字段全部来自 Windows A 接口(`EnumServicesStatus`/`QueryServiceConfig` 返回 ANSI按 clientType 判定 `cp`(与进程/文件一致);仅 Windows 客户端实现(`EnumServicesStatus` 为 Windows-onlyLNX/MAC 主机提前返回 `-32005`
- 返回结构:`structuredContent.services[] = {display_name, service_name, binary_path, status, start_type}`
**`get_client_log`(客户端运行日志,一次性子链接取首条全量)**
- 复用经典日志链路 `COMMAND_QUERY_LOG`(155)→`TOKEN_REPORT_LOG`(156)。客户端 `KernelManager::OnReceive` 收到 `COMMAND_QUERY_LOG` 建子链接 `LoopClientLogManager``CClientLogManager` 构造函数 `SendLogDump()``m_sentIdx=0` 返回**全量**),并启动 push 线程**每 3s 推增量**。
- **关键语义**`TOKEN_REPORT_LOG` 会**多次**到达(全量 + 每 3s 增量。MCP 只取**首条全量**`MessageHandle``IsPending` 命中时 `TakeMainResponse` 后立即 `CancelIO` 关子链接,后续增量不再到达(与 MFC 日志对话框「持续接收增量、不关链」的路径区分)。
- **日志内容**YAMA 客户端**自身运行时日志**`Logger` 内存 ring buffer最近最多 1000 条,含 `[pid][时间戳] [文件:行号] 消息`**非** Windows 系统日志。
- **编码**:日志文本为客户端 ANSIWindows 走 `vsnprintf` A 版),按 clientType 判定 `cp``data[0]=token` 后为无 `'\0'` 终止的原始字节,需先 `std::string` 包装再 `ToUtf8``ToUtf8` 内部以 `-1` 读 null 终止串)。
- 返回结构:`structuredContent.log = "<原始日志文本>"`
**改动**
- `McpServer.h/.cpp`
- 解析器 `ParseServiceList`5 字段记录,有界读 + 「displayName 与 serviceName 皆空 = 尾部零填充」终止)。
- schema `BuildServiceListOutputSchema` / `BuildClientLogOutputSchema`inputSchema 复用 `BuildGetHostDetailInputSchema``{ id }`)。
- 工具 `BuildListServices`(发 `COMMAND_SERVICES`LNX/MAC 提前 `-32005`)、`BuildGetClientLog`(发 `COMMAND_QUERY_LOG`)。
- `2015RemoteDlg.cpp``MessageHandle`(两处拦截):
- `TOKEN_SERVERLIST`(子 context`if (IsPending) { TakeMainResponse; CancelIO(); break; }`(一次性,取走即关)。
- `TOKEN_REPORT_LOG`(子 context`if (IsPending) { TakeMainResponse; CancelIO(); break; }`(取首条全量即关,停止 3s 增量)。
**影响面**`MessageHandle` 仅新增两个 `if` 分支;非 MCP 触发时 `IsPending` 为假MFC 服务管理弹框与日志对话框路径不变(回归点)。
**验收 / 实机验证点**
- MFC 双击「服务管理」弹框仍正常回归点MFC 打开「客户端运行日志」对话框仍正常、增量持续追加(回归点)。
- MCP `list_services` 对 Windows 主机返回 `services[]`,中文显示名/路径无乱码,`status`/`start_type` 为英文枚举;对 LNX/MAC 主机返回 `-32005`
- MCP `get_client_log` 返回 `log` 文本快照UTF-8中文日志无乱码空日志返回空 `log`
- 错误路径:缺 `id`/非数字 `id``-32602`;未知/离线主机 → `-32002`;同 host 并发 → `-32003`;发送失败 → `-32004`;超时 → `-32001`
**验证记录**2026-08-19真实在线主机实测
- `list_services`Windows 11 EXE 主机「Essential / VCU」返回 255 个服务,字段序(显示名→服务名→路径→状态→启动类型)正确,`status`/`start_type` 英文枚举、路径完整。
- `get_client_log`(同上主机):返回 510 行 / 40309 字节全量快照,`[pid][时间戳][文件:行号]` 格式正确中文日志「启动运行」「任务已存在」「打开注册表失败错误码2」等GBK→UTF-8 转码全部正确。
- 错误路径实测LNX 主机 `list_services``-32005`;缺/非法 `id``-32602`;离线 → `-32002`
- **客户端版本门槛**`get_client_log` 对 Linux 客户端 20s 超时返回 `-32001`——当前 Linux 客户端运行版本未实现 `COMMAND_QUERY_LOG``CClientLogManager` 日志回传链路(服务端下发命令无响应),属版本/实现门槛,非 MCP 缺陷(与 §6.2 `get_activity_history` 对老客户端超时同类)。`list_services` 因服务为 Windows-only`EnumServicesStatus`),已对 LNX/MAC 提前 `-32005`,不受此门槛影响。
**回滚**revert `McpServer.h` + `McpServer.cpp` + `MessageHandle` 两处拦截。
### 6.5 P3a`exec_command`(模式 B写操作评审后实施
**改动**:复刻 Web 终端链路§4.3),把 `m_TermPending` 换成 MCP 自己的挂起标记:
-`COMMAND_SHELL``TOKEN_SHELL_START`/`TOKEN_SHELL_DATA` 接管 → 发送命令文本 → 收集 stdout → 关子链接 → 返回。
@@ -433,6 +480,7 @@ Web 是长连接子链接可长驻MCP 是**一次性 request/response**。
1. **P2a**`search_hosts` + `get_host_detail`)✅ ——纯内存、零风险,已实现,验证「多工具 dispatch」模式。
2. **P2b**`list_processes` + `list_windows` + `get_activity_history`)✅ ——首次引入**模式 A 一次性子链接**(进程/窗口)与**模式 A 主连接 RPC**(历史活动)无头接管,把 §4.2/§4.2 的机制跑通并沉淀成 §7 配方。
3. **P2c**`get_screenshot` 走屏幕预览链路 + `list_files`)✅ ——补上「看」的能力,仍是只读(`get_screenshot` 模式 A、`list_files` 模式 A
4. **P3a**`exec_command`)——首次引入**模式 B 子链接流式**,在 P2 的挂起机制成熟后,走完安全评审再落地写操作
4. **P2d**`list_services` + `get_client_log`)✅ ——补上「看服务 / 看日志」的感知能力,仍是只读(均为模式 A复用 P2b/P2c 的一次性子链接机制)
5. **P3a**`exec_command`)——首次引入**模式 B 子链接流式**,在 P2 的挂起机制成熟后,走完安全评审再落地写操作。
每完成一期即提交、验收,再进入下一期,保持「有序、平稳、循序渐进」。