搭建 mcp server+mcp plugin
This commit is contained in:
gjm committed 2026-10-02 12:39:35 +08:00
1 parent 09f9d69d36
commit 5d6f1c74d1
16 files changed
+792 -229

No files matched your search

+110
View File
@@ -0,0 +1,110 @@
#pragma once
#include <string>
#include <nlohmann/json.hpp>
class mcp_Tool_Utility
{
public:
// 1. 纯文本响应
static nlohmann::json make_text_result(const std::string &text)
{
return {
{ "result", {
{ "content", {
{ { "type", "text" }, { "text", text } }
} }
} }
};
}
// 2. 图片响应 (传入 base64 编码的图片数据)
static nlohmann::json make_image_result(const std::string &base64Data
, const std::string &mimeType = "image/png")
{
return {
{ "result", {
{ "content", {
{ { "type", "image" }, { "data", base64Data }, { "mimeType", mimeType } }
} }
} }
};
}
// 3. 图文混合响应 (文字汇报结果 + 附带 CAD 视口截图)
static nlohmann::json make_mixed_result(const std::string &text
, const std::string &base64Data
, const std::string &mimeType = "image/png")
{
return {
{ "result", {
{ "content", {
{ { "type", "text" }, { "text", text } },
{ { "type", "image" }, { "data", base64Data }, { "mimeType", mimeType } }
} }
} }
};
}
static nlohmann::json make_error(int code, const std::string &message)
{
return {
{ "error",{
{ "code", code },
{ "message", message }
} }
};
}
};
class mcp_Tool
{
protected:
nlohmann::json m_config;
public:
virtual ~mcp_Tool() = default;
void SetConfig(const nlohmann::json &config) { m_config = config; }
// 兜底三剑客:当 JSON 中不写这些字段时,才调用这里(均可选重载)
virtual std::string private_name() const { return "unnamed_tool"; }
virtual std::string private_description() const { return ""; }
virtual nlohmann::json private_input_schema() const { return nlohmann::json::object(); }
// 统一由框架接管 Name 的获取 (供路由字典 mcp_Tools::Register 调用)
virtual std::string GetName() const final { return m_config.value("name", private_name()); }
// 统一由框架接管 ToLLM 组装
virtual nlohmann::json ToLLM() const final
{
nlohmann::json tool;
tool["name"] = GetName();
std::string desc = m_config.value("description", "");
tool["description"] = desc.empty() ? private_description() : desc;
if (m_config.contains("inputSchema") && !m_config["inputSchema"].empty())
{
tool["inputSchema"] = m_config["inputSchema"];
}
else
{
tool["inputSchema"] = private_input_schema();
}
return tool;
}
// 整个类唯一一个纯虚函数:全心全意干活!
virtual nlohmann::json Execute(const nlohmann::json &args) = 0;
};
#define EXPORT_MCP_TOOL(ToolClass, FactoryName) \
extern "C" __declspec(dllexport) mcp_Tool* FactoryName() \
{ \
return new ToolClass(); \
} \
extern "C" __declspec(dllexport) void Destroy_##FactoryName(mcp_Tool* pTool) \
{ \
delete pTool; \
}
+4 -4
View File
@@ -1,7 +1,7 @@
#include "StdAfx.h"
#include "StdAfx.h"
#include "resource.h"
#include "cadsdk.h"
#include "mcp.h"
#include "mcp_server.h"
HINSTANCE _hdllInstance = NULL ;
@@ -50,7 +50,7 @@ extern "C" AcRx::AppRetCode acrxEntryPoint(AcRx::AppMsgCode msg, void* pkt)
void CR_SCGXCmdFnc();
void InitApplication()
{
mcp::init_mcp();
mcp::init_mcp_server();
acrxBuildClassHierarchy();
AddCommand(_T("SHCRACKTOOLS"), _T("CR_SCGX"), _T("CR_SCGX"), ACRX_CMD_TRANSPARENT | ACRX_CMD_USEPICKSET, CR_SCGXCmdFnc);
@@ -60,7 +60,7 @@ void InitApplication()
void UnloadApplication()
{
acedRegCmds->removeGroup(_T("SHCRACKTOOLS"));
mcp::close_mcp();
mcp::close_mcp_server();
}
void AddCommand(const TCHAR* cmdGroup, const TCHAR* cmdInt, const TCHAR* cmdLoc,
+1 -1
View File
@@ -1,4 +1,4 @@
// StdAfx.cpp : source file that includes just the standard includes
// 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
+1 -1
View File
@@ -1,4 +1,4 @@
#pragma once
#pragma once
#if _MSC_VER > 1000
#pragma once
+95
View File
@@ -0,0 +1,95 @@
{
"version": "1.0.0",
"global_settings": {
"plugin_directory": "./mcp_plugins",
"lisp_directory": "./mcp_lisp",
"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"
}
}
]
}
+5 -3
View File
@@ -3,14 +3,15 @@ cmake_minimum_required(VERSION 3.16)
project(AutoAgent)
find_package(nlohmann_json CONFIG REQUIRED)
find_package(CURL CONFIG REQUIRED)
set(SOURCES
"AutoAgent.cpp"
"command.cpp"
"stdafx.cpp"
"AutoAgent.rc"
"mcp.cpp"
"mcp_server.cpp"
"mcp_tool.cpp"
"mcp_ConfigParser.cpp"
)
add_def_file_name(SOURCES "${PROJECT_NAME}")
@@ -24,7 +25,8 @@ 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
CURL::libcurl
)
+1 -1
View File
@@ -1,4 +1,4 @@
#include "StdAfx.h"
#include "StdAfx.h"
void CR_SCGXCmdFnc()
{
-209
View File
@@ -1,209 +0,0 @@
#include "StdAfx.h"
#include "cadsdk.h"
#include <winsock2.h>
#include <ws2tcpip.h>
#include <thread>
#include <atomic>
#include <nlohmann/json.hpp>
#pragma comment(lib, "ws2_32.lib")
#define WM_MCP_EXECUTE_TASK (WM_USER + 2048)
static std::thread g_serverThread;
static std::atomic<bool> g_bRunning(false);
static SOCKET g_listenSocket = INVALID_SOCKET;
static HWND g_hMsgWnd = NULL;
static const wchar_t* g_szMsgClassName = L"AutoAgentMcpMsgWndClass";
// 用于跨线程传递数据的结构体
struct TaskData
{
std::string rawJson;
};
// 此函数在 AutoCAD 主 UI 线程运行,绝对安全.
void ExecuteTaskInMainThread(TaskData *pTask)
{
if (pTask == nullptr)
{
return;
}
try
{
auto j = nlohmann::json::parse(pTask->rawJson);
if (j.contains("cmd") && j["cmd"] == "draw_circle")
{
double x = j.value("x", 0.0);
double y = j.value("y", 0.0);
double r = j.value("r", 100.0);
AcApDocument *pDoc = acDocManager->curDocument();
if (pDoc)
{
// 此时在主线程,安全锁文档
Acad::ErrorStatus es = acDocManager->lockDocument(pDoc, AcAp::kWrite, nullptr, nullptr, false);
if (es == Acad::eOk)
{
AcDbDatabase *pDb = pDoc->database();
AcDbBlockTable *pBlockTable = nullptr;
pDb->getBlockTable(pBlockTable, AcDb::kForRead);
AcDbBlockTableRecord *pBTR = nullptr;
pBlockTable->getAt(ACDB_MODEL_SPACE, pBTR, AcDb::kForWrite);
pBlockTable->close();
AcGePoint3d center(x, y, 0.0);
AcGeVector3d normal(0.0, 0.0, 1.0);
AcDbCircle *pCircle = new AcDbCircle(center, normal, r);
AcDbObjectId circleId;
pBTR->appendAcDbEntity(circleId, pCircle);
pBTR->close();
pCircle->close();
acDocManager->unlockDocument(pDoc);
acutPrintf(_T("\n[MCP Server] 成功在坐标(%.2f, %.2f)绘制了半径为%.2f的圆\n"), x, y, r);
}
}
}
}
catch (...)
{
acutPrintf(_T("\n[MCP Server] JSON 解析或绘图异常!\n"));
}
}
// 隐式窗口的回调函数 (属于主线程)
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 监听后台线程
static 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(8080);
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->rawJson = recvBuf;
// 关键转变:通过 PostMessage 发送给主线程拥有的隐式窗口
if (g_hMsgWnd != NULL)
{
PostMessage(g_hMsgWnd, WM_MCP_EXECUTE_TASK, 0, reinterpret_cast<LPARAM>(pTask));
}
else
{
delete pTask;
}
}
closesocket(clientSocket);
}
}
}
closesocket(g_listenSocket);
WSACleanup();
}
namespace mcp
{
bool init_mcp()
{
// 1. 在主线程注册并创建 Message-Only 窗口
WNDCLASSEX wc = { 0 };
wc.cbSize = sizeof(WNDCLASSEX);
wc.lpfnWndProc = McpWndProc;
wc.hInstance = GetModuleHandle(NULL);
wc.lpszClassName = g_szMsgClassName;
RegisterClassEx(&wc);
g_hMsgWnd = CreateWindowEx(
0,
g_szMsgClassName,
L"AutoAgentMcpMsgWindow",
0, 0, 0, 0, 0,
HWND_MESSAGE, // 纯消息窗口,无界面、无感知
NULL,
GetModuleHandle(NULL),
NULL
);
g_bRunning = true;
try
{
g_serverThread = std::thread(TcpServerLoop);
acutPrintf(_T("\n[MCP Server] 后台 TCP 监听线程已启动 (端口: 8080)...\n"));
}
catch (const std::system_error &e)
{
// 线程创建失败,重置状态标志并输出错误
g_bRunning = false;
// e.what() 返回的是 const char*,所以在 acutPrintf 中要用 %hs 而不是 %s 或 %ls
acutPrintf(_T("\n[MCP Server] 致命错误:无法创建后台监听线程!原因: %hs\n"), e.what());
}
return g_bRunning;
}
bool close_mcp()
{
g_bRunning = false;
if (g_serverThread.joinable())
{
g_serverThread.join();
}
if (g_hMsgWnd != NULL)
{
DestroyWindow(g_hMsgWnd);
UnregisterClass(g_szMsgClassName, GetModuleHandle(NULL));
g_hMsgWnd = NULL;
}
acutPrintf(_T("\n[MCP Server] 插件已卸载,线程已关闭。\n"));
return true;
}
}
-9
View File
@@ -1,9 +0,0 @@
#pragma once
namespace mcp
{
bool init_mcp();
bool close_mcp();
}
+124
View File
@@ -0,0 +1,124 @@
#include "mcp_ConfigParser.h"
#include <windows.h>
#include "mcp_tool.h"
#pragma region mcp_ConfigParser
bool mcp_ConfigParser::LoadAndRegister(const std::string &jsonFilePath, mcp_Tools &registry)
{
std::ifstream ifs(jsonFilePath);
if (!ifs.is_open()) return false;
nlohmann::json config;
try { ifs >> config; }
catch (...) { return false; } // 防御非法 JSON
std::string pluginDir = config["global_settings"].value("plugin_directory", "./mcp_plugins");
std::string lispDir = config["global_settings"].value("lisp_directory", "./mcp_lisp");
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")
{
auto pTool = std::make_shared<mcp_Tool_LispFile>(lispDir);
pTool->SetConfig(toolConfig);
registry.Register(pTool);
}
else if (type == "c++_dll")
{
LoadDllTool(toolConfig, pluginDir, registry);
}
}
return true;
}
void mcp_ConfigParser::LoadDllTool(const nlohmann::json &toolConfig, const std::string &pluginDir, mcp_Tools &registry)
{
std::string dllName = toolConfig["backend"].value("dll_name", "");
std::string factoryName = toolConfig["backend"].value("factory_function", "");
std::string fullPath = pluginDir + "\\" + dllName; // Windows 环境下拼接 DLL 路径
HMODULE hMod = LoadLibraryA(fullPath.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, "未找到指定的 Tool: " + name);
}
#pragma endregion mcp_Tools
+29
View File
@@ -0,0 +1,29 @@
#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);
};
class mcp_ConfigParser
{
public:
static bool LoadAndRegister(const std::string &jsonFilePath, mcp_Tools &registry);
private:
static void LoadDllTool(const nlohmann::json &toolConfig, const std::string &pluginDir, mcp_Tools &registry);
};
+261
View File
@@ -0,0 +1,261 @@
#include "StdAfx.h"
#include "cadsdk.h"
#include <winsock2.h>
#include <ws2tcpip.h>
#include <thread>
#include <atomic>
#include <nlohmann/json.hpp>
#include "mcp_ConfigParser.h"
#pragma comment(lib, "ws2_32.lib")
#define WM_MCP_EXECUTE_TASK (WM_USER + 2048)
extern HINSTANCE _hdllInstance;
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 &params)
{
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(8080);
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 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,
GetModuleHandle(NULL),
NULL
);
g_bRunning = true;
try
{
g_serverThread = std::thread(TcpServerLoop);
acutPrintf(_T("\n[MCP Server] 后台 TCP 监听线程已启动 (端口: 8080)...\n"));
}
catch (const std::system_error &e)
{
// 线程创建失败,重置状态标志并输出错误
g_bRunning = false;
// e.what() 返回的是 const char*,所以在 acutPrintf 中要用 %hs 而不是 %s 或 %ls
acutPrintf(_T("\n[MCP Server] 致命错误:无法创建后台监听线程!原因: %hs\n"), e.what());
}
// 如果监听跑起来了,则注册tools
if (g_bRunning)
{
mcp_ConfigParser::LoadAndRegister("mcp_config.json", g_mcpTools);
}
return g_bRunning;
}
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;
}
acutPrintf(_T("\n[MCP Server] 插件已卸载,线程已关闭。\n"));
return true;
}
}
+7
View File
@@ -0,0 +1,7 @@
#pragma once
namespace mcp
{
bool init_mcp_server();
bool close_mcp_server();
}
+111
View File
@@ -0,0 +1,111 @@
#include "mcp_tool.h"
#include "cadsdk.h"
namespace
{
// UTF-8 转 宽字符 (std::wstring)
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;
}
std::string ReplaceTemplateArgs(std::string tpl, const nlohmann::json &args)
{
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();
}
}
return tpl;
}
}
#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::string finalScript = ReplaceTemplateArgs(tpl, args) + " "; // 尾部加空格,等同于在 CAD 命令行敲回车
std::wstring wScript = Utf8ToWString(finalScript);
// 异步推送到 AutoCAD 命令行执行
acDocManager->sendStringToExecute(acDocManager->curDocument(), wScript.c_str());
return mcp_Tool_Utility::make_text_result("LISP内联指令已下发执行 (异步)");
}
#pragma endregion mcp_Tool_LispInline
#pragma region mcp_Tool_LispFile
nlohmann::json mcp_Tool_LispFile::Execute(const nlohmann::json &args)
{
std::string fileName = m_config["backend"].value("file_name", "");
std::string callTpl = m_config["backend"].value("call_template", "");
// 组装文件完整路径,并将反斜杠 \ 转换为 LISP 安全的正斜杠 /
std::string fullPath = m_lispDir + "/" + fileName;
std::replace(fullPath.begin(), fullPath.end(), '\\', '/');
// 组装前置加载命令,末尾的 nil 是为了防止文件不存在时 LISP 引擎报错弹窗
std::string loadCmd = "(load \"" + fullPath + "\" nil) ";
std::string executeCmd = ReplaceTemplateArgs(callTpl, args) + " ";
std::string finalScript = loadCmd + executeCmd;
std::wstring wScript = Utf8ToWString(finalScript);
acDocManager->sendStringToExecute(acDocManager->curDocument(), wScript.c_str());
return mcp_Tool_Utility::make_text_result("LISP外部文件已加载并下发执行 (异步)");
}
#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, "底层 DLL 工具实例已失效");
}
#pragma endregion mcp_Tool_DllProxy
+42
View File
@@ -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::string m_lispDir;
public:
mcp_Tool_LispFile(const std::string& 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;
};
+1 -1
View File
@@ -1,4 +1,4 @@
//{{NO_DEPENDENCIES}}
//{{NO_DEPENDENCIES}}
// Microsoft Visual C++ generated include file.
// Used by newProject.rc
//