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

从零理解 MCP 插件开发:Resource、Tool 与资源读取协议详解

本文以 C++ 插件BacioQuote,系统讲解 MCP 插件的核心概念、资源读取协议以及 Tool 与 Resource 的本质区别。


一、从一个"随机名言插件"说起

想象这样一个简单需求:写一个插件,每次被调用时随机返回一句意大利 Bacio Perugina 风格的名言。

这个需求对应的 C++ 插件,结构如下:

复制代码
BacioQuote.cpp
├── 名言数据(std::vector<std::string>)
├── 资源声明(PluginResource)
├── 插件元信息(GetName / GetVersion / GetType)
├── 初始化与销毁(Initialize / Shutdown)
└── 核心请求处理(HandleRequestImpl)

整体思路清晰:插件向外暴露一个叫 bacio:///quote 的资源,每次收到请求时从预设名言里随机抽一条,按协议格式包装后返回。


二、代码结构逐层解析

2.1 头文件依赖

cpp 复制代码
#include <vector>
#include <string>
#include <random>
#include "PluginAPI.h"
#include "json.hpp"
#include "../../src/utils/MCPBuilder.h"

各模块职责:

  • vector / string:存储和操作名言文本
  • random:提供现代化随机数生成能力
  • PluginAPI.h:定义插件接口规范
  • json.hpp(nlohmann::json):处理 JSON 序列化与反序列化
  • MCPBuilder.h:辅助构建符合 MCP 协议的资源响应

2.2 资源声明

cpp 复制代码
static PluginResource resources[] = {
    {
        "bacio-quote",
        "A list of the famous italian bacio perugina quotes",
        "bacio:///quote",
        "text/plain",
    }
};

这四个字段分别代表:资源名称、描述、URI 和 MIME 类型。外界只需要知道 bacio:///quote 这个地址,就能读取这个资源。

2.3 插件元信息

cpp 复制代码
const char* GetNameImpl()    { return "bacio-quote"; }
const char* GetVersionImpl() { return "1.0.0"; }
PluginType  GetTypeImpl()    { return PLUGIN_TYPE_RESOURCES; }

PLUGIN_TYPE_RESOURCES 表明这是一个资源型插件,而非工具型插件。这个区别在第四节会详细展开。

2.4 初始化函数

cpp 复制代码
int InitializeImpl() {
    return 1;
}

返回 1 表示初始化成功。


三、核心函数:HandleRequestImpl 详解

这是整个插件最关键的部分,负责接收请求并生成响应。

cpp 复制代码
char* HandleRequestImpl(const char* req) {
    // 解析传入的 JSON 字符串,至少验证其合法性
    auto request = json::parse(req);

    // 初始化空响应对象
    nlohmann::json response = json::object();

    // ── 随机数生成 ──────────────────────────────
    std::random_device rd;                  // 硬件随机种子
    std::mt19937 gen(rd());                 // Mersenne Twister 引擎
    std::uniform_int_distribution<> distr(0, messages.size() - 1); // 均匀分布

    // ── 构建响应内容 ─────────────────────────────
    nlohmann::json contents = json::array();
    contents.push_back(
        MCPBuilder::ResourceText(
            resources[0].uri,   // "bacio:///quote"
            resources[0].mime,  // "text/plain"
            messages[distr(gen)] // 随机选中的名言
        )
    );
    response["contents"] = contents;

    // ── 序列化并返回 ─────────────────────────────
    std::string result = response.dump();
    char* buffer = new char[result.length() + 1];
#ifdef _WIN32
    strcpy_s(buffer, result.length() + 1, result.c_str());
#else
    strcpy(buffer, result.c_str());
#endif
    return buffer;
}

设计细节:

  • 随机数引擎使用 std::mt19937,比老式的 rand() 质量更高
  • 返回的是堆上分配的 char*,调用方需要负责释放

四、resources/read:资源读取协议详解

4.1 它是什么

resources/read 是 MCP 协议中"读取资源"的标准方法。可以理解为:

"请把这个 URI 对应的资源内容读给我。"

4.2 请求与响应格式

请求:

json 复制代码
{
  "jsonrpc": "2.0",
  "method": "resources/read",
  "params": {
    "uri": "bacio:///quote"
  },
  "id": "request-id"
}

响应:

json 复制代码
{
  "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 插件跳过了 methoduri 的校验,更严谨的实现应该是:

cpp 复制代码
char* HandleRequestImpl(const char* req) {
    auto request = json::parse(req);
    std::string method = request["method"];

    if (method == "resources/read") {
        std::string uri = request["params"]["uri"];

        if (uri == "bacio:///quote") {
            // 构建并返回响应
        }
        return buildError("Resource not found");
    }
    return buildError("Unknown method");
}

先校验方法名,再校验 URI,最后才生成内容------这才是生产级别的实现思路。


五、Tool vs Resource:本质区别

Tool = 帮你做事(执行动作)
Resource = 给你内容(读取数据)

5.1 协议层对比

维度 Tool Resource
请求方法 tools/call resources/read
核心参数 name + arguments uri
响应字段 content contents
语义 执行 读取
副作用 可能有 通常没有

5.2 数据流对比

Tool 的调用流:

json 复制代码
// 请求
{
  "method": "tools/call",
  "params": {
    "name": "calculator",
    "arguments": { "expression": "2+3*4" }
  }
}

// 响应
{
  "content": [{ "type": "text", "text": "14" }]
}

Resource 的调用流:

json 复制代码
// 请求
{
  "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
相关推荐
刘大猫.26 分钟前
宇树科技回应联合英伟达开发“H2+”人形机器人,预计今年下半年正式亮相
人工智能·科技·机器学习·ai·chatgpt·机器人·大模型
Sammyyyyy30 分钟前
2026 Mac 本地大模型部署深度解析与混合架构指南
数据库·人工智能·macos·ai·架构·servbay
随意起个昵称34 分钟前
线性dp-LIS题目5(导弹拦截,二分优化)
c++·算法·动态规划
winlife_35 分钟前
全程用 AI 做一款商业级手游 · EP10 道具系统:让三个按钮真正改变棋盘
windows·算法·unity·ai编程·游戏开发·mcp·玩法系统
光电笑映44 分钟前
进程间通信:深入 System V IPC:共享内存、消息队列与信号量
linux·运维·服务器·c++
xian_wwq1 小时前
【学习笔记】倾斜摄影、高斯泼溅(3DGS)、点云与数字孪生“族谱”全盘点
笔记·学习·3d
a诠释淡然1 小时前
C++模板元编程—现代C++的黑魔法
开发语言·c++
汉克老师1 小时前
GESP2026年3月认证C++六级真题与解析(单选题1-8)
c++·多态··构造函数·循环队列·bst·gesp6级
charlie1145141911 小时前
现代C++工程:constexpr 基础:编译期求值的艺术
开发语言·c++
Mr_Morning1 小时前
MCP 通信
mcp