diff --git a/server/2015Remote/McpServer.cpp b/server/2015Remote/McpServer.cpp index 29d47fd..5b7a14b 100644 --- a/server/2015Remote/McpServer.cpp +++ b/server/2015Remote/McpServer.cpp @@ -1136,10 +1136,13 @@ Json::Value BuildRemoteKeyboardInputSchema(); Json::Value BuildRemoteKeyboardOutputSchema(); Json::Value BuildRemoteMouseInputSchema(); Json::Value BuildRemoteMouseOutputSchema(); +Json::Value BuildRemoteClipboardInputSchema(); +Json::Value BuildRemoteClipboardOutputSchema(); std::string BuildRemoteOpen(const Json::Value& id, const Json::Value& args, CMy2015RemoteDlg* parent); std::string BuildRemoteClose(const Json::Value& id, const Json::Value& args, CMy2015RemoteDlg* parent); std::string BuildRemoteKeyboard(const Json::Value& id, const Json::Value& args, CMy2015RemoteDlg* parent); std::string BuildRemoteMouse(const Json::Value& id, const Json::Value& args, CMy2015RemoteDlg* parent); +std::string BuildRemoteClipboard(const Json::Value& id, const Json::Value& args, CMy2015RemoteDlg* parent); // tools/list std::string BuildToolsListResult(const Json::Value& id) { @@ -1379,6 +1382,14 @@ std::string BuildToolsListResult(const Json::Value& id) { tool["outputSchema"] = BuildRemoteMouseOutputSchema(); tools.append(tool); } + { + Json::Value tool(Json::objectValue); + tool["name"] = "remote_clipboard"; + tool["description"] = u8"把文本写入远程主机的剪贴板(UTF-8 → GBK/ANSI,非 GBK 字符如 emoji 会丢失)。仅设置剪贴板,粘贴需随后用 remote_keyboard 注入 Ctrl+V。"; + tool["inputSchema"] = BuildRemoteClipboardInputSchema(); + tool["outputSchema"] = BuildRemoteClipboardOutputSchema(); + tools.append(tool); + } } result["tools"] = tools; @@ -3408,6 +3419,110 @@ std::string BuildRemoteMouse(const Json::Value& id, const Json::Value& args, CMy return BuildResult(id, result); } +// ===== P5:remote_clipboard(写远程剪贴板,UTF-8 → GBK 直发,见设计 §6.4)===== + +Json::Value BuildRemoteClipboardInputSchema() { + 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 sid(Json::objectValue); + sid["type"] = "string"; + sid["description"] = u8"remote_open 返回的 session_id"; + props["session_id"] = sid; + + Json::Value text(Json::objectValue); + text["type"] = "string"; + text["description"] = u8"要写入剪贴板的文本(UTF-8;非 GBK 字符如 emoji 会丢失)"; + props["text"] = text; + + Json::Value schema(Json::objectValue); + schema["type"] = "object"; + schema["properties"] = props; + Json::Value required(Json::arrayValue); + required.append("id"); + required.append("session_id"); + required.append("text"); + schema["required"] = required; + return schema; +} + +Json::Value BuildRemoteClipboardOutputSchema() { + Json::Value props(Json::objectValue); + Json::Value schema(Json::objectValue); + schema["type"] = "object"; + schema["properties"] = props; + return schema; +} + +// tools/call:remote_clipboard(写远程剪贴板;成功返回空对象) +std::string BuildRemoteClipboard(const Json::Value& id, const Json::Value& args, CMy2015RemoteDlg* parent) { + CMcpServer& mcp = CMcpServer::Instance(); + mcp.SweepIdleScreenCtrl(kScreenCtrlIdleTimeoutSec); + + uint64_t devId = 0; + context* ctx = nullptr; + std::string errJson; + if (!ResolveScreenCtrlSessionHost(id, args, parent, devId, ctx, errJson)) + return errJson; + + std::string sessionId = GetStringArg(args, "session_id"); + if (sessionId.empty()) + return BuildError(id, -32602, "Missing required parameter: session_id"); + + std::string text = GetStringArg(args, "text"); + if (text.empty()) + return BuildError(id, -32602, "Missing required parameter: text"); + if (text.size() > 262144) // 256 KB 上限(CF_TEXT 单包,防内存/传输失控) + return BuildError(id, -32602, "text too long (max 262144 bytes)"); + + // UTF-8 → GBK:客户端 UpdateClientClipboard 以 CF_TEXT(ANSI/GBK)落剪贴板(设计 §6.4)。 + // 非 GBK 字符(emoji 等)在此被替换为 '?',MVP 已知限制。 + std::string ansi = ToAnsi(text, 936); + + // 校验 session_id/busy 并置 busy,取屏幕子连接(注入与剪贴板共用同一子连接串行化)。 + context* subCtx = nullptr; + int screenW = 0, screenH = 0; + int r = mcp.BeginScreenCtrlAction(devId, sessionId, subCtx, screenW, screenH); + if (r == 2) return BuildError(id, -32003, "Device busy: another injection is in flight for this session"); + if (r == 1) return BuildError(id, -32002, "Remote control session not found, not ready, or closed: " + sessionId); + + // 组包 [COMMAND_SCREEN_SET_CLIPBOARD][GBK text],经屏幕子连接发送(客户端自行补 '\0')。 + const int len = (int)(1 + ansi.size()); + std::vector packet((size_t)len); + packet[0] = COMMAND_SCREEN_SET_CLIPBOARD; + memcpy(packet.data() + 1, ansi.data(), ansi.size()); + bool ok = subCtx->Send2Client(packet.data(), (ULONG)len) != FALSE; + + mcp.EndScreenCtrlAction(devId, sessionId); + + if (!ok) + return BuildError(id, -32004, "Failed to send clipboard text"); + + // 审计(不可关闭;记字节数,不落全文以免审计日志膨胀) + if (parent) { + std::string audit = "host " + std::to_string(devId) + " remote-clipboard: session " + sessionId + + " input_bytes=" + std::to_string(text.size()); + parent->PostMessageA(WM_SHOWERRORMSG, + (WPARAM)new CString(ToAnsi(audit, 936).c_str()), + (LPARAM)new CString(_TR("MCP远程控制"))); + } + + Json::Value result(Json::objectValue); + result["structuredContent"] = Json::Value(Json::objectValue); + Json::Value content(Json::arrayValue); + Json::Value item(Json::objectValue); + item["type"] = "text"; + item["text"] = std::string(u8"ok"); + 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"]; @@ -3440,6 +3555,7 @@ std::string BuildToolsCall(const Json::Value& root, CMy2015RemoteDlg* parent) { if (toolName == "remote_close") return BuildRemoteClose(id, args, parent); if (toolName == "remote_keyboard") return BuildRemoteKeyboard(id, args, parent); if (toolName == "remote_mouse") return BuildRemoteMouse(id, args, parent); + if (toolName == "remote_clipboard") return BuildRemoteClipboard(id, args, parent); return BuildError(id, -32602, "Unknown tool: " + (toolName.empty() ? std::string("(empty)") : toolName));