refactor
修改了目录和部分源文件名称
This commit is contained in:
1 parent
1435b864f8
commit
cf87cfe93d
21 files changed
+8
-7
No files matched your search
@@ -0,0 +1,6 @@
|
||||
// StdAfx.cpp : source file that includes just the standard includes
|
||||
// StdAfx.pch will be the pre-compiled header
|
||||
// StdAfx.obj will contain the pre-compiled type information
|
||||
|
||||
#include "StdAfx.h"
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
#pragma once
|
||||
|
||||
#if _MSC_VER > 1000
|
||||
#pragma once
|
||||
#endif // _MSC_VER > 1000
|
||||
|
||||
#define WINVER 0x0600
|
||||
|
||||
#pragma warning(disable: 4786)
|
||||
|
||||
//- 'DEBUG workaround' below prevents the MFC or ATL #include-s from pulling
|
||||
//- in "Afx.h" that would force the debug CRT through #pragma-s.
|
||||
#if defined(_DEBUG) && !defined(AC_FULL_DEBUG)
|
||||
#define _DEBUG_WAS_DEFINED
|
||||
#undef _DEBUG
|
||||
#pragma message ("Compiling MFC / STL / ATL header files in release mode.")
|
||||
#endif
|
||||
|
||||
#pragma pack (push, 8)
|
||||
#pragma warning(disable: 4786 4996)
|
||||
|
||||
#define VC_EXTRALEAN // Exclude rarely-used stuff from Windows headers
|
||||
|
||||
#include <afxwin.h> // MFC core and standard components
|
||||
#include <afxext.h> // MFC extensions
|
||||
|
||||
#ifndef _AFX_NO_OLE_SUPPORT
|
||||
#include <afxole.h> // MFC OLE classes
|
||||
#include <afxodlgs.h> // MFC OLE dialog classes
|
||||
#include <afxdisp.h> // MFC Automation classes
|
||||
#endif
|
||||
|
||||
#include <afxdtctl.h> // MFC support for Internet Explorer 4 Common Controls
|
||||
#ifndef _AFX_NO_AFXCMN_SUPPORT
|
||||
#include <afxcmn.h> // MFC support for Windows Common Controls
|
||||
#endif
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <afxdhtml.h>
|
||||
#include <afxwin.h>
|
||||
|
||||
extern HINSTANCE _hdllInstance;
|
||||
|
||||
#ifdef _DEBUG_WAS_DEFINED
|
||||
#define _DEBUG
|
||||
#undef _DEBUG_WAS_DEFINED
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
{
|
||||
"version": "1.0.0",
|
||||
"global_settings": {
|
||||
"plugin_directory": "./mcp_plugins",
|
||||
"lisp_directory": "./mcp_plugins",
|
||||
"hot_reload": true
|
||||
},
|
||||
"tools": [
|
||||
{
|
||||
"name": "draw_circle_inline",
|
||||
"description": "在指定的WCS坐标绘制一个圆。适用于简单的标注场景。",
|
||||
"inputSchema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"x": {
|
||||
"type": "number",
|
||||
"description": "圆心X坐标"
|
||||
},
|
||||
"y": {
|
||||
"type": "number",
|
||||
"description": "圆心Y坐标"
|
||||
},
|
||||
"r": {
|
||||
"type": "number",
|
||||
"description": "半径"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"x",
|
||||
"y",
|
||||
"r"
|
||||
]
|
||||
},
|
||||
"backend": {
|
||||
"type": "lisp_inline",
|
||||
"script_template": "(command \"_.CIRCLE\" \"{x},{y}\" \"{r}\")"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "auto_dimension_room",
|
||||
"description": "调用外部复杂的 LISP 脚本,根据指定的房间ID自动生成四周的尺寸标注。",
|
||||
"inputSchema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"room_id": {
|
||||
"type": "string",
|
||||
"description": "房间的标识符,例如 ROOM-101"
|
||||
},
|
||||
"scale": {
|
||||
"type": "number",
|
||||
"description": "标注全局比例因子"
|
||||
},
|
||||
"layer": {
|
||||
"type": "string",
|
||||
"description": "标注存放的图层"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"room_id",
|
||||
"scale"
|
||||
]
|
||||
},
|
||||
"backend": {
|
||||
"type": "lisp_file",
|
||||
"file_name": "auto_dimension.lsp",
|
||||
"call_template": "(mcp-auto-dimension \"{room_id}\" {scale} \"{layer}\")"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "check_interference",
|
||||
"description": "调用 C++ 底层插件,进行3D实体的高性能干涉检查,返回碰撞点坐标数组。",
|
||||
"inputSchema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"tolerance": {
|
||||
"type": "number",
|
||||
"description": "干涉容差值"
|
||||
},
|
||||
"layer_filter": {
|
||||
"type": "string",
|
||||
"description": "需要检查的图层名,支持通配符"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"tolerance"
|
||||
]
|
||||
},
|
||||
"backend": {
|
||||
"type": "c++_dll",
|
||||
"dll_name": "mcp_hardcore_tools.dll",
|
||||
"factory_function": "CreateInterferenceTool"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
#include "StdAfx.h"
|
||||
#include "resource.h"
|
||||
#include "cadsdk.h"
|
||||
#include "mcp_server.h"
|
||||
|
||||
HINSTANCE _hdllInstance = NULL ;
|
||||
|
||||
#ifndef _ttof
|
||||
#define _ttof(pszVal) _tcstod((pszVal), NULL)
|
||||
#endif
|
||||
|
||||
void AddCommand(const TCHAR* cmdGroup, const TCHAR* cmdInt, const TCHAR* cmdLoc,
|
||||
const int cmdFlags, const AcRxFunctionPtr cmdProc, const int idLocal = -1);
|
||||
|
||||
void InitApplication();
|
||||
void UnloadApplication();
|
||||
|
||||
AC_IMPLEMENT_EXTENSION_MODULE(EarthWormDLL);
|
||||
|
||||
extern "C" BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID /*lpReserved*/)
|
||||
{
|
||||
if (dwReason == DLL_PROCESS_ATTACH)
|
||||
{
|
||||
_hdllInstance = hInstance;
|
||||
EarthWormDLL.AttachInstance(hInstance);
|
||||
}
|
||||
else if (dwReason == DLL_PROCESS_DETACH)
|
||||
{
|
||||
EarthWormDLL.DetachInstance();
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
extern "C" AcRx::AppRetCode acrxEntryPoint(AcRx::AppMsgCode msg, void* pkt)
|
||||
{
|
||||
switch (msg)
|
||||
{
|
||||
case AcRx::kInitAppMsg:
|
||||
acrxDynamicLinker->unlockApplication(pkt);
|
||||
acrxDynamicLinker->registerAppMDIAware(pkt);
|
||||
if (!mcp::init_mcp_server())
|
||||
return AcRx::kRetError;
|
||||
InitApplication();
|
||||
break;
|
||||
case AcRx::kUnloadAppMsg:
|
||||
mcp::close_mcp_server();
|
||||
UnloadApplication();
|
||||
break;
|
||||
}
|
||||
return AcRx::kRetOK;
|
||||
}
|
||||
|
||||
void CR_SCGXCmdFnc();
|
||||
void InitApplication()
|
||||
{
|
||||
acrxBuildClassHierarchy();
|
||||
AddCommand(_T("SHCRACKTOOLS"), _T("CR_SCGX"), _T("CR_SCGX"), ACRX_CMD_TRANSPARENT | ACRX_CMD_USEPICKSET, CR_SCGXCmdFnc);
|
||||
|
||||
}
|
||||
|
||||
void UnloadApplication()
|
||||
{
|
||||
acedRegCmds->removeGroup(_T("SHCRACKTOOLS"));
|
||||
}
|
||||
|
||||
void AddCommand(const TCHAR* cmdGroup, const TCHAR* cmdInt, const TCHAR* cmdLoc,
|
||||
const int cmdFlags, const AcRxFunctionPtr cmdProc, const int idLocal)
|
||||
{
|
||||
TCHAR cmdLocRes[65];
|
||||
|
||||
if (idLocal != -1)
|
||||
{
|
||||
::LoadString(_hdllInstance, idLocal, cmdLocRes, 64);
|
||||
acedRegCmds->addCommand(cmdGroup, cmdInt, cmdLocRes, cmdFlags, cmdProc);
|
||||
}
|
||||
else
|
||||
{
|
||||
acedRegCmds->addCommand(cmdGroup, cmdInt, cmdLoc, cmdFlags, cmdProc);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
|
||||
LIBRARY "AutoAgent.arx"
|
||||
|
||||
EXPORTS
|
||||
acrxEntryPoint PRIVATE
|
||||
acrxGetApiVersion PRIVATE
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
// Microsoft Visual C++ generated resource script.
|
||||
//
|
||||
#include "resource.h"
|
||||
|
||||
#define APSTUDIO_READONLY_SYMBOLS
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Generated from the TEXTINCLUDE 2 resource.
|
||||
//
|
||||
#include "afxres.h"
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
#undef APSTUDIO_READONLY_SYMBOLS
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
// Chinese (Simplified, PRC) resources
|
||||
|
||||
#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_CHS)
|
||||
LANGUAGE LANG_CHINESE, SUBLANG_CHINESE_SIMPLIFIED
|
||||
#pragma code_page(936)
|
||||
|
||||
#ifdef APSTUDIO_INVOKED
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// TEXTINCLUDE
|
||||
//
|
||||
|
||||
1 TEXTINCLUDE
|
||||
BEGIN
|
||||
"resource.h\0"
|
||||
END
|
||||
|
||||
2 TEXTINCLUDE
|
||||
BEGIN
|
||||
"#include ""afxres.h""\r\n"
|
||||
"\0"
|
||||
END
|
||||
|
||||
3 TEXTINCLUDE
|
||||
BEGIN
|
||||
"#include ""res\\AutoAgent.rc2"" // non-Microsoft Visual C++ edited resources\r\n"
|
||||
"\0"
|
||||
END
|
||||
|
||||
#endif // APSTUDIO_INVOKED
|
||||
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Version
|
||||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 1,0,0,1
|
||||
PRODUCTVERSION 1,0,0,1
|
||||
FILEFLAGSMASK 0x3fL
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS 0x1L
|
||||
#else
|
||||
FILEFLAGS 0x0L
|
||||
#endif
|
||||
FILEOS 0x4L
|
||||
FILETYPE 0x2L
|
||||
FILESUBTYPE 0x0L
|
||||
BEGIN
|
||||
BLOCK "StringFileInfo"
|
||||
BEGIN
|
||||
BLOCK "040904b0"
|
||||
BEGIN
|
||||
VALUE "FileDescription", "AutoAgent ObjectARX application"
|
||||
VALUE "FileVersion", "1, 0, 0, 1"
|
||||
VALUE "InternalName", "AutoAgent"
|
||||
VALUE "LegalCopyright", "Copyright 2014"
|
||||
VALUE "OriginalFilename", "AutoAgent.arx"
|
||||
VALUE "ProductName", "AutoAgent Application"
|
||||
VALUE "ProductVersion", "1, 0, 0, 1"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
BEGIN
|
||||
VALUE "Translation", 0x409, 1200
|
||||
END
|
||||
END
|
||||
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// String Table
|
||||
//
|
||||
|
||||
STRINGTABLE
|
||||
BEGIN
|
||||
IDS_PROJNAME "AutoAgent"
|
||||
END
|
||||
|
||||
#endif // Chinese (Simplified, PRC) resources
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
|
||||
#ifndef APSTUDIO_INVOKED
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Generated from the TEXTINCLUDE 3 resource.
|
||||
//
|
||||
#include "res\AutoAgent.rc2" // non-Microsoft Visual C++ edited resources
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
#endif // not APSTUDIO_INVOKED
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
|
||||
project(cad_mcp_frame)
|
||||
|
||||
find_package(nlohmann_json CONFIG REQUIRED)
|
||||
find_package(ghc_filesystem CONFIG REQUIRED)
|
||||
|
||||
set(SOURCES
|
||||
"cad_mcp_frame.cpp"
|
||||
"command.cpp"
|
||||
"stdafx.cpp"
|
||||
"cad_mcp_frame.rc"
|
||||
"mcp_server.cpp"
|
||||
"mcp_tool.cpp"
|
||||
"mcp_ConfigParser.cpp"
|
||||
"mcp_utils.cpp"
|
||||
)
|
||||
add_def_file_name(SOURCES "${PROJECT_NAME}")
|
||||
|
||||
add_library(${PROJECT_NAME} SHARED ${SOURCES})
|
||||
|
||||
target_precompile_headers(${PROJECT_NAME} PRIVATE StdAfx.h)
|
||||
|
||||
target_link_objectarx(${PROJECT_NAME} AECRXBASE_EXPORT)
|
||||
|
||||
target_set_name_and_suffix(${PROJECT_NAME} "${PROJECT_NAME}" ".arx")
|
||||
|
||||
target_compile_options(${PROJECT_NAME} PRIVATE /Zc:forScope)
|
||||
|
||||
target_include_directories(${PROJECT_NAME} INTERFACE "${CMAKE_SOURCE_DIR}/inc")
|
||||
|
||||
target_link_libraries(${PROJECT_NAME} PRIVATE
|
||||
nlohmann_json::nlohmann_json
|
||||
)
|
||||
@@ -0,0 +1,5 @@
|
||||
#include "StdAfx.h"
|
||||
|
||||
void CR_SCGXCmdFnc()
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
#include "mcp_ConfigParser.h"
|
||||
#include "cadsdk.h"
|
||||
#include <windows.h>
|
||||
#include "mcp_tool.h"
|
||||
#include "mcp_utils.h"
|
||||
#include "ghc/filesystem.hpp"
|
||||
|
||||
#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;
|
||||
}
|
||||
|
||||
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());
|
||||
if (!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());
|
||||
|
||||
if (createFn && destroyFn)
|
||||
{
|
||||
mcp_Tool *pRealTool = createFn();
|
||||
if (pRealTool)
|
||||
{
|
||||
// 给真正的 DLL 实例也注入配置,以防第三方需要在内部读取
|
||||
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, "Specified tool not found: " + 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
|
||||
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
#pragma once
|
||||
#include <fstream>
|
||||
#include <unordered_map>
|
||||
#include "mcp_plugin_api.h"
|
||||
|
||||
class mcp_Tools
|
||||
{
|
||||
private:
|
||||
std::unordered_map<std::string, std::shared_ptr<mcp_Tool>> m_tools;
|
||||
|
||||
public:
|
||||
// 注册工具
|
||||
void Register(std::shared_ptr<mcp_Tool> pTool);
|
||||
|
||||
// 组装所有工具的描述,返回 JSON 数组 (用于 tools/list)
|
||||
nlohmann::json ToLLM() const;
|
||||
|
||||
// 路由分发执行指令 (用于 tools/call)
|
||||
nlohmann::json Call(const std::string &name, const nlohmann::json &args);
|
||||
|
||||
void print();
|
||||
|
||||
void clear();
|
||||
};
|
||||
|
||||
class mcp_ConfigParser
|
||||
{
|
||||
public:
|
||||
static bool LoadAndRegister(const std::wstring &jsonFilePath, mcp_Tools ®istry);
|
||||
|
||||
private:
|
||||
static void LoadDllTool(const nlohmann::json &toolConfig, const std::wstring &pluginDir, mcp_Tools ®istry);
|
||||
};
|
||||
@@ -0,0 +1,273 @@
|
||||
#include "StdAfx.h"
|
||||
#include "cadsdk.h"
|
||||
#include <winsock2.h>
|
||||
#include <ws2tcpip.h>
|
||||
#include <thread>
|
||||
#include <atomic>
|
||||
#include <nlohmann/json.hpp>
|
||||
#include "mcp_ConfigParser.h"
|
||||
#include "mcp_utils.h"
|
||||
|
||||
#pragma comment(lib, "ws2_32.lib")
|
||||
#define WM_MCP_EXECUTE_TASK (WM_USER + 2048)
|
||||
extern HINSTANCE _hdllInstance;
|
||||
|
||||
#define MCP_SERVER_PORT 8080
|
||||
#define MCP_SERVER_CONFIG _T("mcp_config.json")
|
||||
|
||||
namespace
|
||||
{
|
||||
std::thread g_serverThread;
|
||||
std::atomic<bool> g_bRunning(false);
|
||||
SOCKET g_listenSocket = INVALID_SOCKET;
|
||||
|
||||
HWND g_hMsgWnd = NULL;
|
||||
const wchar_t *g_szMsgClassName = L"AutoAgentMcpMsgWndClass";
|
||||
|
||||
// 声明一个全局工具集对象
|
||||
mcp_Tools g_mcpTools;
|
||||
|
||||
// 用于跨线程传递数据的结构体
|
||||
struct TaskData
|
||||
{
|
||||
~TaskData()
|
||||
{
|
||||
if (clientSocket != INVALID_SOCKET)
|
||||
{
|
||||
::closesocket(clientSocket);
|
||||
clientSocket = INVALID_SOCKET;
|
||||
}
|
||||
}
|
||||
SOCKET clientSocket = INVALID_SOCKET;
|
||||
std::string rawJson;
|
||||
};
|
||||
|
||||
// 处理工具查询请求
|
||||
std::string HandleToolsList(int msgId)
|
||||
{
|
||||
nlohmann::json resp;
|
||||
resp["jsonrpc"] = "2.0";
|
||||
resp["id"] = msgId;
|
||||
|
||||
// 直接向全局工具集要数据
|
||||
resp["result"]["tools"] = g_mcpTools.ToLLM();
|
||||
|
||||
return resp.dump();
|
||||
}
|
||||
|
||||
// 处理工具调用请求
|
||||
std::string HandleToolsCall(int msgId, const nlohmann::json ¶ms)
|
||||
{
|
||||
nlohmann::json resp;
|
||||
resp["jsonrpc"] = "2.0";
|
||||
resp["id"] = msgId;
|
||||
|
||||
std::string toolName = params.value("name", "");
|
||||
nlohmann::json args = params.value("arguments", nlohmann::json::object());
|
||||
|
||||
// 让工具集去执行,返回 payload 可能是 {"result": ...} 也可能是 {"error": ...}
|
||||
nlohmann::json payload = g_mcpTools.Call(toolName, args);
|
||||
|
||||
// 将 payload 注入到外层信封中
|
||||
resp.update(payload);
|
||||
|
||||
return resp.dump();
|
||||
}
|
||||
|
||||
// 此函数在 AutoCAD 主 UI 线程运行,绝对安全.
|
||||
void ExecuteTaskInMainThread(TaskData *pTask)
|
||||
{
|
||||
if (pTask == nullptr) return;
|
||||
|
||||
std::string responseStr;
|
||||
|
||||
// 关闭 nlohmann 的异常抛出机制,安全解析
|
||||
nlohmann::json req = nlohmann::json::parse(pTask->rawJson, nullptr, false);
|
||||
|
||||
if (req.is_discarded())
|
||||
{
|
||||
// 非法 JSON 报文
|
||||
responseStr = R"({"jsonrpc": "2.0", "error": {"code": -32700, "message": "Parse error"}, "id": null})";
|
||||
}
|
||||
else
|
||||
{
|
||||
std::string method = req.value("method", "");
|
||||
int msgId = req.value("id", 0);
|
||||
|
||||
if (method == "tools/list")
|
||||
{
|
||||
responseStr = HandleToolsList(msgId);
|
||||
}
|
||||
else if (method == "tools/call")
|
||||
{
|
||||
responseStr = HandleToolsCall(msgId, req.value("params", nlohmann::json::object()));
|
||||
}
|
||||
else
|
||||
{
|
||||
// 找不到方法
|
||||
nlohmann::json err;
|
||||
err["jsonrpc"] = "2.0";
|
||||
err["id"] = msgId;
|
||||
err["error"] = { {"code", -32601}, {"message", "Method not found"} };
|
||||
responseStr = err.dump();
|
||||
}
|
||||
}
|
||||
|
||||
// 通过传递过来的 Socket 将结果发送回大模型端
|
||||
if (pTask->clientSocket != INVALID_SOCKET)
|
||||
{
|
||||
send(pTask->clientSocket, responseStr.c_str(), responseStr.length(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
// 隐式窗口的回调函数 (属于主线程)
|
||||
LRESULT CALLBACK McpWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
|
||||
{
|
||||
if (message == WM_MCP_EXECUTE_TASK)
|
||||
{
|
||||
TaskData *pTask = reinterpret_cast<TaskData *>(lParam);
|
||||
if (pTask)
|
||||
{
|
||||
ExecuteTaskInMainThread(pTask);
|
||||
delete pTask; // 执行完毕,清理内存
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
return DefWindowProc(hWnd, message, wParam, lParam);
|
||||
}
|
||||
|
||||
// TCP 监听后台线程
|
||||
void TcpServerLoop()
|
||||
{
|
||||
WSADATA wsaData;
|
||||
WSAStartup(MAKEWORD(2, 2), &wsaData);
|
||||
|
||||
g_listenSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
|
||||
sockaddr_in serverAddr = { 0 };
|
||||
serverAddr.sin_family = AF_INET;
|
||||
inet_pton(AF_INET, "127.0.0.1", &serverAddr.sin_addr);
|
||||
serverAddr.sin_port = htons(MCP_SERVER_PORT);
|
||||
|
||||
bind(g_listenSocket, (sockaddr *)&serverAddr, sizeof(serverAddr));
|
||||
listen(g_listenSocket, SOMAXCONN);
|
||||
|
||||
while (g_bRunning)
|
||||
{
|
||||
// 使用 select 模型实现带超时的轮询,确保插件卸载时线程能正常退出
|
||||
fd_set readSet;
|
||||
FD_ZERO(&readSet);
|
||||
FD_SET(g_listenSocket, &readSet);
|
||||
|
||||
timeval timeout = { 1, 0 }; // 1秒超时机制
|
||||
int ret = select(0, &readSet, nullptr, nullptr, &timeout);
|
||||
|
||||
if (ret > 0 && FD_ISSET(g_listenSocket, &readSet))
|
||||
{
|
||||
SOCKET clientSocket = accept(g_listenSocket, nullptr, nullptr);
|
||||
if (clientSocket != INVALID_SOCKET)
|
||||
{
|
||||
char recvBuf[2048] = { 0 };
|
||||
int bytesReceived = recv(clientSocket, recvBuf, sizeof(recvBuf) - 1, 0);
|
||||
if (bytesReceived > 0)
|
||||
{
|
||||
// 将数据拷贝到堆内存,准备派发给主线程
|
||||
TaskData *pTask = new TaskData;
|
||||
pTask->clientSocket = clientSocket; // 记录 Socket
|
||||
pTask->rawJson = recvBuf;
|
||||
|
||||
// 关键转变:通过 PostMessage 发送给主线程拥有的隐式窗口
|
||||
if (g_hMsgWnd != NULL)
|
||||
{
|
||||
PostMessage(g_hMsgWnd, WM_MCP_EXECUTE_TASK, 0, reinterpret_cast<LPARAM>(pTask));
|
||||
}
|
||||
else
|
||||
{
|
||||
delete pTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
closesocket(clientSocket);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
closesocket(g_listenSocket);
|
||||
WSACleanup();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
namespace mcp
|
||||
{
|
||||
bool close_mcp_server()
|
||||
{
|
||||
g_bRunning = false;
|
||||
if (g_serverThread.joinable())
|
||||
{
|
||||
g_serverThread.join();
|
||||
}
|
||||
|
||||
if (g_hMsgWnd != NULL)
|
||||
{
|
||||
DestroyWindow(g_hMsgWnd);
|
||||
UnregisterClass(g_szMsgClassName, _hdllInstance);
|
||||
g_hMsgWnd = NULL;
|
||||
}
|
||||
|
||||
g_mcpTools.clear();
|
||||
|
||||
acutPrintf(_T("\n[MCP Server] The plugin has been uninstalled, and the thread has been closed.\n"));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool init_mcp_server()
|
||||
{
|
||||
// 1. 在主线程注册并创建 Message-Only 窗口
|
||||
WNDCLASSEX wc = { 0 };
|
||||
wc.cbSize = sizeof(WNDCLASSEX);
|
||||
wc.lpfnWndProc = McpWndProc;
|
||||
wc.hInstance = _hdllInstance;
|
||||
wc.lpszClassName = g_szMsgClassName;
|
||||
RegisterClassEx(&wc);
|
||||
|
||||
g_hMsgWnd = CreateWindowEx(
|
||||
0,
|
||||
g_szMsgClassName,
|
||||
L"AutoAgentMcpMsgWindow",
|
||||
0, 0, 0, 0, 0,
|
||||
HWND_MESSAGE, // 纯消息窗口,无界面、无感知
|
||||
NULL,
|
||||
_hdllInstance,
|
||||
NULL
|
||||
);
|
||||
g_bRunning = true;
|
||||
try
|
||||
{
|
||||
g_serverThread = std::thread(TcpServerLoop);
|
||||
acutPrintf(_T("\n[MCP Server] Background TCP listening thread started (Port: 8080)...\n"));
|
||||
}
|
||||
catch (const std::system_error &e)
|
||||
{
|
||||
// 线程创建失败,重置状态标志并输出错误
|
||||
g_bRunning = false;
|
||||
// e.what() 返回的是 const char*,所以在 acutPrintf 中要用 %hs 而不是 %s 或 %ls
|
||||
acutPrintf(_T("\n[MCP Server] Fatal error: Unable to create background listening thread! Reason: %hs\n"), e.what());
|
||||
}
|
||||
|
||||
// 如果监听跑起来了,则注册tools
|
||||
if (g_bRunning)
|
||||
{
|
||||
if (!mcp_ConfigParser::LoadAndRegister(mcp_utils::getModelDirPath() / MCP_SERVER_CONFIG, g_mcpTools))
|
||||
{
|
||||
acutPrintf(_T("\n[MCP Server] Warning: Failed to load tool configuration!\n[MCP Server] Automatic Shutdown.\n"));
|
||||
g_bRunning = false;
|
||||
}
|
||||
|
||||
g_mcpTools.print();
|
||||
}
|
||||
|
||||
return g_bRunning;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
namespace mcp
|
||||
{
|
||||
bool init_mcp_server();
|
||||
bool close_mcp_server();
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
#include "cadsdk.h"
|
||||
#include "mcp_tool.h"
|
||||
#include "mcp_utils.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
bool ReplaceTemplateArgs(const std::string &_tpl, const nlohmann::json &args, std::wstring &outScript)
|
||||
{
|
||||
if (_tpl.empty()) return false;
|
||||
|
||||
std::string tpl(_tpl);
|
||||
for (auto it = args.begin(); it != args.end(); ++it)
|
||||
{
|
||||
std::string placeholder = "{" + it.key() + "}";
|
||||
std::string valueStr;
|
||||
|
||||
if (it.value().is_string())
|
||||
{
|
||||
valueStr = it.value().get<std::string>();
|
||||
}
|
||||
else if (it.value().is_boolean())
|
||||
{
|
||||
valueStr = it.value().get<bool>() ? "T" : "nil"; // 适配 AutoLISP 的布尔逻辑
|
||||
}
|
||||
else
|
||||
{
|
||||
valueStr = it.value().dump(); // 数字类型直接转字符串
|
||||
}
|
||||
|
||||
size_t pos = 0;
|
||||
while ((pos = tpl.find(placeholder, pos)) != std::string::npos)
|
||||
{
|
||||
tpl.replace(pos, placeholder.length(), valueStr);
|
||||
pos += valueStr.length();
|
||||
}
|
||||
}
|
||||
outScript = mcp_utils::utf8ToWString(tpl);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
#pragma region mcp_Tool_LispInline
|
||||
nlohmann::json mcp_Tool_LispInline::Execute(const nlohmann::json &args)
|
||||
{
|
||||
std::string tpl = m_config["backend"].value("script_template", "");
|
||||
std::wstring finalScript;
|
||||
|
||||
if (!ReplaceTemplateArgs(tpl, args, finalScript))
|
||||
return mcp_Tool_Utility::make_error(-32603, "Script template replacement failed.");
|
||||
|
||||
finalScript += _T(" "); // 尾部加空格,等同于在 CAD 命令行敲回车
|
||||
|
||||
// 异步推送到 AutoCAD 命令行执行
|
||||
acDocManager->sendStringToExecute(acDocManager->curDocument(), finalScript.c_str());
|
||||
|
||||
return mcp_Tool_Utility::make_text_result("LISP inline command issued for execution (asynchronous)");
|
||||
}
|
||||
#pragma endregion mcp_Tool_LispInline
|
||||
|
||||
#pragma region mcp_Tool_LispFile
|
||||
nlohmann::json mcp_Tool_LispFile::Execute(const nlohmann::json &args)
|
||||
{
|
||||
fs::path filePath
|
||||
= mcp_utils::relative2absolute(m_config["backend"].value("file_name", "")
|
||||
, fs::path(m_lispDir));
|
||||
|
||||
if (!fs::exists(filePath))
|
||||
{
|
||||
return mcp_Tool_Utility::make_error(-32603, "LISP file does not exist: " + filePath.string());
|
||||
}
|
||||
|
||||
std::string callTpl = m_config["backend"].value("call_template", "");
|
||||
|
||||
// 组装前置加载命令,末尾的 nil 是为了防止文件不存在时 LISP 引擎报错弹窗
|
||||
std::wstring loadCmd = std::wstring(_T("(load \"")) + filePath.generic_wstring() + _T("\" nil) ");
|
||||
std::wstring executeCmd;
|
||||
if (!ReplaceTemplateArgs(callTpl, args, executeCmd))
|
||||
{
|
||||
return mcp_Tool_Utility::make_error(-32603, "Script template replacement failed.");
|
||||
}
|
||||
executeCmd += _T(" ");
|
||||
|
||||
std::wstring finalScript = loadCmd + executeCmd;
|
||||
|
||||
acDocManager->sendStringToExecute(acDocManager->curDocument(), finalScript.c_str());
|
||||
|
||||
return mcp_Tool_Utility::make_text_result("LISP external file loaded and issued for execution (asynchronous)");
|
||||
}
|
||||
#pragma endregion mcp_Tool_LispFile
|
||||
|
||||
#pragma region mcp_Tool_DllProxy
|
||||
// RAII:基座销毁工具时,自动通知 DLL 释放内存,并卸载模块
|
||||
mcp_Tool_DllProxy::~mcp_Tool_DllProxy()
|
||||
{
|
||||
if (m_pRealTool && m_destroyFunc)
|
||||
{
|
||||
m_destroyFunc(m_pRealTool);
|
||||
}
|
||||
if (m_hModule)
|
||||
{
|
||||
FreeLibrary(m_hModule);
|
||||
}
|
||||
}
|
||||
|
||||
nlohmann::json mcp_Tool_DllProxy::Execute(const nlohmann::json &args)
|
||||
{
|
||||
if (m_pRealTool)
|
||||
{
|
||||
return m_pRealTool->Execute(args); // 完美转发
|
||||
}
|
||||
return mcp_Tool_Utility::make_error(-32603, "Underlying DLL tool instance is invalid");
|
||||
}
|
||||
#pragma endregion mcp_Tool_DllProxy
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
#pragma once
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include <nlohmann/json.hpp>
|
||||
#include "mcp_plugin_api.h"
|
||||
|
||||
class mcp_Tool_LispInline : public mcp_Tool
|
||||
{
|
||||
public:
|
||||
virtual nlohmann::json Execute(const nlohmann::json &args) override;
|
||||
};
|
||||
|
||||
class mcp_Tool_LispFile : public mcp_Tool
|
||||
{
|
||||
private:
|
||||
std::wstring m_lispDir;
|
||||
|
||||
public:
|
||||
mcp_Tool_LispFile(const std::wstring& lispDir) : m_lispDir(lispDir) {}
|
||||
|
||||
virtual nlohmann::json Execute(const nlohmann::json &args) override;
|
||||
};
|
||||
|
||||
class mcp_Tool_DllProxy : public mcp_Tool
|
||||
{
|
||||
private:
|
||||
HMODULE m_hModule;
|
||||
mcp_Tool* m_pRealTool; // 真正由第三方 DLL 创建的工具对象
|
||||
|
||||
typedef void (*DestroyFunc)(mcp_Tool*);
|
||||
DestroyFunc m_destroyFunc;
|
||||
|
||||
public:
|
||||
mcp_Tool_DllProxy(HMODULE hMod, mcp_Tool* pReal, DestroyFunc pDestroy)
|
||||
: m_hModule(hMod), m_pRealTool(pReal), m_destroyFunc(pDestroy) {}
|
||||
|
||||
// RAII:基座销毁工具时,自动通知 DLL 释放内存,并卸载模块
|
||||
virtual ~mcp_Tool_DllProxy();
|
||||
|
||||
virtual nlohmann::json Execute(const nlohmann::json &args) override;
|
||||
};
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
#pragma once
|
||||
#include "mcp_utils.h"
|
||||
#include <Windows.h>
|
||||
#include <tchar.h>
|
||||
#include <ghc/filesystem.hpp>
|
||||
|
||||
namespace mcp_utils
|
||||
{
|
||||
std::wstring utf8ToWString(const std::string &utf8Str)
|
||||
{
|
||||
if (utf8Str.empty()) return std::wstring();
|
||||
|
||||
// 第一次调用获取需要的宽字符缓冲区大小
|
||||
int sizeNeeded = ::MultiByteToWideChar(CP_UTF8, 0, &utf8Str[0], (int)utf8Str.size(), NULL, 0);
|
||||
std::wstring wstrTo(sizeNeeded, 0);
|
||||
|
||||
// 第二次调用真正进行转换
|
||||
::MultiByteToWideChar(CP_UTF8, 0, &utf8Str[0], (int)utf8Str.size(), &wstrTo[0], sizeNeeded);
|
||||
return wstrTo;
|
||||
}
|
||||
|
||||
bool mcp_utils::hasFileExist(const std::wstring &filePath) { return _taccess(filePath.c_str(), 0) == 0; }
|
||||
|
||||
bool mcp_utils::hasDirExist(const std::wstring &filePath) { return _taccess(filePath.c_str(), 0) == 0; }
|
||||
|
||||
std::wstring getModelDir()
|
||||
{
|
||||
TCHAR szPath[MAX_PATH];
|
||||
::GetModuleFileName(_hdllInstance, szPath, MAX_PATH);
|
||||
ghc::filesystem::path path(szPath);
|
||||
return path.parent_path().wstring();
|
||||
}
|
||||
|
||||
fs::path getModelDirPath()
|
||||
{
|
||||
return fs::path(getModelDir());
|
||||
}
|
||||
|
||||
fs::path relative2absolute(const std::string &relativePath, const fs::path &rootPath)
|
||||
{
|
||||
return relative2absolute(fs::path(utf8ToWString(relativePath)), rootPath);
|
||||
}
|
||||
|
||||
fs::path relative2absolute(const fs::path &relativePath, const fs::path &rootPath)
|
||||
{
|
||||
fs::path absolutePath = rootPath / relativePath;
|
||||
return absolutePath.lexically_normal().wstring();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
#pragma once
|
||||
#include "ghc/filesystem.hpp"
|
||||
|
||||
namespace fs = ghc::filesystem;
|
||||
|
||||
namespace mcp_utils
|
||||
{
|
||||
std::wstring utf8ToWString(const std::string &utf8Str);
|
||||
|
||||
bool hasFileExist(const std::wstring &filePath);
|
||||
bool hasDirExist(const std::wstring &filePath);
|
||||
|
||||
std::wstring getModelDir();
|
||||
|
||||
ghc::filesystem::path getModelDirPath();
|
||||
|
||||
fs::path relative2absolute(const std::string &relativePath
|
||||
, const fs::path &rootPath = getModelDirPath());
|
||||
|
||||
fs::path relative2absolute(const fs::path &relativePath
|
||||
, const fs::path &rootPath = getModelDirPath());
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
//
|
||||
// AutoAgent.RC2 - resources Microsoft Visual C++ does not edit directly
|
||||
//
|
||||
|
||||
#ifdef APSTUDIO_INVOKED
|
||||
#error this file is not editable by Microsoft Visual C++
|
||||
#endif //APSTUDIO_INVOKED
|
||||
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
// Add manually edited resources here...
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
@@ -0,0 +1,16 @@
|
||||
//{{NO_DEPENDENCIES}}
|
||||
// Microsoft Visual C++ generated include file.
|
||||
// Used by newProject.rc
|
||||
//
|
||||
#define IDS_PROJNAME 100
|
||||
|
||||
// Next default values for new objects
|
||||
//
|
||||
#ifdef APSTUDIO_INVOKED
|
||||
#ifndef APSTUDIO_READONLY_SYMBOLS
|
||||
#define _APS_NEXT_RESOURCE_VALUE 426
|
||||
#define _APS_NEXT_COMMAND_VALUE 32768
|
||||
#define _APS_NEXT_CONTROL_VALUE 438
|
||||
#define _APS_NEXT_SYMED_VALUE 122
|
||||
#endif
|
||||
#endif
|
||||
Reference in new issue
Block a user