Feature: Add MCP (Model Context Protocol) server integration

Add an optional MCP server (JSON-RPC 2.0 over Streamable HTTP) exposing
an online-host listing tool, protected by a Bearer token. Disabled by
default; configured via a new "Extensions > MCP Settings" dialog.

- McpServer: httplib + JSON-RPC 2.0 dispatch (initialize/ping/tools/list/tools/call)
- McpSettingsDlg: runtime-created dialog for enable/port/bind/token
- HostJson: extract single-host JSON serialization shared with WebService
- FRP: expose MCP port (union with listening/Web ports) when bound to 0.0.0.0
- i18n: en_US / zh_TW translations

Co-Authored-By: deepseek-v4-pro
This commit is contained in:
yuanyuanxiang
2026-08-16 13:58:36 +02:00
parent 43398e405e
commit 13052b7cae
17 changed files with 1313 additions and 95 deletions

View File

@@ -0,0 +1,117 @@
#include "stdafx.h"
#include "HostJson.h"
#include "HostInfo.h" // _ClientList / MAP_NOTE
#include "context.h" // context 接口 + ONLINELIST_* 枚举
#include "2015RemoteDlg.h" // GetClientEncoding 辅助函数UTF-8 能力位判断)
#ifndef _WIN64
#ifdef _DEBUG
#pragma comment(lib, "jsoncpp/jsoncppd.lib")
#else
#pragma comment(lib, "jsoncpp/jsoncpp.lib")
#endif
#else
#ifdef _DEBUG
#pragma comment(lib, "jsoncpp/jsoncpp_x64d.lib")
#else
#pragma comment(lib, "jsoncpp/jsoncpp_x64.lib")
#endif
#endif
// Helper: Convert ANSI (GBK) string to UTF-8
// (自 WebService.cpp 抽出,供 BuildDeviceListJson 与 BuildHostJson 共用)
static std::string AnsiToUtf8(const CString& str) {
if (str.IsEmpty()) return "";
#ifdef _UNICODE
// Unicode build: CString is already UTF-16, convert to UTF-8
int len = WideCharToMultiByte(CP_UTF8, 0, str, -1, NULL, 0, NULL, NULL);
if (len <= 0) return "";
std::string result(len - 1, '\0');
WideCharToMultiByte(CP_UTF8, 0, str, -1, &result[0], len, NULL, NULL);
return result;
#else
// MBCS build: CString is ANSI (GBK), need to convert to UTF-16 first, then to UTF-8
int wlen = MultiByteToWideChar(CP_ACP, 0, str, -1, NULL, 0);
if (wlen <= 0) return "";
std::wstring wstr(wlen - 1, L'\0');
MultiByteToWideChar(CP_ACP, 0, str, -1, &wstr[0], wlen);
int u8len = WideCharToMultiByte(CP_UTF8, 0, wstr.c_str(), -1, NULL, 0, NULL, NULL);
if (u8len <= 0) return "";
std::string result(u8len - 1, '\0');
WideCharToMultiByte(CP_UTF8, 0, wstr.c_str(), -1, &result[0], u8len, NULL, NULL);
return result;
#endif
}
Json::Value BuildHostJson(context* ctx, _ClientList* clientMap) {
Json::Value device;
if (!ctx) return device; // 空 context → 返回 null Json::Value
// Use string for ID to avoid JavaScript number precision loss
device["id"] = std::to_string(ctx->GetClientID());
CString name = ctx->GetClientData(ONLINELIST_COMPUTER_NAME);
device["name"] = AnsiToUtf8(name);
// 用户在 MFC 端给这台主机起的备注(菜单"修改备注"写入 MAP_NOTE
if (clientMap) {
CString remark = clientMap->GetClientMapData(ctx->GetClientID(), MAP_NOTE);
if (!remark.IsEmpty()) {
device["remark"] = AnsiToUtf8(remark);
}
}
CString ip = ctx->GetClientData(ONLINELIST_IP);
device["ip"] = AnsiToUtf8(ip);
CString os = ctx->GetClientData(ONLINELIST_OS);
device["os"] = AnsiToUtf8(os);
CString location = ctx->GetClientData(ONLINELIST_LOCATION);
device["location"] = AnsiToUtf8(location);
CString rtt = ctx->GetClientData(ONLINELIST_PING);
device["rtt"] = AnsiToUtf8(rtt);
CString version = ctx->GetClientData(ONLINELIST_VERSION);
device["version"] = AnsiToUtf8(version);
// 活动窗口编码由客户端能力位决定:新客户端是 UTF-8老客户端是 CP_ACP默认 936
// 不能像其它字段那样无脑 AnsiToUtf8——会把新客户端的 UTF-8 字节再当 GBK 双重编码。
CString activeWindow = ctx->GetClientData(ONLINELIST_LOGINTIME);
std::string activeWindowU8;
if (!activeWindow.IsEmpty()) {
UINT cp = GetClientEncoding(ctx);
int wlen = MultiByteToWideChar(cp, 0, activeWindow, -1, NULL, 0);
if (wlen > 1) {
std::wstring w(wlen - 1, L'\0');
MultiByteToWideChar(cp, 0, activeWindow, -1, &w[0], wlen);
int u8len = WideCharToMultiByte(CP_UTF8, 0, w.c_str(), -1, NULL, 0, NULL, NULL);
if (u8len > 1) {
activeWindowU8.resize(u8len - 1);
WideCharToMultiByte(CP_UTF8, 0, w.c_str(), -1, &activeWindowU8[0], u8len, NULL, NULL);
}
}
}
device["activeWindow"] = activeWindowU8;
device["online"] = true;
// Add device group to response
std::string deviceGroup = ctx->GetGroupName();
if (deviceGroup.empty()) deviceGroup = "default";
device["group"] = deviceGroup;
// Get screen info from client's reported resolution
// Format: "n:MxN" where n=monitor count, M=width, N=height
CString resolution = ctx->GetAdditionalData(RES_RESOLUTION);
if (!resolution.IsEmpty()) {
device["screen"] = AnsiToUtf8(resolution); // e.g. "2:3840x1080"
}
CString clientType = ctx->GetAdditionalData(RES_CLIENT_TYPE);
device["clientType"] = AnsiToUtf8(clientType); // e.g. "MAC", "LNX", "EXE"
return device;
}