重构 mcp-server 功能
This commit is contained in:
gjm committed 2026-10-02 20:39:34 +08:00
1 parent 5d6f1c74d1
commit 1435b864f8
12 files changed
+229 -92

No files matched your search

+3 -3
View File
@@ -38,9 +38,12 @@ extern "C" AcRx::AppRetCode acrxEntryPoint(AcRx::AppMsgCode msg, void* pkt)
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;
}
@@ -50,8 +53,6 @@ extern "C" AcRx::AppRetCode acrxEntryPoint(AcRx::AppMsgCode msg, void* pkt)
void CR_SCGXCmdFnc();
void InitApplication()
{
mcp::init_mcp_server();
acrxBuildClassHierarchy();
AddCommand(_T("SHCRACKTOOLS"), _T("CR_SCGX"), _T("CR_SCGX"), ACRX_CMD_TRANSPARENT | ACRX_CMD_USEPICKSET, CR_SCGXCmdFnc);
@@ -60,7 +61,6 @@ void InitApplication()
void UnloadApplication()
{
acedRegCmds->removeGroup(_T("SHCRACKTOOLS"));
mcp::close_mcp_server();
}
void AddCommand(const TCHAR* cmdGroup, const TCHAR* cmdInt, const TCHAR* cmdLoc,
+1 -1
View File
@@ -2,7 +2,7 @@
"version": "1.0.0",
"global_settings": {
"plugin_directory": "./mcp_plugins",
"lisp_directory": "./mcp_lisp",
"lisp_directory": "./mcp_plugins",
"hot_reload": true
},
"tools": [
+2
View File
@@ -3,6 +3,7 @@ cmake_minimum_required(VERSION 3.16)
project(AutoAgent)
find_package(nlohmann_json CONFIG REQUIRED)
find_package(ghc_filesystem CONFIG REQUIRED)
set(SOURCES
"AutoAgent.cpp"
@@ -12,6 +13,7 @@ set(SOURCES
"mcp_server.cpp"
"mcp_tool.cpp"
"mcp_ConfigParser.cpp"
"mcp_utils.cpp"
)
add_def_file_name(SOURCES "${PROJECT_NAME}")
+43 -13
View File
@@ -1,9 +1,12 @@
#include "mcp_ConfigParser.h"
#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::string &jsonFilePath, mcp_Tools &registry)
bool mcp_ConfigParser::LoadAndRegister(const std::wstring &jsonFilePath, mcp_Tools &registry)
{
std::ifstream ifs(jsonFilePath);
if (!ifs.is_open()) return false;
@@ -12,8 +15,15 @@ bool mcp_ConfigParser::LoadAndRegister(const std::string &jsonFilePath, mcp_Tool
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");
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"])
{
@@ -25,28 +35,35 @@ bool mcp_ConfigParser::LoadAndRegister(const std::string &jsonFilePath, mcp_Tool
pTool->SetConfig(toolConfig);
registry.Register(pTool);
}
else if (type == "lisp_file")
else if (type == "lisp_file" && fs::exists(lispPath))
{
auto pTool = std::make_shared<mcp_Tool_LispFile>(lispDir);
auto pTool = std::make_shared<mcp_Tool_LispFile>(lispPath);
pTool->SetConfig(toolConfig);
registry.Register(pTool);
}
else if (type == "c++_dll")
else if (type == "c++_dll" && fs::exists(pluginPath))
{
LoadDllTool(toolConfig, pluginDir, registry);
LoadDllTool(toolConfig, pluginPath, registry);
}
}
return true;
}
void mcp_ConfigParser::LoadDllTool(const nlohmann::json &toolConfig, const std::string &pluginDir, mcp_Tools &registry)
void mcp_ConfigParser::LoadDllTool(const nlohmann::json &toolConfig, const std::wstring &pluginDir, mcp_Tools &registry)
{
std::string dllName = toolConfig["backend"].value("dll_name", "");
fs::path dllPath
= mcp_utils::relative2absolute(toolConfig["backend"].value("dll_name", "")
, fs::path(pluginDir));
std::string factoryName = toolConfig["backend"].value("factory_function", "");
std::string fullPath = pluginDir + "\\" + dllName; // Windows 环境下拼接 DLL 路径
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 = LoadLibraryA(fullPath.c_str());
HMODULE hMod = LoadLibrary(dllPath.c_str());
if (!hMod) return;
typedef mcp_Tool *(*CreateFunc)();
@@ -117,7 +134,20 @@ nlohmann::json mcp_Tools::Call(const std::string &name, const nlohmann::json &ar
}
// 找不到工具时,返回标准的 JSON-RPC 错误对象
return mcp_Tool_Utility::make_error(-32601, "未找到指定的 Tool: " + name);
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
+6 -2
View File
@@ -17,13 +17,17 @@ public:
// 路由分发执行指令 (用于 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::string &jsonFilePath, mcp_Tools &registry);
static bool LoadAndRegister(const std::wstring &jsonFilePath, mcp_Tools &registry);
private:
static void LoadDllTool(const nlohmann::json &toolConfig, const std::string &pluginDir, mcp_Tools &registry);
static void LoadDllTool(const nlohmann::json &toolConfig, const std::wstring &pluginDir, mcp_Tools &registry);
};
+36 -24
View File
@@ -6,11 +6,15 @@
#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;
@@ -142,7 +146,7 @@ namespace
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);
serverAddr.sin_port = htons(MCP_SERVER_PORT);
bind(g_listenSocket, (sockaddr *)&serverAddr, sizeof(serverAddr));
listen(g_listenSocket, SOMAXCONN);
@@ -197,6 +201,27 @@ namespace
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 窗口
@@ -214,48 +239,35 @@ namespace mcp
0, 0, 0, 0, 0,
HWND_MESSAGE, // 纯消息窗口,无界面、无感知
NULL,
GetModuleHandle(NULL),
_hdllInstance,
NULL
);
g_bRunning = true;
try
{
g_serverThread = std::thread(TcpServerLoop);
acutPrintf(_T("\n[MCP Server] 后台 TCP 监听线程已启动 (端口: 8080)...\n"));
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] 致命错误:无法创建后台监听线程!原因: %hs\n"), e.what());
acutPrintf(_T("\n[MCP Server] Fatal error: Unable to create background listening thread! Reason: %hs\n"), e.what());
}
// 如果监听跑起来了,则注册tools
if (g_bRunning)
{
mcp_ConfigParser::LoadAndRegister("mcp_config.json", g_mcpTools);
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;
}
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;
}
}
+36 -33
View File
@@ -1,24 +1,14 @@
#include "mcp_tool.h"
#include "cadsdk.h"
#include "cadsdk.h"
#include "mcp_tool.h"
#include "mcp_utils.h"
namespace
{
// UTF-8 转 宽字符 (std::wstring)
std::wstring Utf8ToWString(const std::string &utf8Str)
bool ReplaceTemplateArgs(const std::string &_tpl, const nlohmann::json &args, std::wstring &outScript)
{
if (utf8Str.empty()) return std::wstring();
if (_tpl.empty()) return false;
// 第一次调用获取需要的宽字符缓冲区大小
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)
{
std::string tpl(_tpl);
for (auto it = args.begin(); it != args.end(); ++it)
{
std::string placeholder = "{" + it.key() + "}";
@@ -44,7 +34,8 @@ namespace
pos += valueStr.length();
}
}
return tpl;
outScript = mcp_utils::utf8ToWString(tpl);
return true;
}
}
@@ -52,36 +43,48 @@ namespace
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);
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(), wScript.c_str());
acDocManager->sendStringToExecute(acDocManager->curDocument(), finalScript.c_str());
return mcp_Tool_Utility::make_text_result("LISP内联指令已下发执行 (异步)");
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)
{
std::string fileName = m_config["backend"].value("file_name", "");
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", "");
// 组装文件完整路径,并将反斜杠 \ 转换为 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::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::string finalScript = loadCmd + executeCmd;
std::wstring wScript = Utf8ToWString(finalScript);
std::wstring finalScript = loadCmd + executeCmd;
acDocManager->sendStringToExecute(acDocManager->curDocument(), wScript.c_str());
acDocManager->sendStringToExecute(acDocManager->curDocument(), finalScript.c_str());
return mcp_Tool_Utility::make_text_result("LISP外部文件已加载并下发执行 (异步)");
return mcp_Tool_Utility::make_text_result("LISP external file loaded and issued for execution (asynchronous)");
}
#pragma endregion mcp_Tool_LispFile
@@ -105,7 +108,7 @@ nlohmann::json mcp_Tool_DllProxy::Execute(const nlohmann::json &args)
{
return m_pRealTool->Execute(args); // 完美转发
}
return mcp_Tool_Utility::make_error(-32603, "底层 DLL 工具实例已失效");
return mcp_Tool_Utility::make_error(-32603, "Underlying DLL tool instance is invalid");
}
#pragma endregion mcp_Tool_DllProxy
+2 -2
View File
@@ -13,10 +13,10 @@ public:
class mcp_Tool_LispFile : public mcp_Tool
{
private:
std::string m_lispDir;
std::wstring m_lispDir;
public:
mcp_Tool_LispFile(const std::string& lispDir) : m_lispDir(lispDir) {}
mcp_Tool_LispFile(const std::wstring& lispDir) : m_lispDir(lispDir) {}
virtual nlohmann::json Execute(const nlohmann::json &args) override;
};
+49
View File
@@ -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();
}
}
+22
View File
@@ -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());
}
+27 -13
View File
@@ -1,20 +1,34 @@
import socket
import json
# 模拟大模型生成的 JSON 指令
command = {
"cmd": "draw_circle",
"x": 500.0,
"y": 500.0,
"r": 250.0
}
try:
def send_request(req_data):
try:
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.connect(("127.0.0.1", 8080))
# 发送序列化后的 JSON 字符串
client.sendall(json.dumps(command).encode('utf-8'))
client.sendall(json.dumps(req_data).encode('utf-8'))
response = client.recv(4096)
print(">>> 收到回复:")
print(json.dumps(json.loads(response.decode('utf-8')), indent=2, ensure_ascii=False))
client.close()
print("指令发送成功,请查看 AutoCAD 屏幕。")
except Exception as e:
except Exception as e:
print(f"连接失败: {e}")
if __name__ == "__main__":
print("=== 测试 LISP 外部文件挂载 ===")
request_call = {
"jsonrpc": "2.0",
"id": 3,
"method": "tools/call",
"params": {
"name": "draw_conference_table",
"arguments": {
"cx": 1000.0,
"cy": 1000.0,
"length": 1500.0,
"width": 600.0,
"chair_count": 4,
"layer": "MCP_CONF_ROOM"
}
}
}
send_request(request_call)
+2 -1
View File
@@ -3,7 +3,8 @@
"version": "1.0.0",
"dependencies": [
"nlohmann-json",
"curl"
"curl",
"ghc-filesystem"
],
"builtin-baseline": "127402f1c75bb3d5ff6bce04b285faa4930a5aca"
}