基于Qt的MCP LLM代理服务开发实战:从0到1扩展大语言模型
最近,MCP Agent 已经遍地开花,遵循MCP协议 的代理服务器可以让AI操作各种软件工具,甚至点亮智能家居。在传统科学计算领域,我们希望AI能够把碳基行业软件的算法用起来。可是,我们的工程师大部分是只会Qt和C++的算法工程师,对MCP倡议的高级语言涉猎不深,对Web的响应等基本问题搞不清楚。我们这篇文章就是针对这个问题写的。
源码传送门在这:https://gitcode.com/colorEagleStdio/qtfnn_kits,文件夹 mcp_example 就是本例子。
Qt开发MCP的意义
传统科学计算领域存在大量C++遗留代码与高性能库,用Qt开发代理可通过轻量级封装将现有计算功能转化为AI可调用工具,避免系统重构,且C++的静态类型安全保障数据交互准确,防止科学计算中因类型错误导致的严重问题。
Qt的开发工具链(如Qt Creator)降低C++工程师进入AI领域的门槛,其与Fortran等语言库的兼容性可复用传统计算资源,在工业物联网、科研仿真等场景中,Qt代理能直接对接硬件或底层计算模块,以毫秒级延迟实现实时数据交互,为传统计算系统赋予AI分析能力,是连接科学计算与大语言模型的高效桥梁,兼具开发效率、性能稳定性与生态整合优势。
从Qt角度看,其跨平台特性使MCP AI代理可无缝部署于Windows、Linux等多系统,适配不同AI客户端环境,且QHttpServer等模块提供高性能异步通信,满足实时交互需求,C++的原生性能则确保文件IO等底层操作高效,适合高频工具调用场景。
本文针对垂直领域大量的既有Qt C++算法的Agent自动化问题,抛砖引玉给出Qt开发MCPAgent的方法。
文章目录
- Qt开发MCP的意义
- 一、MCP协议与Qt开发背景
- 1.1 MCP协议简介
- 1.2 Qt技术选型
- 二、环境搭建与项目结构
- 2.1 本地大语言模型+Qt环境
- 2.2 代码结构说明
- 三、MCP协议核心功能实现
- 3.1 初始化流程(initialize)
- 功能说明
- 代码实现
- 3.2 工具列表查询(tools/list)
- 功能说明
- 代码实现
- 3.3 工具调用处理(tools/call)
- 功能说明
- 代码实现
- 3.4 具体工具实现(fileSizeQuery)
- 功能说明
- 代码实现
- 四、辅助功能实现
- 4.1 统一响应处理
- 4.2 错误处理
- 五、与AnythingLLM集成测试
- 5.1 配置MCP服务器
- 5.2 工具调用流程
- 六、常见问题与解决方案
- 6.1 工具未显示在列表中
- 6.2 参数解析失败
- 6.3 跨域问题
- 七、总结与扩展方向
- 7.1 技术总结
- 7.2 扩展方向
- 完整代码
一、MCP协议与Qt开发背景
1.1 MCP协议简介
MCP(Model Context Protocol)是AI代理与工具服务器之间的通信标准,用于定义服务注册、工具调用和流式交互流程。其核心通过JSON-RPC 2.0实现,支持以下核心功能:
initialize
:服务初始化与能力声明tools/list
:工具列表查询tools/call
:具体工具调用ping
:连接保活检测
(图片来自https://mcp-docs.cn/introduction)
1.2 Qt技术选型
Qt提供了完整的HTTP服务器框架(QHttpServer
)和JSON处理模块(QJson
),非常适合快速搭建MCP服务器。本例使用Qt 6.9开发,核心组件包括:
QHttpServer
:处理HTTP请求路由QJsonDocument/QJsonObject
:解析和生成JSON数据QFileInfo
:文件系统操作
二、环境搭建与项目结构
2.1 本地大语言模型+Qt环境
请参考文章:https://blog.csdn.net/goldenhawking/article/details/145647894
完成 Anything LLM、Ollama的搭建, 并安QWen3:1.7b以上的本地大模型。注意deepSeek 1.5b似乎对agent支持不太好,蒸馏的太猛了。
开发环境我用的是Qt 6.9(需包含Qt HttpServer
模块)。
2.2 代码结构说明
代码结构如下:
// 核心模块划分
- 协议处理:initialize/ping/tools/list/call等路由
- 工具实现:fileSizeQuery具体功能逻辑
- 辅助函数:HTTP头处理、错误响应、结果发送
三、MCP协议核心功能实现
3.1 初始化流程(initialize)
功能说明
客户端首次连接时会发送initialize
请求,服务器需返回:
- 支持的协议版本(
protocolVersion
) - 服务信息(
serverInfo
) - 能力声明(
capabilities
,本例仅声明工具列表变更通知)
实操的JSON交互:
//Client
{"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"helloWorld","version":"1.0.0"}},"jsonrpc":"2.0","id":0}//Server回复
{"id": 0,"jsonrpc": "2.0","result": {"capabilities": {"tools": {"listChanged": true}},"protocolVersion": "2025-03-26","serverInfo": {"name": "MyExample","version": "1.0"}}
}//Client
{"method":"notifications/initialized","jsonrpc":"2.0"}//Server
HttpAccepted//注意,有时候Client还会PingServer:
//Client
{"method":"ping","jsonrpc":"2.0","id":1}//Server
{"id": 1,"jsonrpc": "2.0","result": {}
}
代码实现
void handle_initialize(const QJsonObject & jsonObject, QHttpServerResponder & response) {// 定义capabilities对象,包含tools对象,tools对象包含listChanged属性,值为trueQJsonObject capabilities{{"tools", QJsonObject({{"listChanged", true}})}};// 定义serverInfo对象,包含name和version属性,值分别为"MyExample"和"1.0"QJsonObject serverInfo{{"name", "MyExample"},{"version", "1.0"}};// 定义result对象,包含capabilities、protocolVersion和serverInfo属性QJsonObject result{{"capabilities", capabilities},{"protocolVersion", "2025-03-26"},{"serverInfo", serverInfo}};// 定义responseJson对象,包含jsonrpc、id和result属性QJsonObject responseJson{{"jsonrpc", "2.0"},{"id", jsonObject["id"]},{"result", result}};// 发送响应send_response(response,responseJson);
}
3.2 工具列表查询(tools/list)
功能说明
客户端通过tools/list
获取可用工具清单,每个工具需包含:
name
:工具唯一标识description
:功能描述inputSchema
:参数JSON Schemareturns
:返回值描述
JSON交互如下:
//Client:
{"method":"tools/list","jsonrpc":"2.0","id":1}
//Server
{"id": 1,"jsonrpc": "2.0","result": {"tools": [{"description": "查询{filename}指定的文件的大小,包含1个参数,string类型,名称为 filename,意义就是要查询的路径。","endpoint": "/mcp","inputSchema": {"properties": {"filename": {"description": "要查询大小的文件的完整路径","type": "string"}},"type": "object"},"method": "POST","name": "fileSizeQuery","returns": {"description": "包含文件大小信息的格式化字符串","type": "string"},"type": "function"}]}
}
代码实现
void handle_toolist(const QJsonObject & jsonObject, QHttpServerResponder & response)
{// 构建文件查询工具描述QJsonObject fileSizeQueryTool {{"name", "fileSizeQuery"},{"type", "function"},{"endpoint", "/mcp"},{"method", "POST"},{"description", "查询{filename}指定的文件的大小,包含1个参数,string类型,名称为 filename,意义就是要查询的路径。"},{"inputSchema", QJsonObject {{"type", QJsonValue("object")},{"properties",QJsonObject {{"filename", QJsonObject {{"type", "string"},{"description", "要查询大小的文件的完整路径"}}}}}}},{"returns", QJsonObject {{"type", "string"},{"description", "包含文件大小信息的格式化字符串"}}}};// 创建工具列表数组QJsonArray toolList;toolList.append(fileSizeQueryTool);//toolList.append(otherTools);// 构建响应对象QJsonObject responseJson {{"jsonrpc", "2.0"},{"id", jsonObject["id"]},{"result", QJsonObject {{"tools", toolList}}}};send_response(response,responseJson);
}
3.3 工具调用处理(tools/call)
功能说明
客户端通过tools/call
触发具体工具执行,流程:
- 解析参数(
name
和arguments
) - 路由到具体工具处理函数
- 返回执行结果或错误信息
JSON交互如下:
//Client:
{"method":"tools/call","params":{"name":"fileSizeQuery","arguments":{"filename":"D:\\test.pptx"}},"jsonrpc":"2.0","id":2}
//Server
{"id": 2,"jsonrpc": "2.0","result": {"content": [{"text": "File D:\\test.pptx size is 7000031 Bytes.","type": "text"}],"isError": false}
}
代码实现
void handle_tools_call(const QJsonObject & jsonObject, QHttpServerResponder & response){// 检查jsonObject中是否包含params字段if (!jsonObject.contains("params")){// 如果不包含,则调用error_happened函数,返回错误信息error_happened(response,jsonObject["id"].toString(),QString("Object params not found"), -32601);return;}// 检查params字段是否为对象if (!jsonObject["params"].isObject()){// 如果不是对象,则调用error_happened函数,返回错误信息error_happened(response,jsonObject["id"].toString(),QString("Params is not object."), -32601);return;}// 将params字段转换为对象QJsonObject op = jsonObject["params"].toObject();// 获取name字段的值QString md = op["name"].toString();// 如果name字段的值为fileSizeQuery,则调用toolfunc_fileSizeRequest函数if (md=="fileSizeQuery")toolfunc_fileSizeRequest(jsonObject, response);// 否则调用func_others函数elsefunc_others(jsonObject, response);
}
3.4 具体工具实现(fileSizeQuery)
功能说明
- 从参数中提取文件路径(
filename
) - 使用
QFileInfo
获取文件大小 - 返回格式化结果(包含字节数和错误状态)
代码实现
void toolfunc_fileSizeRequest(const QJsonObject & objreq, QHttpServerResponder & response) {// 从请求参数中获取文件名QJsonObject objParas = objreq["params"].toObject();QJsonObject objArgs = objParas["arguments"].toObject();//只有一个参数,filenameQString filename = objArgs["filename"].toString();QFileInfo info(filename);// 计算文件大小(MB)qint64 fileSize = -1;if (info.exists()) {fileSize = info.size();}// 创建响应QJsonArray arr_content;QJsonObject result {{"type","text"},{"text",QString("File %1 size is %2 Bytes.").arg(filename).arg(fileSize)}};arr_content.append(result);QJsonObject mcpResponse {{"jsonrpc", "2.0"},{"id", objreq["id"]},{"result",QJsonObject{{"isError",false},{"content",arr_content}}}};send_response(response,mcpResponse);
}
四、辅助功能实现
4.1 统一响应处理
//发送响应函数
void send_response(QHttpServerResponder & response,QJsonObject obj,QHttpServerResponder::StatusCode status)
{//创建响应对象QHttpServerResponse rp(status);//如果obj不为空,则将obj转换为json格式if (!obj.empty())rp = QHttpServerResponse(QJsonDocument(obj).toJson(),status);//设置响应头rp.setHeaders(defautl_Header());//发送响应response.sendResponse(rp);//OutputQTextStream stm(stderr);stm << "\nServer:" <<QJsonDocument(obj).toJson()<<"\n";stm.flush();
}QHttpHeaders defautl_Header()
{// 创建一个QHttpHeaders对象QHttpHeaders h;h.append("Content-Type","application/json");h.append("Cache-Control", "no-cache");h.append("Access-Control-Allow-Origin", "*");h.append("Access-Control-Allow-Methods", "POST");return h;
}
4.2 错误处理
void error_happened(QHttpServerResponder & response,QString reqID,QString message,int code )
{// 创建错误响应QVariantMap vm_err,vm_detail;vm_detail["code"] = code;vm_detail["message"] = message;vm_err["jsonrpc"] = "2.0";if (reqID.length())vm_err["id"] = reqID;elsevm_err["id"] = QJsonValue::Null;vm_err["error"] = vm_detail;QJsonObject mcpError = QJsonObject::fromVariantMap(vm_err);send_response(response,mcpError);
}
五、与AnythingLLM集成测试
5.1 配置MCP服务器
- 启动Qt开发的MCP服务器(监听端口3456)
- 在AnythingLLM中添加MCP服务器配置:
- 名称:helloWorld
- 地址:http://localhost:3456/mcp
这一步比较坑,需要搜索一个叫做anythingllm_mcp_servers.json 的文件,
Windows下藏在
C:\Users\你的用户名\AppData\Roaming\anythingllm-desktop\storage\plugins
里。
{"mcpServers": {"helloWorld": {"type": "streamable","url": "http://127.0.0.1:3456/mcp","headers": {}}
}
}
运行起来后,可以看到注册的结果,在“代理技能”里面出现了MCP Servers helloWorld:
这一步之前,我们的Qt工程就会经过注册步骤,出现输出。
5.2 工具调用流程
- 用户提问:“@agent 请查询文件d:\test.pptx的大小”
- AnythingLLM解析后触发
tools/call
请求:
{"method": "tools/call","params": {"name": "fileSizeQuery","arguments": {"filename": "d:\\test.pptx"}},"jsonrpc": "2.0","id": 2
}
- 服务器返回结果:
{"id": 2,"jsonrpc": "2.0","result": {"content": [{"text": "File d:\\test.pptx size is 7000031 Bytes.","type": "text"}],"isError": false}
}
- AnythingLLM将结果整理为自然语言回答:
文件d:\test.pptx的大小已查询成功,结果为7,000,031字节(约6.7MB)。
这里比较坑的是, AnythingLLM需要用提示词 @agent 明确打开Agent,否则不会去调用。
六、常见问题与解决方案
6.1 工具未显示在列表中
- 检查
tools/list
响应是否正确返回工具定义(确保name
与调用时一致) - 确认MCP服务器已正确注册(AnythingLLM中状态为"On")
通过命令行开启AnythingLLM,可以看到报错。如果没有报错,应该响应类似:
6.2 参数解析失败
- 确保
inputSchema
与实际参数匹配(类型、必填项) - 使用
QJsonParseError
捕获解析错误:
QJsonParseError parseError;
QJsonObject jsonObject = QJsonDocument::fromJson(request.body(), &parseError).object();
if (parseError.error != QJsonParseError::NoError) {error_happened(response, "", "JSON解析错误", -32700);return;
}
6.3 跨域问题
- 确保响应头包含
Access-Control-Allow-Origin: *
- 在
defautl_Header
函数中添加跨域相关头信息
七、总结与扩展方向
7.1 技术总结
通过Qt实现MCP服务器的核心步骤:
- 实现
initialize
完成服务注册 - 通过
tools/list
声明可用工具 - 在
tools/call
中处理具体工具逻辑 - 使用统一响应函数确保格式一致性
7.2 扩展方向
- 支持多工具并行调用
- 实现流式响应(
text/event-stream
) - 添加身份验证机制
- 集成更多文件系统操作(如文件删除、目录列表)
本文通过具体案例演示了Qt与MCP协议的结合,Qt工程师可基于此框架快速扩展AI代理功能,实现与大语言模型的深度集成。
完整代码
参考:
https://gitcode.net/coloreaglestdio/qtfnn_kits
文件夹是 mcp_example.