MCP项目笔记八(插件 sleep)
C++ 实现插件式 sleep 工具:
本文以一个极简但完整的 C++ 插件示例为切入点,带你搞清楚插件框架的核心设计模式、JSON Schema 的参数约束方式,以及跨模块 ABI 的若干实际陷阱。
背景:为什么需要插件式工具?
在很多宿主程序(Host)中——无论是 AI 推理引擎、脚本运行时还是自动化平台——都有一类需求:
在运行时动态扩展功能,而无需重新编译宿主。
插件(Plugin)机制就是解决这个问题的经典方案。宿主定义好接口规范,插件按规范实现并编译成动态库(.dll / .so),宿主在运行时加载、调用。
sleep-tools 就是这样一个最小可运行示例:它只做一件事——让线程睡一觉,然后告诉宿主"睡了多久"。麻雀虽小,五脏俱全。
整体结构一览
sleep-tools 插件
│
├── 元信息层 GetNameImpl / GetVersionImpl / GetTypeImpl
├── 生命周期层 InitializeImpl / ShutdownImpl
├── 工具描述层 methods[] + JSON Schema
├── 请求处理层 HandleRequestImpl(核心)
└── 导出层 CreatePlugin / DestroyPlugin
宿主通过 CreatePlugin() 拿到一张"函数表"(PluginAPI*),之后所有交互都走这张表完成。
逐层拆解
一、工具元信息:methods[]
methods[] 数组向宿主声明"这个插件能做什么":
| 字段 | 内容 |
|---|---|
| 工具名 | sleep |
| 描述 | 暂停执行指定毫秒数 |
| 参数 Schema | JSON Schema Draft-07 |
JSON Schema 的含义
{
"type": "object",
"properties": {
"milliseconds": { "type": "number", "minimum": 0 }
},
"required": ["milliseconds"],
"additionalProperties": false
}
这段 Schema 规定:
- 请求体必须是对象
- 必须包含
milliseconds字段,且为非负数 - 不允许传入多余字段
宿主拿到这段 Schema,就能在调用前做参数校验,而不必深入插件实现。
二、核心函数:HandleRequestImpl
这是整个插件最重要的部分,分四步走:
第一步:解析 JSON
auto request = json::parse(req);
宿主传入的 req 是一段 JSON 字符串,结构大致如下:
{
"params": {
"arguments": {
"milliseconds": 1000
}
}
}
第二步:提取参数
auto milliseconds = request["params"]["arguments"]["milliseconds"].get<int>();
沿着 params → arguments → milliseconds 的路径取值,并转成 int。
第三步:执行休眠
std::this_thread::sleep_for(std::chrono::milliseconds(milliseconds));
调用标准库 <thread> 中的 sleep_for,阻塞当前线程指定毫秒数。例如传入 1000 就暂停 1 秒,传入 5000 就暂停 5 秒。
第四步:构造并返回响应
响应 JSON 的最终结构:
{
"content": [
{
"type": "text",
"text": "Waited for 1000 milliseconds"
}
],
"isError": false
}
三、为什么返回 char* 而不是 std::string?
这是跨模块 ABI 的经典问题。
插件编译成动态库后,宿主和插件可能使用不同版本的 C++ 标准库,std::string 的内存布局、引用计数实现等细节在不同版本间并不一致。直接跨 DLL / .so 边界传递 std::string 非常危险。
因此这里做了一个"转义":
std::string result = response.dump(); // C++ 侧操作
char* buffer = new char[result.length() + 1]; // 分配 C 风格缓冲区
strcpy(buffer, result.c_str()); // 复制内容
return buffer; // 返回裸指针
C 风格的 char* 没有运行时依赖,任何语言、任何编译器都能安全处理。
四、extern "C" 与名字改编
extern "C" PLUGIN_API PluginAPI* CreatePlugin();
extern "C" PLUGIN_API void DestroyPlugin(PluginAPI*);
C++ 编译器会对函数名做"名字改编"(Name Mangling),给同名函数加上参数类型编码,以支持函数重载。例如 CreatePlugin 在编译后可能变成 _Z12CreatePluginv(GCC 风格)或 ?CreatePlugin@@YAPAUPluginAPI@@XZ(MSVC 风格)。
加上 extern "C" 后,编译器按 C 的规则导出符号,函数名保持原样,宿主才能通过固定的字符串名称找到它。
更多推荐

所有评论(0)