MCP项目笔记九(插件 bacio-quote)

张开发
2026/4/8 8:54:52 15 分钟阅读

分享文章

MCP项目笔记九(插件 bacio-quote)
从零理解 MCP 插件开发Resource、Tool 与资源读取协议详解本文以 C 插件BacioQuote系统讲解 MCP 插件的核心概念、资源读取协议以及 Tool 与 Resource 的本质区别。一、从一个随机名言插件说起想象这样一个简单需求写一个插件每次被调用时随机返回一句意大利 Bacio Perugina 风格的名言。这个需求对应的 C 插件结构如下BacioQuote.cpp ├── 名言数据std::vectorstd::string ├── 资源声明PluginResource ├── 插件元信息GetName / GetVersion / GetType ├── 初始化与销毁Initialize / Shutdown └── 核心请求处理HandleRequestImpl整体思路清晰插件向外暴露一个叫bacio:///quote的资源每次收到请求时从预设名言里随机抽一条按协议格式包装后返回。二、代码结构逐层解析2.1 头文件依赖#includevector#includestring#includerandom#includePluginAPI.h#includejson.hpp#include../../src/utils/MCPBuilder.h各模块职责vector/string存储和操作名言文本random提供现代化随机数生成能力PluginAPI.h定义插件接口规范json.hppnlohmann::json处理 JSON 序列化与反序列化MCPBuilder.h辅助构建符合 MCP 协议的资源响应2.2 资源声明staticPluginResource resources[]{{bacio-quote,A list of the famous italian bacio perugina quotes,bacio:///quote,text/plain,}};这四个字段分别代表资源名称、描述、URI 和 MIME 类型。外界只需要知道bacio:///quote这个地址就能读取这个资源。2.3 插件元信息constchar*GetNameImpl(){returnbacio-quote;}constchar*GetVersionImpl(){return1.0.0;}PluginTypeGetTypeImpl(){returnPLUGIN_TYPE_RESOURCES;}PLUGIN_TYPE_RESOURCES表明这是一个资源型插件而非工具型插件。这个区别在第四节会详细展开。2.4 初始化函数intInitializeImpl(){return1;}返回1表示初始化成功。三、核心函数HandleRequestImpl 详解这是整个插件最关键的部分负责接收请求并生成响应。char*HandleRequestImpl(constchar*req){// 解析传入的 JSON 字符串至少验证其合法性autorequestjson::parse(req);// 初始化空响应对象nlohmann::json responsejson::object();// ── 随机数生成 ──────────────────────────────std::random_device rd;// 硬件随机种子std::mt19937gen(rd());// Mersenne Twister 引擎std::uniform_int_distributiondistr(0,messages.size()-1);// 均匀分布// ── 构建响应内容 ─────────────────────────────nlohmann::json contentsjson::array();contents.push_back(MCPBuilder::ResourceText(resources[0].uri,// bacio:///quoteresources[0].mime,// text/plainmessages[distr(gen)]// 随机选中的名言));response[contents]contents;// ── 序列化并返回 ─────────────────────────────std::string resultresponse.dump();char*buffernewchar[result.length()1];#ifdef_WIN32strcpy_s(buffer,result.length()1,result.c_str());#elsestrcpy(buffer,result.c_str());#endifreturnbuffer;}设计细节随机数引擎使用std::mt19937比老式的rand()质量更高返回的是堆上分配的char*调用方需要负责释放四、resources/read资源读取协议详解4.1 它是什么resources/read是 MCP 协议中读取资源的标准方法。可以理解为“请把这个 URI 对应的资源内容读给我。”4.2 请求与响应格式请求{jsonrpc:2.0,method:resources/read,params:{uri:bacio:///quote},id:request-id}响应{contents:[{uri:bacio:///quote,mimeType:text/plain,text:某一句名言}]}关键参数只有一个uri。这也是 Resource 和 Tool 最直观的区别之一——Resource 的输入非常简洁。4.3 完整执行链插件声明资源GetResourceCountImpl / GetResourceImpl ↓ 宿主发起 resources/read 请求携带目标 URI ↓ HandleRequestImpl 解析请求匹配 URI ↓ 生成内容封装进 contents 数组 ↓ 返回 JSON 响应给宿主4.4 更规范的实现方式当前的 BacioQuote 插件跳过了method和uri的校验更严谨的实现应该是char*HandleRequestImpl(constchar*req){autorequestjson::parse(req);std::string methodrequest[method];if(methodresources/read){std::string urirequest[params][uri];if(uribacio:///quote){// 构建并返回响应}returnbuildError(Resource not found);}returnbuildError(Unknown method);}先校验方法名再校验 URI最后才生成内容——这才是生产级别的实现思路。五、Tool vs Resource本质区别Tool 帮你做事执行动作Resource 给你内容读取数据5.1 协议层对比维度ToolResource请求方法tools/callresources/read核心参数nameargumentsuri响应字段contentcontents语义执行读取副作用可能有通常没有5.2 数据流对比Tool 的调用流// 请求{method:tools/call,params:{name:calculator,arguments:{expression:23*4}}}// 响应{content:[{type:text,text:14}]}Resource 的调用流// 请求{method:resources/read,params:{uri:bacio:///quote}}// 响应{contents:[{uri:bacio:///quote,mimeType:text/plain,text:名言}]}5.3 一个常见的误区很多人认为返回数据 Resource但这并不准确。判断依据应该是数据是被动提供的还是通过动态逻辑生成的随机名言简单取值→ Resource根据条件筛选的名言 → Tool固定配置 → Resource需要计算或查询的结果 → Tool六、整体流程回顾宿主程序加载插件 ↓ 调用 CreatePlugin() 获取接口表 ↓ 调用 InitializeImpl() 完成初始化 ↓ 通过 GetResourceCountImpl / GetResourceImpl 发现资源 ↓ 发送 resources/read 请求 ↓ HandleRequestImpl 随机选取名言封装 JSON 响应 ↓ 宿主收到 contents完成资源读取 ↓ 程序结束时调用 ShutdownImpl / DestroyPlugin

更多文章