最近,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的方法。
文章目录
一、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属性,值为true
QJsonObject 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函数
else
func_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();
//只有一个参数,filename
QString 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);
//Output
QTextStream 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;
else
vm_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.