Feature: Add MCP (Model Context Protocol) server integration
Add an optional MCP server (JSON-RPC 2.0 over Streamable HTTP) exposing an online-host listing tool, protected by a Bearer token. Disabled by default; configured via a new "Extensions > MCP Settings" dialog. - McpServer: httplib + JSON-RPC 2.0 dispatch (initialize/ping/tools/list/tools/call) - McpSettingsDlg: runtime-created dialog for enable/port/bind/token - HostJson: extract single-host JSON serialization shared with WebService - FRP: expose MCP port (union with listening/Web ports) when bound to 0.0.0.0 - i18n: en_US / zh_TW translations Co-Authored-By: deepseek-v4-pro
This commit is contained in:
443
docs/Mcp_Design.md
Normal file
443
docs/Mcp_Design.md
Normal file
@@ -0,0 +1,443 @@
|
||||
# YAMA 服务端 MCP 支持方案
|
||||
|
||||
> 版本:1.0
|
||||
> 状态:Final(定稿)
|
||||
> 适用范围:`server/2015Remote/`(C++/MFC 服务端,生产主流)
|
||||
|
||||
---
|
||||
|
||||
## 1. 背景与目标
|
||||
|
||||
### 1.1 背景
|
||||
|
||||
MCP(Model Context Protocol)是 Anthropic 提出的开放协议,让 AI 助手(Claude Code、Claude Desktop 等)通过标准化的工具调用接口访问外部系统的能力。
|
||||
|
||||
YAMA 服务端维护了「在线主机列表」这一核心数据,如果能通过 MCP 暴露出去,用户就能在 Claude Code 里直接问「现在有多少台机器在线?」「列出所有在线的 Windows 主机」等,由 AI 调用工具获取实时数据。
|
||||
|
||||
### 1.2 本期范围(MVP)
|
||||
|
||||
**只跑通 MCP,只实现一个工具**,把可扩展架构搭起来:
|
||||
|
||||
- 传输方式:Streamable HTTP(`POST /mcp`,JSON-RPC 2.0 over HTTP)
|
||||
- 工具数量:1 个 —— `list_online_hosts`(获取在线主机列表)
|
||||
- 暴露范围:启用开关(默认关闭,经「扩展 → MCP设置」对话框开启)+ 绑定地址可配置(默认 `127.0.0.1` 仅本机)+ token 认证(env / THIS_CFG / 随机)
|
||||
- 后续:逐步添加工具(进程列表、命令执行、屏幕截图等),架构不动
|
||||
|
||||
### 1.3 非目标(明确不做)
|
||||
|
||||
- 不做 SSE 服务器推送(本期工具调用是 request/response 即可)
|
||||
- 不做多用户/角色体系(复用现有 WebService 认证或静态 token)
|
||||
- 不做写操作工具(操控主机的工具属于后续阶段,需单独评审安全边界)
|
||||
- 不碰现有 Web 控制台的 WebSocket 服务
|
||||
|
||||
---
|
||||
|
||||
## 2. 现状分析
|
||||
|
||||
C++ 服务端已有大量可直接复用的资产,MCP 的落地成本因此大幅降低。
|
||||
|
||||
| 现成资产 | 位置 | 在 MCP 中的角色 |
|
||||
|---|---|---|
|
||||
| HTTP 服务器 | `httplib.h`(header-only,`file_server.h:34` 已用) | MCP 的 HTTP 传输层 |
|
||||
| JSON 库 | `jsoncpp/json.h`(`WebService.cpp` 已用) | JSON-RPC 编解码 |
|
||||
| Token 认证 | `WebServiceAuth.h`(`GenerateToken` / `ValidateToken` / `ComputeSHA256`) | 可选升级路径;MVP 用简单静态 token 比对(见 §3.5) |
|
||||
| 在线主机列表 → JSON | `CWebService::BuildDeviceListJson()`(`WebService.cpp:1506`) | **工具 `list_online_hosts` 的核心逻辑** |
|
||||
| 在线主机数据源 | `CMy2015RemoteDlg::m_HostList`(`2015RemoteDlg.h:345`,`m_cs` 保护)+ `context::IsLogin()` + `context::GetClientData()` | 取数入口 |
|
||||
| 配置系统 | `THIS_CFG.GetInt/GetStr("settings", ...)` | MCP 端口/token 配置 |
|
||||
|
||||
### 2.1 关键结论
|
||||
|
||||
- **HTTP、JSON、认证、主机列表 JSON 全部现成**,MCP 侧不需要重新造轮子。
|
||||
- **唯一需要手写的是 MCP 的 JSON-RPC 2.0 协议层**,因为 C++ 没有官方 MCP SDK(官方仅有 TS/Python/Java/Kotlin/C#/Go)。
|
||||
|
||||
### 2.2 `BuildDeviceListJson` 现状
|
||||
|
||||
`WebService.cpp:1506` 的 `CWebService::BuildDeviceListJson(const std::string& username)` 已实现:
|
||||
|
||||
- 遍历 `m_pParentDlg->m_HostList`,过滤 `ctx->IsLogin()`
|
||||
- 输出字段:`id` / `name` / `remark` / `ip` / `os` / `location` / `rtt` / `version` / `activeWindow` / `online` / `group` / `screen` / `clientType`
|
||||
- 已处理 GBK/UTF-8 编码分叉(`activeWindow` 字段按客户端能力位选择编码页,见 `WebService.cpp:1581` 附近的 `GetClientEncoding`)
|
||||
|
||||
**该方法是 `private`**(`WebService.h:160`,位于 private 段)。MCP 侧需通过公开入口复用(见 §3.4)。
|
||||
|
||||
---
|
||||
|
||||
## 3. 方案设计
|
||||
|
||||
### 3.1 总体架构
|
||||
|
||||
```
|
||||
2015Remote.exe(MFC 服务端进程)
|
||||
├─ IOCP TCP 服务(被控端连接,现有)
|
||||
├─ WebService(ws::Server,Web 控制台,现有,8080)
|
||||
└─ McpServer(httplib,新增,默认禁用,经「MCP设置」开启后监听 127.0.0.1:6544)
|
||||
POST /mcp → JSON-RPC 分发
|
||||
├─ initialize
|
||||
├─ tools/list
|
||||
└─ tools/call
|
||||
└─ list_online_hosts → 复用 BuildHostJson(公共函数,§3.4)
|
||||
```
|
||||
|
||||
**核心决策:独立起一个 httplib server,不挂到现有 `ws::Server`。**
|
||||
|
||||
理由:
|
||||
- 现有 `ws::Server` 的 `HttpHandler` 只有 `path` 参数、无 POST body(`SimpleWebSocket.h:345`),强挂 MCP 需改动其底层,侵入大、有回归风险。
|
||||
- 独立 server 零耦合、零风险,且天然满足「仅 127.0.0.1」的绑定需求。
|
||||
|
||||
### 3.2 新增文件清单
|
||||
|
||||
| 文件 | 职责 | 估算量 |
|
||||
|---|---|---|
|
||||
| `McpServer.h` / `McpServer.cpp` | httplib 封装、`POST /mcp` 路由、token 校验、生命周期 | ~120 行 |
|
||||
| `McpProtocol.h` / `McpProtocol.cpp` | JSON-RPC 2.0 协议层:`initialize` / `tools/list` / `tools/call` 分发 | ~250 行 |
|
||||
| `McpTools.h` / `McpTools.cpp` | 工具注册表 + `list_online_hosts` 实现 | ~80 行 |
|
||||
| `McpSettingsDlg.h` / `McpSettingsDlg.cpp` | 「MCP设置」配置对话框(启用/端口/绑定/token + 重启提醒) | ~150 行 |
|
||||
| 修改 `2015RemoteDlg.cpp` / `.h` | 启动/停止 McpServer + 菜单项接线(`ID_MCP_SETTINGS` → `OnMcpSettings`) | ~30 行 |
|
||||
| 修改 `2015Remote.rc` / `resource.h` / `UIBranding.h` / `FeatureFlags.h` | `ID_MCP_SETTINGS` 菜单项 + `IDD_DIALOG_MCP_SETTINGS` 对话框资源 + `HIDE_MENU_MCP_SETTINGS` + `MF_MCP_SETTINGS` 许可位 | 少量 |
|
||||
|
||||
> 头文件/实现可合并精简,实际以「一个 `McpServer` 类 + 一个工具注册表 + 一个协议分发函数」为最小形态,文件拆分仅为可读性。
|
||||
|
||||
### 3.3 协议层设计(JSON-RPC 2.0)
|
||||
|
||||
MCP 是 JSON-RPC 2.0。MVP 只需实现以下方法:
|
||||
|
||||
#### 3.3.1 `initialize`(握手)
|
||||
|
||||
请求:
|
||||
```json
|
||||
{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"claude","version":"..."}}}
|
||||
```
|
||||
|
||||
响应:
|
||||
```json
|
||||
{"jsonrpc":"2.0","id":1,"result":{
|
||||
"protocolVersion":"2025-06-18",
|
||||
"capabilities":{"tools":{}},
|
||||
"serverInfo":{"name":"yama","version":"<VERSION_STR>"}
|
||||
}}
|
||||
```
|
||||
|
||||
#### 3.3.2 `tools/list`
|
||||
|
||||
请求:`{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}`
|
||||
|
||||
响应:
|
||||
```json
|
||||
{"jsonrpc":"2.0","id":2,"result":{"tools":[{
|
||||
"name":"list_online_hosts",
|
||||
"description":"获取当前所有在线主机的列表,包含计算机名、IP、操作系统、版本、备注、分组、活动窗口、延迟等实时信息。",
|
||||
"inputSchema":{"type":"object","properties":{},"required":[]},
|
||||
"outputSchema":{
|
||||
"type":"object",
|
||||
"properties":{
|
||||
"hosts":{
|
||||
"type":"array",
|
||||
"items":{
|
||||
"type":"object",
|
||||
"properties":{
|
||||
"id":{"type":"string"},
|
||||
"name":{"type":"string"},
|
||||
"remark":{"type":"string"},
|
||||
"ip":{"type":"string"},
|
||||
"os":{"type":"string"},
|
||||
"location":{"type":"string"},
|
||||
"rtt":{"type":"string"},
|
||||
"version":{"type":"string"},
|
||||
"activeWindow":{"type":"string"},
|
||||
"online":{"type":"boolean"},
|
||||
"group":{"type":"string"},
|
||||
"screen":{"type":"string"},
|
||||
"clientType":{"type":"string"}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"required":["hosts"]
|
||||
}
|
||||
}]}}
|
||||
```
|
||||
|
||||
#### 3.3.3 `tools/call`
|
||||
|
||||
请求:
|
||||
```json
|
||||
{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"list_online_hosts","arguments":{}}}
|
||||
```
|
||||
|
||||
响应(结构化输出:`structuredContent` 承载主机数组,`content` 附带可读摘要):
|
||||
```json
|
||||
{"jsonrpc":"2.0","id":3,"result":{
|
||||
"structuredContent":{
|
||||
"hosts":[
|
||||
{"id":"123","name":"DESKTOP-ABC","remark":"我的Windows","ip":"192.168.0.92",
|
||||
"os":"Windows 10","location":"上海市","rtt":"12","version":"...",
|
||||
"activeWindow":"WeChat","online":true,"group":"default","screen":"1:1920x1080","clientType":"EXE"}
|
||||
]
|
||||
},
|
||||
"content":[{"type":"text","text":"共 1 台主机在线。"}],
|
||||
"isError":false
|
||||
}}
|
||||
```
|
||||
|
||||
#### 3.3.4 可选实现
|
||||
|
||||
- `ping`:返回空 result(便于健康检查)
|
||||
- `notifications/initialized`:客户端通知,可忽略
|
||||
|
||||
#### 3.3.5 错误响应(JSON-RPC 2.0 标准)
|
||||
|
||||
协议层必须处理以下错误,返回标准 `error` 结构(`id` 回填请求 id):
|
||||
|
||||
| 场景 | code | message |
|
||||
|---|---|---|
|
||||
| 请求体不是合法 JSON | `-32700` | Parse error |
|
||||
| 请求结构不合法(缺 jsonrpc/method 等) | `-32600` | Invalid Request |
|
||||
| 方法未实现 | `-32601` | Method not found |
|
||||
| 参数非法 | `-32602` | Invalid params |
|
||||
| 内部错误 | `-32603` | Internal error |
|
||||
|
||||
示例(方法未实现):
|
||||
```json
|
||||
{"jsonrpc":"2.0","id":1,"error":{"code":-32601,"message":"Method not found"}}
|
||||
```
|
||||
|
||||
#### 3.3.6 传输层约定(Streamable HTTP 最小子集)
|
||||
|
||||
- 客户端 `POST /mcp`,请求头 `Content-Type: application/json`、`Accept: application/json, text/event-stream`。
|
||||
- 服务端返回 `Content-Type: application/json`(MVP 工具调用为 request/response,无需 SSE 流式)。
|
||||
- `initialize` 响应可回填 `Mcp-Session-Id` 头用于会话管理;MVP 单会话可省略,实现时对照最新 MCP 规范确认。
|
||||
- 仅接受 `POST /mcp`,其余路径/方法返回 404/405。
|
||||
|
||||
### 3.4 工具设计:`list_online_hosts`
|
||||
|
||||
#### 取数方案(已定:抽公共函数)
|
||||
|
||||
`BuildDeviceListJson` 目前是 `CWebService` 的 private 方法,且依赖 `m_pParentDlg`。为满足「MCP 与 WebService 是平级数据消费者、不互相依赖」的原则,采用**方案 C:抽公共函数**。
|
||||
|
||||
```cpp
|
||||
// 公共函数:提取单台在线主机的完整数据(含 GBK/UTF-8 编码处理),返回 UTF-8 的 Json::Value
|
||||
Json::Value BuildHostJson(context* ctx, _ClientList* clientMap);
|
||||
```
|
||||
|
||||
- `CWebService::BuildDeviceListJson` 内部改调该公共函数,Web 侧签名与输出不变(零回归)。
|
||||
- `McpServer` 侧持 `CMy2015RemoteDlg*`(`SetParentDlg`),在 `m_cs` 锁内遍历 `m_HostList`、`IsLogin()` 过滤,对每台主机调 `BuildHostJson`,组装为结构化输出。
|
||||
- **职责边界**:用户名/分组过滤(`username` → `allowed_groups`)、`m_cs` 加锁、以及外层结构(Web 的 `{"cmd","devices"}` vs MCP 的 `{"hosts"}`)属于**调用方**,不进公共函数;`BuildHostJson` 只负责「单台主机 → `Json::Value`」的字段序列化与编码转换。其中 `remark` 依赖 `m_ClientMap->GetClientMapData(id, MAP_NOTE)`,故 `clientMap` 作为参数传入。
|
||||
|
||||
选型说明:方案 A(MCP 依赖 `CWebService` 单例)依赖方向错误,且 `WebSvrPort=0` 时 `m_pParentDlg` 为空导致取不到数;方案 B(MCP 自持父对话框复制逻辑)违反 DRY、重复易错的编码处理。故取 C。
|
||||
|
||||
#### 返回内容(全量,含实时信息)
|
||||
|
||||
**保留 `BuildDeviceListJson` 的全部字段,不做精简**,以支持「AI 协助监督客户端运行状态」的场景:
|
||||
|
||||
- 基础标识:`id` / `name` / `remark` / `ip` / `os` / `location` / `group` / `version` / `clientType` / `screen`
|
||||
- 实时信息:`rtt`(延迟)、`activeWindow`(当前活动窗口,即被控端正在做什么)
|
||||
|
||||
输出结构 `{"hosts": [...]}`,每台主机一个对象,字段与 §3.3.2 的 `outputSchema` 一致。
|
||||
|
||||
### 3.5 安全设计
|
||||
|
||||
- **绑定地址**:默认 `127.0.0.1`(仅本机回环,其他机器无法访问);可经 `McpBind` 配置为 `0.0.0.0`(所有网卡)或内网 IP(如 `192.168.0.92`)以支持远程调用。
|
||||
- 非回环绑定时 token 经明文 HTTP 传输,需强 token,并建议配合防火墙限制源 IP 或启用 HTTPS(项目已有 WebHTTPS 实践)。
|
||||
- **静态 token 校验**:每个 `POST /mcp` 请求校验 `Authorization: Bearer <token>` header;不匹配返回 HTTP 401。
|
||||
- **token 来源**(优先顺序):环境变量 `YAMA_MCP_TOKEN` → `THIS_CFG` 的 `McpToken` → 两者皆空则**随机生成**。
|
||||
- 前两者任一非空即作为固定 token;仅当两者皆空时才随机生成(仅本次进程运行期间有效,进程重启后重新生成不同的 token)。
|
||||
- 随机生成时**必须将 token 输出到日志**(`Mprintf`),否则用户无从获知、无法在 Claude Code 侧配置。
|
||||
- 环境变量名 `YAMA_MCP_TOKEN` 建议同 `BRAND_WEB_ENV_VAR` 一样在 `UIBranding.h` 定义为宏(如 `BRAND_MCP_ENV_VAR`),避免散落硬编码字符串。
|
||||
- **只读边界**:MVP 仅一个只读工具,不暴露任何写/操控能力。
|
||||
- **HTTP 方法限制**:仅接受 `POST /mcp`,其余返回 404/405。
|
||||
|
||||
### 3.6 配置设计
|
||||
|
||||
沿用现有 `THIS_CFG`(`settings` 节):
|
||||
|
||||
| 配置键 | 默认值 | 说明 |
|
||||
|---|---|---|
|
||||
| `McpEnabled` | `0` | MCP 总开关:`0` = 禁用(默认),`1` = 启用。经「扩展 → MCP设置」对话框设置 |
|
||||
| `McpPort` | `6544` | MCP 监听端口;仅当 `McpEnabled=1` 时生效;未配置用默认 `6544` |
|
||||
| `McpBind` | `127.0.0.1` | 监听地址;默认仅本机;可设 `0.0.0.0`(所有网卡)或内网 IP(如 `192.168.0.92`)以支持远程 |
|
||||
| `McpToken` | 空(UI 默认随机值) | 静态 token;对话框内「token 必填、默认随机值」——首次打开若为空则预填随机并随保存持久化。运行时优先级:env `YAMA_MCP_TOKEN` → `McpToken` → 皆空则随机生成(见 §3.5) |
|
||||
|
||||
> 与旧版「`McpPort=0` 即禁用」不同:启用与否改由独立的 `McpEnabled` 开关控制,`McpPort` 回归纯端口语义,二者解耦。
|
||||
|
||||
### 3.7 生命周期接入
|
||||
|
||||
在 `2015RemoteDlg.cpp` Web 服务启动块(约 `2048-2081`)之后,新增 MCP 启动逻辑。`McpServer` 与 `CWebService` 同为单例:提供 `static McpServer& Instance()` 与全局 `inline McpServer& McpServer()` 访问器(仿 `WebService()`,见 `WebService.h` 末尾的全局访问器写法)。
|
||||
|
||||
```cpp
|
||||
auto mcpEnabled = THIS_CFG.GetInt("settings", "McpEnabled", 0); // 默认禁用
|
||||
auto mcpPort = THIS_CFG.GetInt("settings", "McpPort", 6544);
|
||||
auto mcpBind = THIS_CFG.GetStr("settings", "McpBind", "127.0.0.1");
|
||||
if (mcpEnabled) {
|
||||
// token 优先顺序:env YAMA_MCP_TOKEN → THIS_CFG McpToken → 皆空则随机(仅本次进程有效)
|
||||
const char* envTok = getenv("YAMA_MCP_TOKEN");
|
||||
std::string mcpToken;
|
||||
if (envTok && *envTok) {
|
||||
mcpToken = envTok;
|
||||
} else {
|
||||
mcpToken = THIS_CFG.GetStr("settings", "McpToken", "");
|
||||
}
|
||||
if (mcpToken.empty()) {
|
||||
mcpToken = GenerateRandomToken(); // 32 hex 随机串
|
||||
Mprintf("[McpServer] YAMA_MCP_TOKEN / McpToken 均未设置,本次进程随机 token:%s\n", mcpToken.c_str());
|
||||
}
|
||||
McpServer().SetParentDlg(this); // 方案 C:MCP 需遍历 m_HostList,须持父对话框指针
|
||||
McpServer().SetToken(mcpToken);
|
||||
if (!McpServer().Start(mcpBind, mcpPort)) {
|
||||
Mprintf("McpServer start failed on %s:%d\n", mcpBind.c_str(), mcpPort);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
并在退出路径(`OnDestroy` / `ExitInstance` 对应位置)调用 `McpServer().Stop()`。
|
||||
|
||||
### 3.8 「MCP设置」配置对话框
|
||||
|
||||
MCP 默认不启用,通过主对话框「扩展 → MCP设置」菜单项打开配置对话框来开启。菜单项位置:`2015Remote.rc` 扩展菜单(`POPUP "扩展(&X)"`)内、「地理信息(&L)」子菜单(纯真数据库 / IP2Region)的 `END` 之后、「插件设置」之前。
|
||||
|
||||
#### 3.8.1 菜单与资源接线
|
||||
|
||||
1. **`resource.h`**:新增命令 ID(取当前空闲段,经查 `33077` 空闲):
|
||||
```cpp
|
||||
#define ID_MCP_SETTINGS 33077
|
||||
```
|
||||
2. **`2015Remote.rc`**:在「地理信息(&L)」子菜单 `END` 之后、`插件设置` 之前插入:
|
||||
```
|
||||
MENUITEM "MCP设置(&M)...", ID_MCP_SETTINGS
|
||||
```
|
||||
并新增对话框资源 `IDD_DIALOG_MCP_SETTINGS`(「启用 MCP」复选框 + 端口/绑定地址/token 三个编辑框 + 确定/取消按钮)。
|
||||
3. **`UIBranding.h`**:扩展菜单隐藏开关新增一行(默认 `0` 显示):
|
||||
```cpp
|
||||
#define HIDE_MENU_MCP_SETTINGS 0 // MCP设置
|
||||
```
|
||||
4. **`FeatureFlags.h`**:新增运行时许可位,占用 MenuFlags 保留段 `[43-63]` 的首位(紧跟 `MF_REQUEST_AUTH` 之后):
|
||||
```cpp
|
||||
#define MF_MCP_SETTINGS (1ULL << 43) // HIDE_MENU_MCP_SETTINGS
|
||||
```
|
||||
5. **`2015RemoteDlg.cpp`**:`#include "McpSettingsDlg.h"`,然后消息映射 + 处理器 + 剪枝:
|
||||
```cpp
|
||||
ON_COMMAND(ID_MCP_SETTINGS, &CMy2015RemoteDlg::OnMcpSettings)
|
||||
// ...
|
||||
void CMy2015RemoteDlg::OnMcpSettings()
|
||||
{
|
||||
CMcpSettingsDlg dlg(this);
|
||||
dlg.DoModal();
|
||||
}
|
||||
```
|
||||
并在扩展菜单剪枝块(`pExtMenu`,约 `2015RemoteDlg.cpp:1225` 之后)追加:
|
||||
```cpp
|
||||
if (SHOULD_HIDE_MENU(HIDE_MENU_MCP_SETTINGS, MF_MCP_SETTINGS))
|
||||
pExtMenu->DeleteMenu(ID_MCP_SETTINGS, MF_BYCOMMAND);
|
||||
```
|
||||
|
||||
> **实现注意**:`2015Remote.rc` 为 UTF-16 编码,菜单与对话框资源须由 VS 资源编辑器维护(手工改文本易破坏编码与资源 ID 引用)。`IDD_DIALOG_MCP_SETTINGS` 需在 `resource.h` 分配独立空闲的 `IDD_` 数值(与 `ID_MCP_SETTINGS` 分开);对话框内控件 ID(如 `IDC_CHK_ENABLE_MCP` / `IDC_EDIT_MCP_PORT` / `IDC_EDIT_MCP_BIND` / `IDC_EDIT_MCP_TOKEN`)同理。
|
||||
|
||||
#### 3.8.2 对话框字段与默认值
|
||||
|
||||
| 控件 | 绑定配置键 | 默认值 | 说明 |
|
||||
|---|---|---|---|
|
||||
| 「启用 MCP」复选框 | `McpEnabled` | 未勾选(`0`) | 勾选后持久化 `McpEnabled=1` |
|
||||
| 端口 | `McpPort` | `6544` | 数字编辑框,仅当启用时有效 |
|
||||
| 绑定地址 | `McpBind` | `127.0.0.1` | 默认仅本机;可改 `0.0.0.0` / 内网 IP |
|
||||
| Token | `McpToken` | 随机值 | 必填;为空时预填随机 token |
|
||||
|
||||
#### 3.8.3 初始化与保存
|
||||
|
||||
打开时读 `THIS_CFG` 回填;token 为空则当场生成随机值预填(32 hex)。保存时落盘并弹重启提醒:
|
||||
|
||||
```cpp
|
||||
BOOL CMcpSettingsDlg::OnInitDialog()
|
||||
{
|
||||
CDialogLangEx::OnInitDialog();
|
||||
int enabled = THIS_CFG.GetInt("settings", "McpEnabled", 0);
|
||||
int port = THIS_CFG.GetInt("settings", "McpPort", 6544);
|
||||
std::string bind = THIS_CFG.GetStr("settings", "McpBind", "127.0.0.1");
|
||||
std::string tok = THIS_CFG.GetStr("settings", "McpToken", "");
|
||||
if (tok.empty()) tok = GenerateRandomToken(); // 预填随机,随保存持久化
|
||||
// 回填到控件:m_bEnabled / m_nPort / m_strBind / m_strToken
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
void CMcpSettingsDlg::OnBnClickedBtnSave()
|
||||
{
|
||||
// 从控件收集:enabled / port / bind / token
|
||||
if (enabled && token.empty()) {
|
||||
MessageBoxL(_TR("Token 不能为空"), _TR("提示"), MB_ICONWARNING);
|
||||
return;
|
||||
}
|
||||
THIS_CFG.SetInt("settings", "McpEnabled", enabled ? 1 : 0);
|
||||
THIS_CFG.SetInt("settings", "McpPort", port);
|
||||
THIS_CFG.SetStr("settings", "McpBind", bind);
|
||||
THIS_CFG.SetStr("settings", "McpToken", token);
|
||||
MessageBoxL(_TR("MCP 设置已保存。\n启用/端口/绑定地址/Token 的改动需重启程序生效。"),
|
||||
_TR("提示"), MB_ICONINFORMATION);
|
||||
CDialogLangEx::OnOK();
|
||||
}
|
||||
```
|
||||
|
||||
#### 3.8.4 与运行时 token 逻辑的衔接
|
||||
|
||||
对话框的「默认随机 token」是把随机值**写入 `McpToken` 并持久化**,于是下次启动走 `env → McpToken` 的固定 token 分支;运行时「两者皆空则随机」分支仅在用户从未通过对话框设置过(或手动清空 token)时兜底,两者不冲突。`GenerateRandomToken()` 为 MCP 侧新增的小工具函数(32 hex 随机串,可用 `rand_s` / `BCryptGenRandom` 实现;如需可复用 `WebServiceAuth.h` 的 `ComputeSHA256` 派生)。
|
||||
|
||||
---
|
||||
|
||||
## 4. 实施步骤
|
||||
|
||||
| 步骤 | 内容 | 验收 |
|
||||
|---|---|---|
|
||||
| 1 | 新建 `McpProtocol`:JSON-RPC 分发 + `initialize`/`tools/list`/`tools/call`(含 jsoncpp 解析/序列化) | 单元自测:手写请求串,验证响应 |
|
||||
| 2 | 新建 `McpServer`:httplib 起 `127.0.0.1:6544`,`POST /mcp` 路由 + token 校验(含随机生成) | curl 请求返回 401/正常 |
|
||||
| 3 | 抽公共函数 `BuildHostJson`,`CWebService::BuildDeviceListJson` 改调之 | Web 输出不变,零回归 |
|
||||
| 4 | 注册 `list_online_hosts` 工具:handler 持父对话框、`m_cs` 锁内遍历、调 `BuildHostJson` 组装 | `tools/call` 返回主机列表 |
|
||||
| 5 | 新增 `McpSettingsDlg` + 菜单项接线(`ID_MCP_SETTINGS` / `OnMcpSettings` / `HIDE_MENU_MCP_SETTINGS` / `IDD_DIALOG_MCP_SETTINGS` 资源) | 对话框可启用/禁用、配置端口/绑定/token,保存落盘并提示重启 |
|
||||
| 6 | 接入 `2015RemoteDlg` 启动/停止(按 `McpEnabled` 门控)+ 配置 | 服务端启动后端口监听正常 |
|
||||
| 7 | Claude Code 接入验证(见 §5) | 见验收标准 |
|
||||
|
||||
---
|
||||
|
||||
## 5. 验收标准
|
||||
|
||||
1. 在「MCP设置」对话框启用并重启服务端后,`127.0.0.1:6544` 监听正常,外部地址无法访问;未启用时该端口不监听。
|
||||
2. 无 token 的请求返回 HTTP 401;错误 token 返回 401;正确 token 通过。
|
||||
3. `curl` 模拟 `initialize` → `tools/list` → `tools/call` 全流程,返回符合 JSON-RPC 2.0 与 MCP 规范。
|
||||
4. Claude Code 侧:
|
||||
```bash
|
||||
claude mcp add yama --transport http http://127.0.0.1:6544/mcp -H "Authorization: Bearer <token>"
|
||||
```
|
||||
`claude mcp list` 能看到 `yama`,在对话中问「列出在线主机」能正确触发工具并返回列表。
|
||||
5. 现有 Web 控制台、IOCP 主服务功能无回归。
|
||||
|
||||
---
|
||||
|
||||
## 6. 风险与约束
|
||||
|
||||
| 风险 | 说明 | 应对 |
|
||||
|---|---|---|
|
||||
| 无官方 C++ MCP SDK | 需手写 JSON-RPC 协议层 | 仅实现 3 个方法,量小可控;协议稳定后再考虑抽库 |
|
||||
| MCP 规范版本演进 | protocolVersion 字符串需与客户端匹配 | 实现时对照最新规范;`initialize` 返回可协商版本 |
|
||||
| 并发安全 | 遍历 `m_HostList` 需持 `m_cs` | MCP 与 WebService 采用相同的「`m_cs` 锁内遍历」模式 |
|
||||
| 字符编码 | GBK/UTF-8 分叉 | 复用公共函数 `BuildHostJson` 的编码处理,不自造 |
|
||||
| token 存储 | `THIS_CFG` 的 token 会明文落盘;随机 token 会打印到日志 | 本地回环 + 只读,风险可控;敏感场景建议仅用 env 并妥善保管 |
|
||||
|
||||
---
|
||||
|
||||
## 7. 后续扩展规划(本期不做)
|
||||
|
||||
- 工具:`get_host_processes`(进程列表)、`execute_command`(命令执行)、`capture_screen`(截图)等
|
||||
- 写操作工具的安全边界评审(操作对象、权限、审计)
|
||||
- 认证升级:复用 `WebServiceAuth` 的签名 token / 过期机制
|
||||
- 分组过滤:`list_online_hosts` 加 `group` 参数
|
||||
|
||||
---
|
||||
|
||||
## 8. 决策记录
|
||||
|
||||
**决策记录(全部已定):**
|
||||
- ✅ **启用开关**:新增 `McpEnabled`,默认 `0`(禁用)。MCP 默认不启动,须用户在「扩展 → MCP设置」对话框手动开启。
|
||||
- ✅ **端口**:`McpPort` 可配置,未配置默认 `6544`(纯端口语义,不再兼任启用开关)。
|
||||
- ✅ **token**:优先顺序 env `YAMA_MCP_TOKEN` → `THIS_CFG.McpToken` → 皆空则随机生成(仅本次进程有效,打印到日志)。对话框内 token 必填、默认随机值(预填并随保存持久化到 `McpToken`)。
|
||||
- ✅ **绑定地址**:默认 `127.0.0.1`(仅本机);可配置为 `0.0.0.0` 或内网 IP(如 `192.168.0.92`)以支持多人协作跨机调用。
|
||||
- ✅ **配置入口**:主对话框「扩展」菜单,位于「地理信息(&L)」子菜单下方新增「MCP设置」菜单项,打开独立配置对话框。
|
||||
- ✅ **生效方式**:MCP 启用/端口/绑定/token 的改动均需重启服务端生效,对话框保存后弹窗提醒「重启程序生效」。
|
||||
- ✅ **取数方案**:C(抽公共函数 `BuildHostJson`,MCP 与 WebService 平级复用)。
|
||||
- ✅ **返回字段**:全量,含实时信息(`rtt` / `activeWindow`),支持 AI 监督场景。
|
||||
- ✅ **序列化**:`structuredContent` + `outputSchema`,`content` 附带可读摘要。
|
||||
Reference in New Issue
Block a user