Feature: Add list_files and get_screenshot MCP tools

Add the two P2c MCP tools on top of the P2b protocol. list_files lists
drives (COMMAND_LIST_DRIVE -> TOKEN_DRIVE_LIST) or a directory (follow-up
COMMAND_LIST_FILES -> TOKEN_FILE_LIST on the same one-shot sub-link);
get_screenshot reuses the screen-preview RPC with a per-host reqId
correlation so stale or MFC-preview responses fall through to the MFC
path untouched. Both share the P2b single-flight pending registry.

Fix file/process name encoding: the client reads process names, paths and
file names via the ANSI (A) APIs, so they are GBK on Windows regardless of
the CLIENT_CAP_UTF8 capability bit (which governs window titles only).
Decode by clientType (LNX/MAC = UTF-8, Windows = 936) rather than
GetClientEncoding, and convert the list_files path to the client ANSI code
page before sending. Sync Mcp_Phase2_Design.md with the P2c design and the
encoding correction.

Co-Authored-By: deepseek-v4-pro
This commit is contained in:
yuanyuanxiang
2026-08-19 13:27:32 +02:00
parent bcde74368d
commit 61089e5fae
4 changed files with 545 additions and 22 deletions

View File

@@ -6190,6 +6190,12 @@ VOID CMy2015RemoteDlg::MessageHandle(CONTEXT_OBJECT* ContextObject)
// IO 线程不接触 UIclientId 用于循环快照路由到对应窗口)。
// 不用 WPARAM 携带 ID32 位 Windows 上 WPARAM 是 32 位,会截 64 位 clientID。
if (len < sizeof(ScreenPreviewRspHeader)) break;
// P2cMCP 挂起时接管(屏幕预览 = 主连接 RPC不能 CancelIO
// reqId 关联:命中 MCP 的 expectedReqId 才取走,避免误吞 MFC 预览在途响应。
uint64_t previewDevId = ContextObject->GetClientID();
const ScreenPreviewRspHeader* previewHdr = reinterpret_cast<const ScreenPreviewRspHeader*>(szBuffer);
if (McpServer().TakePreviewResponse(previewDevId, previewHdr->reqId, szBuffer, len))
break;
auto* msg = new CMy2015RemoteDlg::PreviewRspMsg{
ContextObject->GetClientID(),
std::vector<BYTE>(szBuffer, szBuffer + len)
@@ -6316,10 +6322,29 @@ VOID CMy2015RemoteDlg::MessageHandle(CONTEXT_OBJECT* ContextObject)
break;
}
case TOKEN_DRIVE_LIST: { // 文件管理【x】
// P2cMCP 挂起时接管。列盘 = 取走数据后用完即关;列目录 = 转向下发
// COMMAND_LIST_FILES、等 TOKEN_FILE_LIST此时不关子链接交给 TOKEN_FILE_LIST 分支收尾)。
uint64_t devId = ContextObject->GetClientID();
if (McpServer().IsPending(devId)) {
if (McpServer().OnDriveList(devId, ContextObject, szBuffer, len))
ContextObject->CancelIO(); // 只列盘 → 用完即关一次性子链接
break;
}
ContextObject->EnableZstdContext(6);
g_2015RemoteDlg->SendMessage(WM_OPENFILEMANAGERDIALOG, 0, (LPARAM)ContextObject);
break;
}
case TOKEN_FILE_LIST: { // 文件列表【P2c仅 MCP 无头路径会到达】
// 正常文件管理由 FileManagerDlg 的 hDlg 接管TOKEN_FILE_LIST 不进此分派;
// 到达此处只有 MCP 无头列目录这一种情形。挂起命中则取数据,否则(超时迟到)
// 直接关掉孤儿子链接,避免泄漏。
uint64_t devId = ContextObject->GetClientID();
if (McpServer().IsPending(devId)) {
McpServer().TakeMainResponse(devId, ContextObject->GetBuffer(0), ContextObject->GetBufferLength());
}
ContextObject->CancelIO(); // 无论是否命中,用完即关(无 MFC 对话框接管此子链接)
break;
}
case TOKEN_TALK_START: { // 发送消息【x】
g_2015RemoteDlg->SendMessage(WM_OPENTALKDIALOG, 0, (LPARAM)ContextObject);
break;

View File

@@ -167,6 +167,22 @@ std::string ToUtf8(const char* s, UINT cp) {
return out;
}
// 从 UTF-8 转为目标编码 cp文件链路的目录路径在 Windows 走 ANSI与 FileManagerDlg 的
// CString 一致cp 按 clientType 判定Windows=936、LNX/MAC=CP_UTF8见 BuildListFiles
// 非 UTF-8如纯 ASCII失败时原样返回。
std::string ToAnsi(const std::string& utf8, UINT cp) {
if (utf8.empty()) return "";
int wlen = MultiByteToWideChar(CP_UTF8, 0, utf8.c_str(), (int)utf8.size(), NULL, 0);
if (wlen <= 0) return utf8;
std::wstring w(wlen, L'\0');
MultiByteToWideChar(CP_UTF8, 0, utf8.c_str(), (int)utf8.size(), &w[0], wlen);
int alen = WideCharToMultiByte(cp, 0, w.c_str(), wlen, NULL, 0, NULL, NULL);
if (alen <= 0) return utf8;
std::string out(alen, '\0');
WideCharToMultiByte(cp, 0, w.c_str(), wlen, &out[0], alen, NULL, NULL);
return out;
}
// 从 "title|status|pid|r1|r2" 解析(自末尾 4 个 '|' 反推),兼容只有标题的老客户端。
// 逻辑复刻 SystemDlg.cpp::ParseWindowAttrs。
void ParseWindowAttrsStr(const char* s, std::string& title, std::string& status, uint64_t& pid) {
@@ -191,8 +207,8 @@ void ParseWindowAttrsStr(const char* s, std::string& title, std::string& status,
}
// 解析 TOKEN_PSLIST 缓冲data[0]=token其后为 [pid:4][name:arch\0][path\0] 记录)。
// 进程名/路径来自客户端 ANSICP_ACP),转 UTF-8 输出。
Json::Value ParseProcessList(const std::vector<BYTE>& data) {
// 进程名/路径客户端 ANSI编码由 cp 指定),转 UTF-8 输出。
Json::Value ParseProcessList(const std::vector<BYTE>& data, UINT cp) {
Json::Value arr(Json::arrayValue);
if (data.size() < 2) return arr;
const char* p = (const char*)data.data();
@@ -223,9 +239,9 @@ Json::Value ParseProcessList(const std::vector<BYTE>& data) {
Json::Value item(Json::objectValue);
item["pid"] = (Json::UInt64)pid;
item["name"] = ToUtf8(name.c_str(), CP_ACP);
item["arch"] = arch.empty() ? "N/A" : ToUtf8(arch.c_str(), CP_ACP);
item["path"] = ToUtf8(fullPath, CP_ACP);
item["name"] = ToUtf8(name.c_str(), cp);
item["arch"] = arch.empty() ? "N/A" : ToUtf8(arch.c_str(), cp);
item["path"] = ToUtf8(fullPath, cp);
arr.append(item);
}
return arr;
@@ -263,6 +279,93 @@ Json::Value ParseWindowList(const std::vector<BYTE>& data, UINT cp) {
return arr;
}
// 解析 TOKEN_DRIVE_LIST 缓冲data[0]=token其后为
// [letter:1][GetDriveType:1][totalMB:4][freeMB:4][typeName\0][fileSystem\0] 记录,
// 以 letter=='\0' 终止。typeName/fileSystem 为客户端 ANSI编码由 cp 指定)。
Json::Value ParseDriveList(const std::vector<BYTE>& data, UINT cp) {
Json::Value arr(Json::arrayValue);
if (data.size() < 2) return arr;
const char* p = (const char*)data.data();
size_t len = data.size();
size_t off = 1; // 跳过 TOKEN 字节
while (off + 10 <= len && p[off] != '\0') {
char letter = p[off];
unsigned int type = (unsigned int)(unsigned char)p[off + 1];
DWORD totalMB = *(const DWORD*)(p + off + 2);
DWORD freeMB = *(const DWORD*)(p + off + 6);
off += 10;
const char* typeName = p + off;
size_t tlen = BoundedStrlen(typeName, len - off);
if (tlen >= len - off) break;
off += tlen + 1;
const char* fileSystem = p + off;
size_t flen = BoundedStrlen(fileSystem, len - off);
if (flen >= len - off) break;
off += flen + 1;
std::string drive;
drive += letter;
drive += ":\\";
Json::Value item(Json::objectValue);
item["drive"] = drive;
item["type"] = (Json::UInt64)type;
item["typeName"] = ToUtf8(typeName, cp);
item["fileSystem"] = ToUtf8(fileSystem, cp);
item["totalMB"] = (Json::UInt64)totalMB;
item["freeMB"] = (Json::UInt64)freeMB;
arr.append(item);
}
return arr;
}
// 解析 TOKEN_FILE_LIST 缓冲data[0]=token其后为
// [attr:1][filename\0][sizeHigh:4][sizeLow:4][ftLastWriteTime:8] 记录)。
// 文件名为客户端 ANSI编码由 cp 指定attr 非 0 表示目录FILE_ATTRIBUTE_DIRECTORY
Json::Value ParseFileList(const std::vector<BYTE>& data, int maxEntries, UINT cp) {
Json::Value arr(Json::arrayValue);
if (data.size() < 2) return arr;
const char* p = (const char*)data.data();
size_t len = data.size();
size_t off = 1;
int count = 0;
while (off + 1 <= len && count < maxEntries) {
bool isDir = (p[off] & FILE_ATTRIBUTE_DIRECTORY) != 0;
off += 1;
const char* name = p + off;
size_t nlen = BoundedStrlen(name, len - off);
if (nlen >= len - off) break;
if (nlen == 0) break; // 空记录 = 尾部零填充LocalAlloc 对齐),停止解析
off += nlen + 1;
if (off + 16 > len) break;
DWORD sizeHigh = *(const DWORD*)(p + off);
DWORD sizeLow = *(const DWORD*)(p + off + 4);
ULONGLONG size = ((ULONGLONG)sizeHigh << 32) | sizeLow;
// 修改时间FILETIME100ns since 1601-01-01→ Unix 秒
// FILETIME 在线上是 dwLowDateTime(低 4 字节) 在前、dwHighDateTime(高 4 字节) 在后
// (客户端 memcpy(&ftLastWriteTime, sizeof(FILETIME)),见 FileManager.cpp::SendFilesList
DWORD ftLow = *(const DWORD*)(p + off + 8);
DWORD ftHigh = *(const DWORD*)(p + off + 12);
off += 16;
ULONGLONG ft = ((ULONGLONG)ftHigh << 32) | ftLow;
Json::Int64 mtime = (Json::Int64)(ft / 10000000ULL) - 11644473600LL;
Json::Value item(Json::objectValue);
item["name"] = ToUtf8(name, cp);
item["isDir"] = isDir;
item["size"] = (Json::UInt64)size;
item["mtime"] = mtime;
arr.append(item);
++count;
}
return arr;
}
// 收集所有在线主机 JSON 数组m_cs 锁内遍历,复用 BuildHostJson 序列化,方案 C
void CollectOnlineHosts(CMy2015RemoteDlg* parent, Json::Value& hosts) {
if (!parent) return;
@@ -456,6 +559,109 @@ Json::Value BuildActivityHistoryOutputSchema() {
return schema;
}
// get_screenshot 的 outputSchemaimage 元数据base64 数据在 content 的 image 块中)
Json::Value BuildScreenshotOutputSchema() {
Json::Value props(Json::objectValue);
Json::Value imageProp(Json::objectValue);
imageProp["type"] = "object";
Json::Value imageProps(Json::objectValue);
Json::Value mimeProp(Json::objectValue);
mimeProp["type"] = "string";
imageProps["mimeType"] = mimeProp;
const char* intFields[] = { "width", "height", "bytes" };
for (const char* f : intFields) {
Json::Value s(Json::objectValue);
s["type"] = "integer";
imageProps[f] = s;
}
imageProp["properties"] = imageProps;
props["image"] = imageProp;
Json::Value schema(Json::objectValue);
schema["type"] = "object";
schema["properties"] = props;
Json::Value required(Json::arrayValue);
required.append("image");
schema["required"] = required;
return schema;
}
// list_files 的 inputSchemaid 必填path 可选path 缺省/空 = 列盘)
Json::Value BuildListFilesInputSchema() {
Json::Value props(Json::objectValue);
Json::Value idProp(Json::objectValue);
idProp["type"] = "string";
idProp["description"] = u8"主机 id取 list_online_hosts / search_hosts 返回的 id 字段";
props["id"] = idProp;
Json::Value pathProp(Json::objectValue);
pathProp["type"] = "string";
pathProp["description"] = u8"要列举的目录路径(如 C:\\Windows省略或传空则返回驱动器列表";
props["path"] = pathProp;
Json::Value schema(Json::objectValue);
schema["type"] = "object";
schema["properties"] = props;
Json::Value required(Json::arrayValue);
required.append("id");
schema["required"] = required;
return schema;
}
// list_files 的 outputSchemadrives 数组 / files 数组,二者其一非空)
Json::Value BuildListFilesOutputSchema() {
Json::Value props(Json::objectValue);
Json::Value drivesProp(Json::objectValue);
drivesProp["type"] = "array";
Json::Value driveItems(Json::objectValue);
driveItems["type"] = "object";
Json::Value driveProps(Json::objectValue);
const char* driveStr[] = { "drive", "typeName", "fileSystem" };
for (const char* f : driveStr) {
Json::Value s(Json::objectValue);
s["type"] = "string";
driveProps[f] = s;
}
const char* driveInt[] = { "type", "totalMB", "freeMB" };
for (const char* f : driveInt) {
Json::Value s(Json::objectValue);
s["type"] = "integer";
driveProps[f] = s;
}
driveItems["properties"] = driveProps;
drivesProp["items"] = driveItems;
props["drives"] = drivesProp;
Json::Value filesProp(Json::objectValue);
filesProp["type"] = "array";
Json::Value fileItems(Json::objectValue);
fileItems["type"] = "object";
Json::Value fileProps(Json::objectValue);
Json::Value nameProp(Json::objectValue);
nameProp["type"] = "string";
fileProps["name"] = nameProp;
Json::Value isDirProp(Json::objectValue);
isDirProp["type"] = "boolean";
fileProps["isDir"] = isDirProp;
const char* fileInt[] = { "size", "mtime" };
for (const char* f : fileInt) {
Json::Value s(Json::objectValue);
s["type"] = "integer";
fileProps[f] = s;
}
fileItems["properties"] = fileProps;
filesProp["items"] = fileItems;
props["files"] = filesProp;
Json::Value schema(Json::objectValue);
schema["type"] = "object";
schema["properties"] = props;
return schema;
}
// tools/list
std::string BuildToolsListResult(const Json::Value& id) {
Json::Value result(Json::objectValue);
@@ -540,6 +746,30 @@ std::string BuildToolsListResult(const Json::Value& id) {
tools.append(tool);
}
// 7) get_screenshotP2c屏幕预览链路主连接 RPC
{
Json::Value tool(Json::objectValue);
tool["name"] = "get_screenshot";
tool["description"] = u8"截取指定在线主机的一帧屏幕,返回 base64 编码的 JPEG 图片image 对象含 mimeType/data/width/height。仅 Windows 客户端支持(能力位 CLIENT_CAP_SCREEN_PREVIEW主连接 RPC、一次性返回。";
tool["inputSchema"] = BuildGetHostDetailInputSchema(); // 复用 { id } 必填 schema
tool["outputSchema"] = BuildScreenshotOutputSchema();
tools.append(tool);
}
// 8) list_filesP2c文件链路列盘/列目录)
{
Json::Value tool(Json::objectValue);
tool["name"] = "list_files";
tool["description"] = u8"列举指定在线主机的目录。省略或空 path 返回驱动器列表drives给定 path 返回该目录一层内的文件/子目录files最多 500 条,含 name/isDir/size/mtime。只读。";
tool["inputSchema"] = BuildListFilesInputSchema();
tool["outputSchema"] = BuildListFilesOutputSchema();
tools.append(tool);
}
result["tools"] = tools;
return BuildResult(id, result);
}
@@ -674,7 +904,13 @@ std::string BuildListProcesses(const Json::Value& id, const Json::Value& args, C
if (!mcp.WaitPending(devId, data, kMcpToolTimeoutMs))
return BuildError(id, -32001, "Timeout waiting for process list");
Json::Value procs = ParseProcessList(data);
// 进程名/路径编码按 clientType 判定Windows 走 A 接口QueryFullProcessImageNameA 等)
// =客户端 ANSIGBK/936不随 CLIENT_CAP_UTF8 转 UTF-8LNX/MAC 的 /proc 天然 UTF-8。
// 不能用 GetClientEncoding——它按能力位返回 CP_UTF8会误解 Windows 客户端的 GBK 进程名。
CString clientType = ctx->GetAdditionalData(RES_CLIENT_TYPE);
UINT cp = (clientType == "LNX" || clientType == "MAC") ? CP_UTF8 : 936;
Json::Value procs = ParseProcessList(data, cp);
int count = (int)procs.size();
Json::Value result(Json::objectValue);
@@ -799,6 +1035,143 @@ std::string BuildGetActivityHistory(const Json::Value& id, const Json::Value& ar
return BuildResult(id, result);
}
// tools/callget_screenshot屏幕预览链路主连接 RPC不建子链接、不弹框
std::string BuildGetScreenshot(const Json::Value& id, const Json::Value& args, CMy2015RemoteDlg* parent) {
uint64_t devId = 0;
std::string err;
if (!ParseHostIdArg(args, devId, err))
return BuildError(id, -32602, err);
context* ctx = FindMainContext(parent, devId);
if (!ctx)
return BuildError(id, -32002, "Host not found or offline: " + std::to_string(devId));
// 能力位门槛:仅 Windows 客户端声明 CLIENT_CAP_SCREEN_PREVIEW非 Windows 返回明确错误)
if (!ctx->SupportsScreenPreview())
return BuildError(id, -32005, "Host does not support screen preview");
CMcpServer& mcp = CMcpServer::Instance();
if (!mcp.BeginPending(devId, "get_screenshot"))
return BuildError(id, -32003, "Device busy: another request is pending for this host");
uint16_t reqId = mcp.NextPreviewReqId();
mcp.SetPendingReqId(devId, reqId);
// 复用 MFC 预览的 RTT/FRP 自适应参数挑选,避免重复实现 GetTargetQualityLevel 逻辑
WORD maxWidth = 0;
BYTE quality = 0;
parent->ChooseScreenPreviewParams(ctx, maxWidth, quality);
parent->SendScreenPreviewRequest(ctx, reqId, maxWidth, quality);
std::vector<BYTE> data;
if (!mcp.WaitPending(devId, data, kMcpToolTimeoutMs))
return BuildError(id, -32001, "Timeout waiting for screenshot");
// data = [ScreenPreviewRspHeader][JPEG]
if (data.size() < sizeof(ScreenPreviewRspHeader))
return BuildError(id, -32000, "Invalid screenshot response");
const ScreenPreviewRspHeader* hdr = reinterpret_cast<const ScreenPreviewRspHeader*>(data.data());
if (hdr->status != SCREEN_PREVIEW_OK || hdr->format != SCREEN_PREVIEW_FMT_JPEG ||
hdr->bytes == 0 || data.size() < sizeof(ScreenPreviewRspHeader) + hdr->bytes) {
return BuildError(id, -32000,
"Screenshot capture failed (status " + std::to_string((int)hdr->status) + ")");
}
std::string b64 = httplib::detail::base64_encode(
std::string((const char*)data.data() + sizeof(ScreenPreviewRspHeader), hdr->bytes));
Json::Value image(Json::objectValue);
image["mimeType"] = "image/jpeg";
image["width"] = (Json::UInt64)hdr->width;
image["height"] = (Json::UInt64)hdr->height;
image["bytes"] = (Json::UInt64)hdr->bytes;
Json::Value result(Json::objectValue);
Json::Value structuredContent(Json::objectValue);
structuredContent["image"] = image; // 元数据(不含 base64避免在 content 之外重复大 payload
result["structuredContent"] = structuredContent;
Json::Value content(Json::arrayValue);
Json::Value item(Json::objectValue);
item["type"] = "image";
item["data"] = b64;
item["mimeType"] = "image/jpeg";
content.append(item);
result["content"] = content;
result["isError"] = false;
return BuildResult(id, result);
}
// tools/calllist_files列盘走 COMMAND_LIST_DRIVE→TOKEN_DRIVE_LIST
// 列目录走 COMMAND_LIST_DRIVE 开子链接后,再下发 COMMAND_LIST_FILES→TOKEN_FILE_LIST
std::string BuildListFiles(const Json::Value& id, const Json::Value& args, CMy2015RemoteDlg* parent) {
uint64_t devId = 0;
std::string err;
if (!ParseHostIdArg(args, devId, err))
return BuildError(id, -32602, err);
context* ctx = FindMainContext(parent, devId);
if (!ctx)
return BuildError(id, -32002, "Host not found or offline: " + std::to_string(devId));
std::string path = GetStringArg(args, "path");
bool listDrives = path.empty() || path == "." || path == "/" || path == "\\";
// 目录路径/文件名编码按 clientType 判定Windows 走 A 接口FindFirstFileA 等)= 客户端
// ANSI(GBK/936),不随 CLIENT_CAP_UTF8 转 UTF-8LNX/MAC 文件系统天然 UTF-8。不能用
// GetClientEncoding——它按能力位返回 CP_UTF8会误解 Windows 客户端的 GBK 文件名。
CString clientType = ctx->GetAdditionalData(RES_CLIENT_TYPE);
UINT cp = (clientType == "LNX" || clientType == "MAC") ? CP_UTF8 : 936;
// 下发前把 UTF-8 path 转成客户端 ANSI空/./\/\\ 为 ASCII转换后不变OnDriveList 直接用。
std::string ansiPath = ToAnsi(path, cp);
CMcpServer& mcp = CMcpServer::Instance();
if (!mcp.BeginPending(devId, "list_files", ansiPath))
return BuildError(id, -32003, "Device busy: another request is pending for this host");
BYTE cmd = COMMAND_LIST_DRIVE;
if (!ctx->Send2Client(&cmd, 1)) {
mcp.ClearPending(devId);
return BuildError(id, -32004, "Failed to send command to host");
}
std::vector<BYTE> data;
if (!mcp.WaitPending(devId, data, kMcpToolTimeoutMs))
return BuildError(id, -32001, "Timeout waiting for file list");
Json::Value drives(Json::arrayValue);
Json::Value files(Json::arrayValue);
int count = 0;
if (listDrives) {
drives = ParseDriveList(data, cp);
count = (int)drives.size();
} else {
files = ParseFileList(data, 500, cp); // 上限 500避免 JSON 响应过大
count = (int)files.size();
}
Json::Value result(Json::objectValue);
Json::Value structuredContent(Json::objectValue);
if (listDrives) structuredContent["drives"] = drives;
else structuredContent["files"] = files;
result["structuredContent"] = structuredContent;
Json::Value content(Json::arrayValue);
Json::Value item(Json::objectValue);
item["type"] = "text";
item["text"] = listDrives
? (std::string(u8"") + std::to_string(count) + std::string(u8" 个驱动器。"))
: (std::string(u8"") + std::to_string(count) + std::string(u8" 个条目。"));
content.append(item);
result["content"] = content;
result["isError"] = false;
return BuildResult(id, result);
}
// tools/call 分派
std::string BuildToolsCall(const Json::Value& root, CMy2015RemoteDlg* parent) {
const Json::Value& id = root["id"];
@@ -817,6 +1190,8 @@ std::string BuildToolsCall(const Json::Value& root, CMy2015RemoteDlg* parent) {
if (toolName == "list_processes") return BuildListProcesses(id, args, parent);
if (toolName == "list_windows") return BuildListWindows(id, args, parent);
if (toolName == "get_activity_history") return BuildGetActivityHistory(id, args, parent);
if (toolName == "get_screenshot") return BuildGetScreenshot(id, args, parent);
if (toolName == "list_files") return BuildListFiles(id, args, parent);
return BuildError(id, -32602,
"Unknown tool: " + (toolName.empty() ? std::string("(empty)") : toolName));
@@ -966,6 +1341,71 @@ void CMcpServer::ClearPending(uint64_t device_id) {
m_Pending.erase(device_id);
}
// ===== P2clist_files / get_screenshot 扩展 =====
bool CMcpServer::BeginPending(uint64_t device_id, const std::string& tool, const std::string& path) {
std::lock_guard<std::mutex> lk(m_PendingMutex);
if (m_Pending.find(device_id) != m_Pending.end()) return false; // 设备忙
PendingRequest r;
r.tool = tool;
r.path = path;
m_Pending[device_id] = std::move(r);
return true;
}
uint16_t CMcpServer::NextPreviewReqId() {
uint16_t v = m_PreviewReqId.fetch_add(1, std::memory_order_relaxed);
if (v == 0) v = m_PreviewReqId.fetch_add(1, std::memory_order_relaxed); // 跳过 0
return v;
}
void CMcpServer::SetPendingReqId(uint64_t device_id, uint16_t reqId) {
std::lock_guard<std::mutex> lk(m_PendingMutex);
auto it = m_Pending.find(device_id);
if (it != m_Pending.end()) it->second.expectedReqId = reqId;
}
bool CMcpServer::TakePreviewResponse(uint64_t device_id, uint16_t reqId, const BYTE* data, ULONG len) {
std::lock_guard<std::mutex> lk(m_PendingMutex);
auto it = m_Pending.find(device_id);
if (it == m_Pending.end()) return false; // 已超时清理 → 迟到数据,回落 MFC
if (it->second.tool != "get_screenshot") return false;
if (it->second.expectedReqId == 0 || it->second.expectedReqId != reqId) return false; // 过期/他途响应
it->second.data.assign(data, data + len);
it->second.done = true;
m_PendingCv.notify_one();
return true;
}
bool CMcpServer::OnDriveList(uint64_t device_id, context* subCtx, const BYTE* buf, ULONG len) {
std::string path;
bool listDrives = true;
{
std::lock_guard<std::mutex> lk(m_PendingMutex);
auto it = m_Pending.find(device_id);
if (it == m_Pending.end()) return true; // 已超时清理 → 调用方 CancelIO 收尾
path = it->second.path;
listDrives = path.empty() || path == "." || path == "/" || path == "\\";
if (listDrives) {
it->second.data.assign(buf, buf + len);
it->second.done = true;
m_PendingCv.notify_one();
return true; // 只列盘 → 调用方用完即关
}
}
// 列目录:锁外下发 COMMAND_LIST_FILES + path子链接保持等 TOKEN_FILE_LIST。
// path 已在 BuildListFiles 按客户端 ANSI 转好OnDriveList 无需再转),结尾 '\0'
// 与 FileManagerDlg 的 PacketSize=len+2 一致。
std::vector<BYTE> pkt;
pkt.reserve(1 + path.size() + 1);
pkt.push_back((BYTE)COMMAND_LIST_FILES);
pkt.insert(pkt.end(), path.begin(), path.end());
pkt.push_back(0);
subCtx->Send2Client(pkt.data(), (ULONG)pkt.size());
return false; // 继续等 TOKEN_FILE_LIST调用方不 CancelIO
}
// rand_sWindows CRT 加密安全随机源(基于系统 CSPRNG。其声明需在 <stdlib.h> 前
// 定义 _CRT_RAND_S为避免依赖 PCH 的包含顺序这里手动声明其导出原型errno_t == int
extern "C" int __cdecl rand_s(unsigned int* randomValue);

View File

@@ -25,6 +25,7 @@
#pragma comment(lib, "ws2_32.lib")
class CMy2015RemoteDlg;
class context;
// MCP (Model Context Protocol) 服务端httplib 封装 + JSON-RPC 2.0 分发 + 静态 token 校验。
// 与 CWebService 平级、互不依赖;默认禁用,经「扩展 → MCP设置」开启后监听
@@ -62,6 +63,26 @@ public:
// 工具线程:清理挂起状态(发送失败等提前退出路径)。
void ClearPending(uint64_t device_id);
// ===== P2clist_files / get_screenshot 扩展 =====
// 登记挂起带目录路径list_files 专用path 为空表示只列盘)。
bool BeginPending(uint64_t device_id, const std::string& tool, const std::string& path);
// get_screenshot工具线程生成 16 位 reqId跳过 00 表示未设置),用于丢弃过期响应。
uint16_t NextPreviewReqId();
// get_screenshot登记期望的 reqId必须在发送 COMMAND_SCREEN_PREVIEW_REQ 前调用)。
void SetPendingReqId(uint64_t device_id, uint16_t reqId);
// get_screenshotMessageHandle(TOKEN_SCREEN_PREVIEW_RSP) 在 IO 线程同步调用;
// 挂起命中且 reqId 一致则拷贝响应并返回 true否则 false回落到 MFC 预览路径)。
bool TakePreviewResponse(uint64_t device_id, uint16_t reqId, const BYTE* data, ULONG len);
// list_filesMessageHandle(TOKEN_DRIVE_LIST) 调用。
// 返回 true = 已取走盘列表(调用方 CancelIOfalse = 已转向下发 COMMAND_LIST_FILES、
// 等 TOKEN_FILE_LIST调用方不关子链接
bool OnDriveList(uint64_t device_id, context* subCtx, const BYTE* buf, ULONG len);
private:
CMcpServer();
~CMcpServer();
@@ -80,12 +101,17 @@ private:
// 挂起请求注册表(受 m_PendingMutex 保护,键 = device_id
struct PendingRequest {
std::string tool;
std::string path; // list_files目录路径空 = 只列盘
uint16_t expectedReqId = 0; // get_screenshot期望的预览 reqId0 = 未设置)
std::vector<BYTE> data;
bool done = false;
};
std::mutex m_PendingMutex;
std::condition_variable m_PendingCv;
std::map<uint64_t, PendingRequest> m_Pending;
// get_screenshot 的 reqId 发生器16 位,跳过 0。与 MFC 预览的 m_PreviewReqId 各自独立计数。
std::atomic<uint16_t> m_PreviewReqId{1};
};
// 全局访问器(仿 WebService(),见 WebService.h 末尾)