#include "mcp_ConfigParser.h" #include "KTCad.h" #include #include "mcp_tool.h" #include "mcp_utils.h" #include "ghc/filesystem.hpp" #include "rxregsvc.h" namespace { // 加载期不能弹模态框(会卡住 CAD 启动、也让后续工具注册不上),统一写日志 + 命令行输出 void McpLog(const char* pszFmt, ...) { char szBuf[2048]; va_list args; va_start(args, pszFmt); _vsnprintf_s(szBuf, sizeof(szBuf), _TRUNCATE, pszFmt, args); va_end(args); acutPrintf(_T("\n[MCP Server] %hs"), szBuf); char szTmp[MAX_PATH + 1]; ::memset(szTmp, 0, sizeof(szTmp)); if (::GetTempPathA(MAX_PATH, szTmp) == 0) return; std::string sPath(szTmp); sPath += "mcp_plugin_load.log"; FILE* fp = NULL; if (fopen_s(&fp, sPath.c_str(), "a") == 0 && fp != NULL) { fprintf(fp, "%s\n", szBuf); fclose(fp); } } } #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)) McpLog("lisp directory: %hs (exists=%d)", lispPath.string().c_str(), (int)fs::exists(lispPath)); McpLog("plugin directory: %hs (exists=%d)", pluginPath.string().c_str(), (int)fs::exists(pluginPath)); if (!fs::exists(pluginPath)) McpLog("WARNING: 插件目录不存在,c++_dll 工具都不会加载"); for (const auto &toolConfig : config["tools"]) { std::string type = toolConfig["backend"].value("type", ""); if (type == "lisp_inline") { auto pTool = std::make_shared(); pTool->SetConfig(toolConfig); registry.Register(pTool); } else if (type == "lisp_file" && fs::exists(lispPath)) { auto pTool = std::make_shared(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)) { McpLog(" [skip] %hs: dll 不存在 (%hs)", factoryName.c_str(), dllPath.string().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) { McpLog(" [skip] %hs: 版本或导出校验失败", factoryName.c_str()); return; } // 1. 版本校验:读插件导出的数据符号 g_mcpPluginApiVersion(见 mcp_plugin_api.h)。 // 不用 acrxGetApiVersion 函数指针比对——实测通过 GetProcAddress 调它拿到的是垃圾值, // 会把 15/16 个工具误判成"版本不匹配"。 const int nMine = ((ARX) << 16) | (SUB_ARX); const int *pDllVer = (const int *)::GetProcAddress(hMod, "g_mcpPluginApiVersion"); if (pDllVer == NULL) { McpLog(" [warn] %hs: 插件未声明 SDK 版本(旧插件),按兼容处理", factoryName.c_str()); } else if (*pDllVer != nMine) { McpLog(" [skip] %hs: SDK 版本不匹配 (base=0x%x dll=0x%x)", factoryName.c_str(), nMine, *pDllVer); FreeLibrary(hMod); 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()); McpLog(" [try ] %hs: create=%p destroy=%p", factoryName.c_str(), (void*)createFn, (void*)destroyFn); 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(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 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