Files
envi-code/SourceCode/Code2026/cad_mcp_frame/mcp_ConfigParser.cpp
T
company-desktop ecb8436720 feat: 移植 cad_mcp_plugins,并对齐中望平台的符号与依赖
插件工程在 CadProject/src/cad_mcp_plugins(8 个 cpp/h),本次一并整合进 code2026。

移植改动:
- 源码拷到 SourceCode/Code2026/cad_mcp_plugins/;cadsdk.h -> KTCad.h
- 输出是普通 DLL(.dll),由 cad_mcp_frame 运行时 LoadLibrary,不注册 ARX 命令
- 新增 cad_mcp_plugins/cmakelists.txt:按 code2026 惯例
  (target_set_name_and_suffix ".dll" / target_link_objectarx / 预编译头 / SUB_ARX)
- mcp_plugin_api.h 从 cad_mcp_frame/inc 上移到 SourceCode/Code2026/inc:
  frame 与 plugins 共用,且在 ShsGlobalIncludes 的 include 路径里两边都可见;
  frame 的 CMake 去掉已删除的本地 inc 目录
- 顶层在同一个 C++11+ 条件里挂载 add_custom_module_2(cad_mcp_plugins CadMcpPlugins)
- 中望适配:源码里的 #pragma comment(lib,"rxapi.lib") 在中望下没有这个库(叫 ZwRx.lib),
  该 pragma 与导出名都按 #ifdef ZWCAD 分开(中望导出 zcrxGetApiVersion);
  frame 里 GetProcAddress 查插件版本号的函数名同样按平台分

验证:
- R230:cad_mcp_frame.arx 826 KB + cad_mcp_plugins.dll 866 KB,已自动部署
- Z2026:cad_mcp_frame.zrx 825 KB + cad_mcp_plugins.dll 864 KB,已自动部署
- 配置里 16 个 c++_dll 工具的工厂函数(CreateXxxTool)全部出现在 dll 导出表里
- 所有 .cpp/.h 为 UTF-8 with BOM

未做:没有在 CAD 里实跑。另外配置文件本身没有随构建部署(set_debug_post_build 只拷产物),
需要把 cad-mcp-frame.json(或改名 mcp_config.json)与 LISP 目录放到部署目录,
否则 frame 起来后 LoadAndRegister 失败会直接自关。详见 cad_mcp_frame-review.md。
2026-10-29 00:16:37 +08:00

211 lines
5.9 KiB
C++

