基于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的方法。

文章目录

一、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 代码结构说明

代码结构如下:

cpp 复制代码
// 核心模块划分
- 协议处理:initialize/ping/tools/list/call等路由
- 工具实现:fileSizeQuery具体功能逻辑
- 辅助函数:HTTP头处理、错误响应、结果发送

三、MCP协议核心功能实现

3.1 初始化流程(initialize)

功能说明

客户端首次连接时会发送initialize请求,服务器需返回:

  • 支持的协议版本(protocolVersion
  • 服务信息(serverInfo
  • 能力声明(capabilities,本例仅声明工具列表变更通知)

实操的JSON交互:

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": {
    }
}
代码实现
cpp 复制代码
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 Schema
  • returns:返回值描述

JSON交互如下:

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"
            }
        ]
    }
}
代码实现
cpp 复制代码
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触发具体工具执行,流程:

  1. 解析参数(namearguments
  2. 路由到具体工具处理函数
  3. 返回执行结果或错误信息

JSON交互如下:

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
    }
}
代码实现
cpp 复制代码
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)

功能说明
  1. 从参数中提取文件路径(filename
  2. 使用QFileInfo获取文件大小
  3. 返回格式化结果(包含字节数和错误状态)
代码实现
cpp 复制代码
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 统一响应处理

cpp 复制代码
//发送响应函数
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 错误处理

cpp 复制代码
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服务器

  1. 启动Qt开发的MCP服务器(监听端口3456)
  2. 在AnythingLLM中添加MCP服务器配置:

这一步比较坑,需要搜索一个叫做anythingllm_mcp_servers.json 的文件,

Windows下藏在

C:\Users\你的用户名\AppData\Roaming\anythingllm-desktop\storage\plugins

里。

json 复制代码
{
  "mcpServers": {
   "helloWorld": {
      "type": "streamable",
      "url": "http://127.0.0.1:3456/mcp",
      "headers": {
        
      }
    }
}
}

运行起来后,可以看到注册的结果,在"代理技能"里面出现了MCP Servers helloWorld:

这一步之前,我们的Qt工程就会经过注册步骤,出现输出。

5.2 工具调用流程

  1. 用户提问:"@agent 请查询文件d:\test.pptx的大小"
  2. AnythingLLM解析后触发tools/call请求:
json 复制代码
{
    "method": "tools/call",
    "params": {
        "name": "fileSizeQuery",
        "arguments": {
            "filename": "d:\\test.pptx"
        }
    },
    "jsonrpc": "2.0",
    "id": 2
}
  1. 服务器返回结果:
json 复制代码
{
    "id": 2,
    "jsonrpc": "2.0",
    "result": {
        "content": [
            {
                "text": "File d:\\test.pptx size is 7000031 Bytes.",
                "type": "text"
            }
        ],
        "isError": false
    }
}
  1. AnythingLLM将结果整理为自然语言回答:
text 复制代码
文件d:\test.pptx的大小已查询成功,结果为7,000,031字节(约6.7MB)。

这里比较坑的是, AnythingLLM需要用提示词 @agent 明确打开Agent,否则不会去调用。

六、常见问题与解决方案

6.1 工具未显示在列表中

  • 检查tools/list响应是否正确返回工具定义(确保name与调用时一致)
  • 确认MCP服务器已正确注册(AnythingLLM中状态为"On")

通过命令行开启AnythingLLM,可以看到报错。如果没有报错,应该响应类似:

6.2 参数解析失败

  • 确保inputSchema与实际参数匹配(类型、必填项)
  • 使用QJsonParseError捕获解析错误:
cpp 复制代码
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服务器的核心步骤:

  1. 实现initialize完成服务注册
  2. 通过tools/list声明可用工具
  3. tools/call中处理具体工具逻辑
  4. 使用统一响应函数确保格式一致性

7.2 扩展方向

  1. 支持多工具并行调用
  2. 实现流式响应(text/event-stream
  3. 添加身份验证机制
  4. 集成更多文件系统操作(如文件删除、目录列表)

本文通过具体案例演示了Qt与MCP协议的结合,Qt工程师可基于此框架快速扩展AI代理功能,实现与大语言模型的深度集成。

完整代码

参考:

https://gitcode.net/coloreaglestdio/qtfnn_kits

文件夹是 mcp_example.

相关推荐
天天代码码天天15 分钟前
PP-OCRv5 C++封装DLL C#调用源码分享
开发语言·c++·c#·ocr
wen__xvn29 分钟前
每日刷题c++
数据结构·c++·算法
wujj_whut2 小时前
【PC网上邻居--1】基于Samba协议的局域网文件共享系统设计与实现
c++
想睡hhh2 小时前
Practice 2025.5.29 —— 二叉树进阶面试题(1)
c++·算法
Ashlee_code2 小时前
TRS收益互换平台开发实践:从需求分析到系统实现
java·数据结构·c++·python·架构·php·需求分析
小王努力学编程2 小时前
【Linux网络编程】传输层协议TCP,UDP
linux·网络·c++·udp·tcp
٩( 'ω' )و2605 小时前
C++进阶--C++11(04)
开发语言·c++·c++11
落羽的落羽5 小时前
【C++】“多态”特性
开发语言·c++·学习
tt5555555555556 小时前
每日一题——提取服务器物料型号并统计出现次数
数据结构·c++·算法