源工程在 d:\new-start\CadProject\src\cad_mcp_frame(vcpkg 独立工程), 本次按 code2026 的 preset/CMake 体系移植到 SourceCode/Code2026/cad_mcp_frame。 移植改动: - 源码 + .def + .rc + res/ + cad-mcp-frame.json 拷入 cad_mcp_frame/; 插件接口头 mcp_plugin_api.h 放到 cad_mcp_frame/inc/ - cadsdk.h -> KTCad.h(两者除尾部 4 个 include 与 using namespace MemOper 外完全一致) - nlohmann/json 单头放 SourceCode/dep_inc/nlohmann/json.hpp, ghc::filesystem 单头放 SourceCode/dep_inc/ghc/filesystem.hpp; target_link_objectarx 已把 DEP_INC_ROOT 加进 include 路径,故无需额外配置 - 新增 cad_mcp_frame/cmakelists.txt:按 PipeHangerCmd 的写法 (add_def_file_name / target_set_name_and_suffix .arx / target_link_objectarx / target_include_directories inc / set_debug_post_build) - 补 SUB_ARX 宏:原工程由 CadProject 的 cmake 注入,code2026 只注入 ARX; 在模块自己的 CMake 里补,不动共享 functions.cmake(中望给 0) - 顶层挂载 add_custom_module_2(cad_mcp_frame CadMcpFrame "SH/Other"), 并加条件:nlohmann/json 与 ghc::filesystem 都需要 C++11+, 而 R180/R190 是 C++98,故只在 (IS_ZWCAD OR CAD_SDK_VERSION >= 20) 时编译 - 中望适配两处:_zrx.def 要导出 zcrxEntryPoint/zcrxGetApiVersion; 代码里显式声明的 acrxGetApiVersion() 不会被 SDK 宏改名, 改为 #ifdef ZWCAD 取 zcrxGetApiVersion() 验证: - R230 Debug 编译通过,cad_mcp_frame.arx 818 KB,自动部署到 c:\ShangHuaSoft\Envi3D-2026-M\R230\ - Z2026 Debug 编译通过,cad_mcp_frame.zrx 818 KB,自动部署到 ...\Z2026\ - R190/R180 按条件跳过(C++98 无法编译 nlohmann/json) - 所有 .cpp/.h 均为 UTF-8 with BOM;cmakelists.txt 保持 ASCII 无 BOM 未做:没有在 CAD 里加载实测;配置文件仍是 cad-mcp-frame.json, 而代码里找的是 mcp_config.json,加载前需要统一(详见 cad_mcp_frame-review.md); 插件工程 cad_mcp_plugins 未移植(18 个工具里 16 个依赖它)。
207 lines
5.8 KiB
C++
207 lines
5.8 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 ®istry)
|
|
{
|
|
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 ®istry)
|
|
{
|
|
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)();
|
|
GetApiVersionFunc getVerFn = (GetApiVersionFunc)GetProcAddress(hMod, "acrxGetApiVersion");
|
|
|
|
// 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
|
|
|
|
|