#include "mcp_ConfigParser.h"
#include "KTCad.h"
#include <windows.h>
#include "mcp_tool.h"
#include "mcp_utils.h"
#include "ghc/filesystem.hpp"
#include "rxregsvc.h"
#pragma region mcp_ConfigParser
bool mcp_ConfigParser::LoadAndRegister(const std::wstring &jsonFilePath, mcp_Tools &registry)
{
std::ifstream ifs(jsonFilePath);
if (!ifs.is_open()) return false;
nlohmann::json config;
try { ifs >> config; }
catch (...) { return false; } // 防御非法 JSON
fs::path pluginPath
= mcp_utils::relative2absolute(config["global_settings"].value("plugin_directory", "./mcp_plugins"));
fs::path lispPath
= mcp_utils::relative2absolute(config["global_settings"].value("lisp_directory", "./mcp_lisp"));
if (!fs::exists(lispPath))
AfxMessageBox(CString(_T("[MCP Server]Warning: The LISP script directory does not exist; the associated tools will not load!\n")) + pluginPath.c_str());
if (!fs::exists(pluginPath))
AfxMessageBox(CString(_T("[MCP Server]Warning: The plugin DLL directory does not exist; the associated tools will not load!\n")) + pluginPath.c_str());
for (const auto &toolConfig : config["tools"])
{
std::string type = toolConfig["backend"].value("type", "");
if (type == "lisp_inline")
{
auto pTool = std::make_shared<mcp_Tool_LispInline>();
pTool->SetConfig(toolConfig);
registry.Register(pTool);
}
else if (type == "lisp_file" && fs::exists(lispPath))
{
auto pTool = std::make_shared<mcp_Tool_LispFile>(lispPath);
pTool->SetConfig(toolConfig);
registry.Register(pTool);
}
else if (type == "c++_dll" && fs::exists(pluginPath))
{
LoadDllTool(toolConfig, pluginPath, registry);
}
}
return true;
}
// 中望平台用的是 zcrx* 家族的符号(ARX 下 acrxEntryPoint 会被 SDK 宏改名,
// 但这里显式声明的版本号函数不会,所以按平台分开)
#ifdef ZWCAD
extern "C" int zcrxGetApiVersion();
#define MCP_OWN_API_VERSION() zcrxGetApiVersion()
#else
extern "C" int acrxGetApiVersion();
#define MCP_OWN_API_VERSION() acrxGetApiVersion()
#endif
void mcp_ConfigParser::LoadDllTool(const nlohmann::json &toolConfig, const std::wstring &pluginDir, mcp_Tools &registry)
{
fs::path dllPath
= mcp_utils::relative2absolute(toolConfig["backend"].value("dll_name", "")
, fs::path(pluginDir));
std::string factoryName = toolConfig["backend"].value("factory_function", "");
if (!fs::exists(dllPath))
{
AfxMessageBox(CString(_T("[MCP Server]Warning: The DLL file does not exist, the associated tools will not load!\n")) + dllPath.c_str());
return;
}
HMODULE hMod = LoadLibrary(dllPath.c_str());
CString verStr;
verStr.Format(_T("Major ver:%d, Minor ver:%d"), ARX, SUB_ARX);
CString ver_errMsg;
ver_errMsg.Format(
CString(_T("[MCP Server]Error: Failed to load the DLL. "))
+ CString(_T("Please check whether the version of the ObjectARX SDK used by "))
+ CString("the DLL matches the current ARX version(%s).\n"), verStr );
if (!hMod)
{
AfxMessageBox(ver_errMsg);
return;
}
// 1. 定义版本校验函数指针
typedef int (*GetApiVersionFunc)();
#ifdef ZWCAD
GetApiVersionFunc getVerFn = (GetApiVersionFunc)GetProcAddress(hMod, "zcrxGetApiVersion");
#else
GetApiVersionFunc getVerFn = (GetApiVersionFunc)GetProcAddress(hMod, "acrxGetApiVersion");
#endif
// 2. 核心拦截逻辑
if (!getVerFn)
{
// 连这个函数都没导出,说明根本不是合法的 ObjectARX/MCP 插件
AfxMessageBox(CString(
CString(_T("The ObjectARX version of the DLL could not be determined."))
+ CString(_T("Please ensure that the acrxGetApiVersion function is exported."))
));
FreeLibrary(hMod);
return;
}
// 拿 DLL 编译时的 SDK 版本,和基座当前运行的 SDK 版本进行比对
int arx_version(MCP_OWN_API_VERSION());
int dll_version(getVerFn());
if (arx_version != dll_version)
{
// 平台大版本不匹配,强行加载必定闪崩,果断踢掉!
FreeLibrary(hMod);
AfxMessageBox(ver_errMsg);
return;
}
typedef mcp_Tool *(*CreateFunc)();
typedef void (*DestroyFunc)(mcp_Tool *);
std::string destroyName = "Destroy_" + factoryName;
CreateFunc createFn = (CreateFunc)GetProcAddress(hMod, factoryName.c_str());
DestroyFunc destroyFn = (DestroyFunc)GetProcAddress(hMod, destroyName.c_str());
if (createFn && destroyFn)
{
mcp_Tool *pRealTool = createFn();
if (pRealTool)
{
// 给真正的 DLL 实例也注入配置,以防第三方需要在内部读取
std::string cfgStr = toolConfig.dump();
pRealTool->SetConfigAbiSafe(cfgStr.c_str());
//pRealTool->SetConfig(toolConfig);
// 实例化代理类,接管生命周期
auto pProxy = std::make_shared<mcp_Tool_DllProxy>(hMod, pRealTool, destroyFn);
// 代理类继承自基类,也需要配置来处理 GetName 和 ToLLM
pProxy->SetConfig(toolConfig);
registry.Register(pProxy);
}
else
{
FreeLibrary(hMod);
}
}
else
{
FreeLibrary(hMod); // 导出函数找不到,直接卸载
}
}
#pragma endregion mcp_ConfigParser
#pragma region mcp_Tools
// 注册工具
void mcp_Tools::Register(std::shared_ptr<mcp_Tool> pTool)
{
if (pTool != nullptr)
{
m_tools[pTool->GetName()] = pTool;
}
}
// 组装所有工具的描述,返回 JSON 数组 (用于 tools/list)
nlohmann::json mcp_Tools::ToLLM() const
{
nlohmann::json arr = nlohmann::json::array();
for (const auto &pair : m_tools)
{
arr.push_back(pair.second->ToLLM());
}
return arr;
}
// 路由分发执行指令 (用于 tools/call)
nlohmann::json mcp_Tools::Call(const std::string &name, const nlohmann::json &args)
{
auto it = m_tools.find(name);
if (it != m_tools.end())
{
return it->second->Execute(args);
}
// 找不到工具时,返回标准的 JSON-RPC 错误对象
return mcp_Tool_Utility::make_error(-32601, L"Specified tool not found: " + mcp_utils::utf8ToWString(name));
}
void mcp_Tools::print()
{
for (const auto &pair : m_tools)
{
acutPrintf(_T("Tool: %s Already registered.\n"), mcp_utils::utf8ToWString(pair.first.c_str()).c_str());
}
}
void mcp_Tools::clear()
{
m_tools.clear();
}
#pragma endregion mcp_Tools