Files
SimpleRemoter/server/2015Remote/McpServer.h
yuanyuanxiang 61089e5fae 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
2026-08-19 13:27:32 +02:00

122 lines
5.1 KiB
C++
Raw 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.
#pragma once
#include <Windows.h>
#include <string>
#include <thread>
#include <atomic>
#include <chrono>
#include <mutex>
#include <condition_variable>
#include <map>
#include <vector>
#include <cstdint>
// httplib 与 Windows 头部的 min/max 宏冲突,按 file_server.h 的既有约定处理。
#undef min
#undef max
#include "httplib.h"
#ifndef max
#define max(a,b) (((a) > (b)) ? (a) : (b))
#endif
#ifndef min
#define min(a,b) (((a) < (b)) ? (a) : (b))
#endif
#pragma comment(lib, "ws2_32.lib")
class CMy2015RemoteDlg;
class context;
// MCP (Model Context Protocol) 服务端httplib 封装 + JSON-RPC 2.0 分发 + 静态 token 校验。
// 与 CWebService 平级、互不依赖;默认禁用,经「扩展 → MCP设置」开启后监听
// (默认 127.0.0.1:6544仅本机回环。见 docs/Mcp_Design.md。
class CMcpServer {
public:
static CMcpServer& Instance();
void SetParentDlg(CMy2015RemoteDlg* pDlg) { m_parent = pDlg; }
void SetToken(const std::string& token) { m_token = token; }
bool Start(const std::string& bind, int port);
void Stop();
bool IsRunning() const { return m_running.load(); }
// ===== P2b无请求 id 的一次性响应 → 每 host 单飞行 =====
// 进程/窗口列表TOKEN_PSLIST/TOKEN_WSLIST一次性子链接与历史活动
// TOKEN_REPORT_ACTIVITY主连接 RPC都不带请求关联同一 host 同一时刻
// 只允许一个挂起请求,避免同 host 并发请求响应归属歧义。
// 注意:单飞行只约束 MCP 侧同 host 并发,不约束与 MFC 对话框并存(各用独立子链接)。
// 该 host 是否已有挂起请求MessageHandle 在 TOKEN_PSLIST/TOKEN_WSLIST 分支调用)。
bool IsPending(uint64_t device_id);
// 拷贝响应缓冲到挂起结果并唤醒等待者(在 MessageHandle 内同步调用,数据此时仍在
// context 缓冲中、IO 线程被阻塞,拷贝是安全的)。
void TakeMainResponse(uint64_t device_id, const BYTE* data, ULONG len);
// 工具线程登记挂起false = 该 host 已有挂起请求,设备忙)。
bool BeginPending(uint64_t device_id, const std::string& tool);
// 工具线程:等待响应;成功返回 true 并把缓冲写入 out超时/失败返回 false并清理
bool WaitPending(uint64_t device_id, std::vector<BYTE>& out, int timeoutMs);
// 工具线程:清理挂起状态(发送失败等提前退出路径)。
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();
CMcpServer(const CMcpServer&) = delete;
CMcpServer& operator=(const CMcpServer&) = delete;
// POST /mcp 处理器token 校验 + JSON-RPC 分发。
void HandleMcp(const httplib::Request& req, httplib::Response& res);
httplib::Server m_server;
std::thread m_thread;
std::atomic<bool> m_running{false};
std::string m_token;
CMy2015RemoteDlg* m_parent = nullptr;
// 挂起请求注册表(受 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 末尾)
inline CMcpServer& McpServer() { return CMcpServer::Instance(); }
// 生成 32 hex128-bit随机 token供运行时兜底与「MCP设置」对话框预填复用。
std::string GenerateRandomToken();