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:
@@ -43,6 +43,8 @@
|
||||
#include <sstream>
|
||||
#include "common/skCrypter.h"
|
||||
#include "common/commands.h"
|
||||
#include "McpServer.h"
|
||||
#include "McpSettingsDlg.h"
|
||||
#include "common/md5.h"
|
||||
#include <algorithm>
|
||||
#include <set>
|
||||
@@ -976,6 +978,7 @@ BEGIN_MESSAGE_MAP(CMy2015RemoteDlg, CDialogEx)
|
||||
ON_COMMAND(ID_SHELLCODE_TEST_AES_BIN, &CMy2015RemoteDlg::OnShellcodeTestAesBin)
|
||||
ON_COMMAND(ID_TOOL_RELOAD_PLUGINS, &CMy2015RemoteDlg::OnToolReloadPlugins)
|
||||
ON_COMMAND(ID_TOOL_PLUGIN_SETTINGS, &CMy2015RemoteDlg::OnToolPluginSettings)
|
||||
ON_COMMAND(ID_MCP_SETTINGS, &CMy2015RemoteDlg::OnMcpSettings)
|
||||
ON_COMMAND(ID_TRIGGER_SETTINGS, &CMy2015RemoteDlg::OnTriggerSettings)
|
||||
ON_COMMAND(ID_SHELLCODE_AES_C_ARRAY, &CMy2015RemoteDlg::OnShellcodeAesCArray)
|
||||
ON_COMMAND(ID_PARAM_KBLOGGER, &CMy2015RemoteDlg::OnParamKblogger)
|
||||
@@ -1217,13 +1220,63 @@ VOID CMy2015RemoteDlg::CreateSolidMenu()
|
||||
pExtMenu->DeleteMenu(ID_BACKUP_DATA, MF_BYCOMMAND);
|
||||
if (SHOULD_HIDE_MENU(HIDE_MENU_IMPORT_DATA, MF_IMPORT_DATA))
|
||||
pExtMenu->DeleteMenu(ID_IMPORT_DATA, MF_BYCOMMAND);
|
||||
if (SHOULD_HIDE_MENU(HIDE_MENU_RELOAD_PLUGINS, MF_RELOAD_PLUGINS))
|
||||
pExtMenu->DeleteMenu(ID_TOOL_RELOAD_PLUGINS, MF_BYCOMMAND);
|
||||
if (SHOULD_HIDE_MENU(HIDE_MENU_PLUGIN_REQUEST, MF_PLUGIN_REQUEST))
|
||||
pExtMenu->DeleteMenu(ID_PLUGIN_REQUEST, MF_BYCOMMAND);
|
||||
if (SHOULD_HIDE_MENU(HIDE_MENU_FRPS_FOR_SUB, MF_FRPS_FOR_SUB))
|
||||
pExtMenu->DeleteMenu(ID_FRPS_FOR_SUB, MF_BYCOMMAND);
|
||||
|
||||
// 动态插入「MCP设置」菜单项(2015Remote.rc 为 UTF-16 不便编辑,改运行时插入,
|
||||
// 位于「地理信息」之后、「插件设置」之前,见 docs/Mcp_Design.md §3.8.1)
|
||||
if (!SHOULD_HIDE_MENU(HIDE_MENU_MCP_SETTINGS, MF_MCP_SETTINGS)) {
|
||||
UINT mcpFlags = MF_BYCOMMAND;
|
||||
if (THIS_CFG.GetInt("settings", "McpEnabled", 0)) {
|
||||
mcpFlags |= MF_CHECKED; // 已启用 MCP 则菜单打勾
|
||||
}
|
||||
pExtMenu->InsertMenu(ID_TOOL_PLUGIN_SETTINGS, mcpFlags,
|
||||
ID_MCP_SETTINGS, _TR("MCP设置(&M)..."));
|
||||
}
|
||||
|
||||
// 将 3 个插件相关菜单项收拢到「插件」二级菜单(位于「MCP设置」之下、原「插件设置」之前,
|
||||
// 顺序不变:插件设置 / 刷新插件 / 获取插件)。
|
||||
// 注意:先删除顶层项、再按位置插入子菜单,避免同一命令 ID 同时存在于子菜单与顶层时,
|
||||
// MF_BYCOMMAND 的 DeleteMenu/InsertMenu 因重复 ID 而误删/误定位子菜单项。
|
||||
{
|
||||
// 记录「插件设置」的索引,作为「插件」子菜单的插入点
|
||||
int pluginPos = -1;
|
||||
for (UINT i = 0; i < pExtMenu->GetMenuItemCount(); i++) {
|
||||
if (pExtMenu->GetMenuItemID(i) == ID_TOOL_PLUGIN_SETTINGS) {
|
||||
pluginPos = (int)i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (pluginPos >= 0) {
|
||||
// 先删除 3 个顶层项
|
||||
pExtMenu->DeleteMenu(ID_TOOL_PLUGIN_SETTINGS, MF_BYCOMMAND);
|
||||
pExtMenu->DeleteMenu(ID_TOOL_RELOAD_PLUGINS, MF_BYCOMMAND);
|
||||
pExtMenu->DeleteMenu(ID_PLUGIN_REQUEST, MF_BYCOMMAND);
|
||||
|
||||
// 再创建「插件」子菜单
|
||||
CMenu pluginMenu;
|
||||
pluginMenu.CreatePopupMenu();
|
||||
pluginMenu.AppendMenu(MF_STRING, ID_TOOL_PLUGIN_SETTINGS, _TR("插件设置(&S)"));
|
||||
if (!SHOULD_HIDE_MENU(HIDE_MENU_RELOAD_PLUGINS, MF_RELOAD_PLUGINS))
|
||||
pluginMenu.AppendMenu(MF_STRING, ID_TOOL_RELOAD_PLUGINS, _TR("刷新插件(&P)"));
|
||||
if (!SHOULD_HIDE_MENU(HIDE_MENU_PLUGIN_REQUEST, MF_PLUGIN_REQUEST))
|
||||
pluginMenu.AppendMenu(MF_STRING, ID_PLUGIN_REQUEST, _TR("获取插件(&P)"));
|
||||
|
||||
// 继承原 3 项的菜单图标(见上方 SetMenuItemBitmaps 区)
|
||||
pluginMenu.SetMenuItemBitmaps(ID_TOOL_PLUGIN_SETTINGS, MF_BYCOMMAND, &m_bmOnline[53], &m_bmOnline[53]);
|
||||
if (!SHOULD_HIDE_MENU(HIDE_MENU_RELOAD_PLUGINS, MF_RELOAD_PLUGINS))
|
||||
pluginMenu.SetMenuItemBitmaps(ID_TOOL_RELOAD_PLUGINS, MF_BYCOMMAND, &m_bmOnline[43], &m_bmOnline[43]);
|
||||
if (!SHOULD_HIDE_MENU(HIDE_MENU_PLUGIN_REQUEST, MF_PLUGIN_REQUEST))
|
||||
pluginMenu.SetMenuItemBitmaps(ID_PLUGIN_REQUEST, MF_BYCOMMAND, &m_bmOnline[44], &m_bmOnline[44]);
|
||||
|
||||
// 插入到原「插件设置」位置(MF_BYPOSITION)
|
||||
pExtMenu->InsertMenu((UINT)pluginPos, MF_BYPOSITION | MF_POPUP,
|
||||
(UINT_PTR)pluginMenu.GetSafeHmenu(), _TR("插件(&P)"));
|
||||
pluginMenu.Detach(); // HMENU 所有权转交 pExtMenu,避免局部析构销毁子菜单
|
||||
}
|
||||
}
|
||||
|
||||
// --- 扩展菜单 - 语言设置子菜单 ---
|
||||
CMenu* pLangMenu = FindSubMenuByCommand(pExtMenu, ID_CHANGE_LANG);
|
||||
if (pLangMenu) {
|
||||
@@ -2080,6 +2133,34 @@ BOOL CMy2015RemoteDlg::OnInitDialog()
|
||||
}
|
||||
}
|
||||
|
||||
// Start MCP service(默认禁用;经「扩展 → MCP设置」开启后监听,见 docs/Mcp_Design.md §3.7)
|
||||
if (THIS_CFG.GetInt("settings", "McpEnabled", 0)) {
|
||||
auto mcpPort = THIS_CFG.GetInt("settings", "McpPort", 6544);
|
||||
auto mcpBind = THIS_CFG.GetStr("settings", "McpBind", "127.0.0.1");
|
||||
|
||||
// token 优先顺序:env BRAND_MCP_ENV_VAR → THIS_CFG McpToken → 皆空则随机(仅本次进程有效)
|
||||
const char* envTok = getenv(BRAND_MCP_ENV_VAR);
|
||||
std::string mcpToken;
|
||||
if (envTok && *envTok) {
|
||||
mcpToken = envTok;
|
||||
} else {
|
||||
mcpToken = THIS_CFG.GetStr("settings", "McpToken", "");
|
||||
}
|
||||
if (mcpToken.empty()) {
|
||||
mcpToken = GenerateRandomToken();
|
||||
Mprintf("[McpServer] %s / McpToken 均未设置,本次进程随机 token:%s\n",
|
||||
BRAND_MCP_ENV_VAR, mcpToken.c_str());
|
||||
}
|
||||
|
||||
McpServer().SetParentDlg(this);
|
||||
McpServer().SetToken(mcpToken);
|
||||
if (!McpServer().Start(mcpBind, mcpPort)) {
|
||||
Mprintf("McpServer start failed on %s:%d\n", mcpBind.c_str(), mcpPort);
|
||||
} else {
|
||||
Mprintf("McpServer started on %s:%d\n", mcpBind.c_str(), mcpPort);
|
||||
}
|
||||
}
|
||||
|
||||
UPDATE_SPLASH(18, "正在初始化文件上传模块...");
|
||||
int ret = InitFileUpload({}, {}, GetHMAC(), 64, 50, Logf, GetUpperHash());
|
||||
g_hKeyboardHook = SetWindowsHookEx(WH_KEYBOARD_LL, LowLevelKeyboardProc, AfxGetInstanceHandle(), 0);
|
||||
@@ -3185,6 +3266,12 @@ void CMy2015RemoteDlg::ApplyFrpSettings()
|
||||
auto arr = StringToVector(ports, ';');
|
||||
int fileServerPort = THIS_CFG.GetInt("settings", "WebSvrPort", 8080);
|
||||
|
||||
// MCP 端口:仅当「启用 MCP 且绑定 0.0.0.0」时纳入 FRP 暴露(与监听/Web 取并集,不覆盖)。
|
||||
int mcpEnabled = THIS_CFG.GetInt("settings", "McpEnabled", 0);
|
||||
std::string mcpBind = THIS_CFG.GetStr("settings", "McpBind", "127.0.0.1");
|
||||
int mcpPort = THIS_CFG.GetInt("settings", "McpPort", 6544);
|
||||
bool exposeMcp = (mcpEnabled != 0) && (mcpBind == "0.0.0.0") && (mcpPort > 0);
|
||||
|
||||
// 为每个服务端生成独立配置文件 (index=0 用 frpc.ini 保持兼容)
|
||||
for (size_t idx = 0; idx < servers.size(); ++idx) {
|
||||
std::string path = (idx == 0) ? basePath : baseNoExt + "." + std::to_string(idx) + ".ini";
|
||||
@@ -3214,6 +3301,14 @@ void CMy2015RemoteDlg::ApplyFrpSettings()
|
||||
cfg.SetInt(name, "remote_port", fileServerPort);
|
||||
cfg.SetStr(name, "proxy_protocol_version", "v2"); // 传递真实客户端 IP
|
||||
}
|
||||
if (exposeMcp) {
|
||||
std::string name = BRAND_NET_PREFIX "-MCP-" + std::to_string(mcpPort);
|
||||
cfg.SetStr(name, "type", "tcp");
|
||||
cfg.SetInt(name, "local_port", mcpPort);
|
||||
cfg.SetInt(name, "remote_port", mcpPort);
|
||||
// 不设置 proxy_protocol_version:MCP 由 httplib 提供、不解析 Proxy Protocol,
|
||||
// 且 MCP 以 Bearer token 鉴权、无需透传真实 IP(监听/Web 端口才需要 PP2)。
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3805,6 +3900,12 @@ void CMy2015RemoteDlg::Release()
|
||||
WebService().Stop();
|
||||
}
|
||||
|
||||
// Stop MCP service
|
||||
if (McpServer().IsRunning()) {
|
||||
Mprintf("Stopping McpServer...\n");
|
||||
McpServer().Stop();
|
||||
}
|
||||
|
||||
UninitFileUpload();
|
||||
DeletePopupWindow(TRUE);
|
||||
CloseAllLoopTips(); // 关闭所有"播放快照"循环窗口
|
||||
@@ -10756,6 +10857,16 @@ void CMy2015RemoteDlg::OnToolPluginSettings()
|
||||
dlg.DoModal();
|
||||
}
|
||||
|
||||
void CMy2015RemoteDlg::OnMcpSettings()
|
||||
{
|
||||
CMcpSettingsDlg dlg(this);
|
||||
if (dlg.DoModal() != IDOK) return;
|
||||
|
||||
// 保存后重生成 frpc.ini:若已启用 MCP 且绑定 0.0.0.0,则把 MCP 端口纳入 FRP 暴露列表
|
||||
// (与监听/Web 端口取并集,互不覆盖)。MCP 生效仍需重启,此处仅为下次启动准备配置。
|
||||
ApplyFrpSettings();
|
||||
}
|
||||
|
||||
void CMy2015RemoteDlg::OnTriggerSettings()
|
||||
{
|
||||
if (m_DllList.empty()) {
|
||||
|
||||
Reference in New Issue
Block a user