Feature: MCP 远程控制(remote_open/close/keyboard/mouse/clipboard) #4

Merged
yuanyuanxiang merged 8 commits from feature/mcp-remote-control into main 2026-08-26 12:05:24 +00:00
Showing only changes of commit 25a6e2d07a - Show all commits

View File

@@ -1136,10 +1136,13 @@ Json::Value BuildRemoteKeyboardInputSchema();
Json::Value BuildRemoteKeyboardOutputSchema(); Json::Value BuildRemoteKeyboardOutputSchema();
Json::Value BuildRemoteMouseInputSchema(); Json::Value BuildRemoteMouseInputSchema();
Json::Value BuildRemoteMouseOutputSchema(); Json::Value BuildRemoteMouseOutputSchema();
Json::Value BuildRemoteClipboardInputSchema();
Json::Value BuildRemoteClipboardOutputSchema();
std::string BuildRemoteOpen(const Json::Value& id, const Json::Value& args, CMy2015RemoteDlg* parent); 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 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 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 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 // tools/list
std::string BuildToolsListResult(const Json::Value& id) { std::string BuildToolsListResult(const Json::Value& id) {
@@ -1379,6 +1382,14 @@ std::string BuildToolsListResult(const Json::Value& id) {
tool["outputSchema"] = BuildRemoteMouseOutputSchema(); tool["outputSchema"] = BuildRemoteMouseOutputSchema();
tools.append(tool); 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; result["tools"] = tools;
@@ -3408,6 +3419,110 @@ std::string BuildRemoteMouse(const Json::Value& id, const Json::Value& args, CMy
return BuildResult(id, result); return BuildResult(id, result);
} }
// ===== P5remote_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/callremote_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_TEXTANSI/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<BYTE> 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 分派 // tools/call 分派
std::string BuildToolsCall(const Json::Value& root, CMy2015RemoteDlg* parent) { std::string BuildToolsCall(const Json::Value& root, CMy2015RemoteDlg* parent) {
const Json::Value& id = root["id"]; 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_close") return BuildRemoteClose(id, args, parent);
if (toolName == "remote_keyboard") return BuildRemoteKeyboard(id, args, parent); if (toolName == "remote_keyboard") return BuildRemoteKeyboard(id, args, parent);
if (toolName == "remote_mouse") return BuildRemoteMouse(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, return BuildError(id, -32602,
"Unknown tool: " + (toolName.empty() ? std::string("(empty)") : toolName)); "Unknown tool: " + (toolName.empty() ? std::string("(empty)") : toolName));