当前位置: 首页 > news >正文

基于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:连接保活检测

MCP
(图片来自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 Schema
  • returns:返回值描述

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触发具体工具执行,流程:

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

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)

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

  1. 启动Qt开发的MCP服务器(监听端口3456)
  2. 在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 工具调用流程

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

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

调用

六、常见问题与解决方案

6.1 工具未显示在列表中

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

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

resp

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服务器的核心步骤:

  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.

相关文章:

  • React从基础入门到高级实战:React 生态与工具 - 探索 React 生态中的工具和库:提升开发效率与项目质量
  • 前端面经 React 组件常见的声明方式
  • 征程 6X VDSP 调试方法
  • macOS 风格番茄计时器:设计与实现详解
  • 4.8.1 利用Spark SQL实现词频统计
  • mp中的密码处理
  • 设计模式-依赖倒转原则
  • 【Bluedriod】蓝牙协议栈 btm_init 源码解析
  • 【生产实践】Kibana控制台暴露风险:Nginx反向代理+权限控制实战方案(附避坑指南)
  • 一种经济实用的尖峰电压防护-PCB放电齿
  • GC1267F单相全波风扇电机预驱动器芯片详解
  • 【ArcGIS Pro微课1000例】0071:将无人机照片生成航线、轨迹点、坐标高程、方位角
  • Spring Boot 启动流程深度解析:从源码到实践
  • 高温炉制造企业Odoo ERP实施规划与深度分析报告
  • 免杀二 内存函数与加密
  • 影响沉金价格的因素如何体现在多层电路板制造上?
  • 智警杯备赛--数据库管理与优化
  • 基于stm32风速风向温湿度和瓦斯检测(仿真+代码)
  • 2025.05.28【读书笔记】|如何用SILVA和RFAM数据库高效去除rRNA污染
  • C++11:系统类型增强
  • 铜陵app网站做招聘/网页设计参考网站
  • wordpress css不更新/浙江seo
  • 专做定制型网站/做网络推广有前途吗
  • 搭建网站 网页/ios aso优化工具
  • 和幼女做视频网站/百度网络推广怎么做
  • b2b商业网站建设/优质的seo快速排名优化