Feature: Add exec_command MCP tool

Add a one-shot exec_command MCP tool that runs a command on a remote
Windows host and returns stdout plus exit code, reusing the Web terminal
link (main-connection COMMAND_SHELL, a shell sub-connection, and a
sentinel command line located via rfind to tolerate ConPTY echo).

The sentinel marker is embedded in the command line ConPTY echoes back,
so it is only treated as hit when it starts a line (preceded by a newline
or the buffer start); this keeps the echoed marker from being mistaken
for the real sentinel when the echo packet arrives before the output.

Execution is gated at Web remote-desktop sensitivity: a read-only mode
(McpReadonly, default on, hides the tool) and a command whitelist
(McpCmdWhitelist) with built-in read-only prefixes. Shell metacharacters
(& | < > ^) are rejected before whitelist matching, and each execution is
recorded in the server audit log.

Extend the MCP settings dialog with the read-only checkbox and a
multi-line whitelist box (commas and newlines both accepted, normalized
to a comma-separated list on save), and add English and Traditional
Chinese mappings for the new UI and audit-log strings.

Co-Authored-By: deepseek-v4-pro
This commit is contained in:
yuanyuanxiang
2026-08-23 23:23:20 +02:00
parent 8bd6f3b60a
commit 0ddbc1aced
7 changed files with 579 additions and 5 deletions

View File

@@ -98,6 +98,47 @@ public:
bool WaitPendingRegistry(uint64_t device_id, std::vector<BYTE>& subkeys,
std::vector<BYTE>& values, int timeoutMs);
// ===== P3exec_commandWindows 一次性远程命令,复刻 Web 终端链路)=====
// 流程:工具线程 BeginTermPending → 主连接下发 COMMAND_SHELL → 客户端 shell 子连接回
// TOKEN_TERMINAL_START / TOKEN_SHELL_START → RegisterTerminalContext 接管 → 发
// COMMAND_NEXT(+PTY resize) 启动输出回流 → 工具线程发哨兵命令行 → OnTerminalData 收集
// 并做哨兵检测 → WaitTerminalDone 取 stdout + exit_code → CancelIO 关子链接。
// 单设备单终端MVPBeginTermPending 命中已有会话即拒绝,避免同设备并发归属歧义。
// 该 host 是否已有待接管的终端会话MessageHandle TOKEN_*_START 分支调用)。
bool IsTermPending(uint64_t device_id);
// 终端子连接就绪:接管(登记 subCtx/isPty、清 pending、发 COMMAND_NEXT 唤醒回流)。
void RegisterTerminalContext(uint64_t device_id, context* subCtx, bool isPty);
// 终端子连接是否已被 MCP 接管MessageHandle 顶部据此把 shell 输出路由到 OnTerminalData
bool IsTerminalContext(context* subCtx);
// 追加 shell 输出并做哨兵检测(命中置 done 并唤醒等待者)。
void OnTerminalData(context* subCtx, const BYTE* data, ULONG len);
// shell 进程退出TOKEN_TERMINAL_CLOSE
void OnTerminalClosed(context* subCtx);
// 工具线程登记终端挂起false = 该 host 已有终端会话)。
bool BeginTermPending(uint64_t device_id, const std::string& command, const std::string& nonce);
// 工具线程:等待子连接回 START 并接管true = 已接管,输出 subCtx/isPty
bool WaitTerminalReady(uint64_t device_id, context*& subCtx, bool& isPty, int timeoutMs);
// 工具线程:等待输出收集完成(哨兵命中 / 进程退出 / 超时)。
// 成功 trueout=截断到哨兵前的原始字节、exitCode=0/1进程退出无哨兵时 -1、closed=是否进程退出。
bool WaitTerminalDone(uint64_t device_id, std::vector<BYTE>& out, int& exitCode, bool& closed, int timeoutMs);
// 工具线程:清理终端挂起(发送失败等提前退出路径)。
void ClearTermPending(uint64_t device_id);
// 安全配置(启动时由 CMy2015RemoteDlg 读 THIS_CFG 后设置)。
void SetReadonly(bool readonly) { m_readonly = readonly; }
void SetCmdWhitelist(const std::string& whitelist) { m_cmdWhitelist = whitelist; }
bool IsReadonly() const { return m_readonly; }
const std::string& GetCmdWhitelist() const { return m_cmdWhitelist; }
private:
CMcpServer();
~CMcpServer();
@@ -131,6 +172,27 @@ private:
// get_screenshot 的 reqId 发生器16 位,跳过 0。与 MFC 预览的 m_PreviewReqId 各自独立计数。
std::atomic<uint16_t> m_PreviewReqId{1};
// ===== P3exec_command 终端会话(受 m_TermMutex 保护)=====
struct TermSession {
bool started = false; // false=已发 COMMAND_SHELL 待 STARTtrue=已接管
context* subCtx = nullptr; // shell 子连接上下文
bool isPty = false; // true=ConPTY(UTF-8)false=老 cmd 管道(GBK)
std::string command; // 原始 UTF-8 命令(审计用)
std::string nonce; // 哨兵随机串
std::vector<BYTE> data; // 收集的原始 shell 输出
size_t sentPos = 0; // 哨兵在 data 中的位置
int exitCode = -1;
bool done = false; // 哨兵命中
bool closed = false; // TOKEN_TERMINAL_CLOSE进程退出
};
std::mutex m_TermMutex;
std::condition_variable m_TermCv;
std::map<uint64_t, TermSession> m_TermSessions; // device_id → 会话
std::map<context*, uint64_t> m_TermContextToDevice; // subCtx → device_id顶部路由
bool m_readonly = true;
std::string m_cmdWhitelist;
};
// 全局访问器(仿 WebService(),见 WebService.h 末尾)