文章目录

AI/LLM基础概念

什么是大语言模型(LLM)

大语言模型(Large Language Model,简称LLM)是一种基于深度学习的AI模型,核心能力是预测下一个词。给定一段文本,它能以极高的准确率继续生成合理的后续内容。通俗来说,可以把LLM想象成一个"超级完形填空机器":给它一个开头,它会用海量训练数据中学到的语言模式填充接下来最可能出现的词,逐个词地生成完整回复。

当前主流的LLM产品
  • Claude — 由Anthropic公司开发,注重安全性和有益性,也是MCP协议的制定者
  • GPT — 由OpenAI公司开发(如GPT-4、GPT-4o),业界知名度最高
  • Gemini — 由Google公司开发,前身为Bard
  • Llama — 由Meta公司开源的LLM系列
  • DeepSeek — 由中国深度求索公司开发,开源且性能强大
  • 通义千问(Qwen) — 由阿里云开发
以本项目的MCP Server为例,它就是为**Claude Desktop**等LLM客户端提供外部能力的桥梁——让AI可以查询天气、读取文件、执行代码审查等超越纯文本生成的操作。

LLM如何工作

Token(词元)

LLM不直接处理"文字",而是处理Token(词元)。Token是文本的最小处理单元:

  • 一个英文单词通常等于1-2个token(如 “hello” = 1 token, “unbelievable” = 3 tokens)
  • 一个中文字通常等于1-2个token(如 “人工智能” = 4 tokens)
  • Token数量决定了LLM"看到"了多少信息,也直接决定了API调用成本
输入文本: "Hello, how are you?"
         ↓ Token化
Token序列: [15496, 11, 1268, 527, 499, 30]
上下文窗口(Context Window)

上下文窗口是LLM在一次对话中能够"记住"的最大token数量。它就像一个滑动窗口——当对话超过窗口容量,最早的内容会被"遗忘"。

  • Claude 3 系列:200K tokens(约15万英文单词或一本中等厚度的书)
  • GPT-4 Turbo:128K tokens
  • Gemini 1.5 Pro:100万tokens(业界最大)

上下文窗口包含:

  • System Prompt(系统提示)—— 设定AI的行为规则
  • 历史对话记录
  • 当前用户输入
  • 工具调用结果(Function Calling返回的数据)
  • MCP资源内容
推理(Inference)

推理是指LLM根据输入的token序列,逐token生成输出文本的过程。每次生成一个token时,模型会根据已生成的内容计算下一个最可能出现的token。这个过程持续进行,直到模型生成一个特殊的"停止token"或达到最大长度限制。

输入: "法国的首都是"
推理过程:
  第1步: 计算下一个token概率 → "巴" (概率 0.42)
  第2步: 计算下一个token概率 → "黎" (概率 0.38)
  第3步: 计算下一个token概率 → <停止> (概率 0.95)
输出: "巴黎"

关键点:LLM的每次推理是无状态的——它不会"记住"上一次推理的结果。你看到的"对话记忆"效果,其实是通过把历史对话重新打包进上下文窗口实现的。每一次发消息给LLM,都需要把所有历史记录+system prompt+新消息一起发送。

Function Calling / Tool Use 是什么

Function Calling(在Anthropic生态中称为Tool Use)是让LLM能够调用外部函数/API的机制。没有这个能力,LLM只能根据训练数据生成文本;有了这个能力,LLM可以:

  • 查询实时天气(训练数据截止于某个时间点,不知道今天的天气)
  • 执行数学计算(LLM本身不擅长精确计算)
  • 读取文件内容
  • 调用数据库
  • 发送邮件

运作流程

1. 用户: "北京今天天气怎么样?"
2. LLM: 我需要调用天气函数,但我不知道参数 → 返回function_call
   → { "name": "get_weather", "arguments": { "city": "北京" } }
3. 客户端: 实际执行 get_weather("北京") → 返回 {"temperature": 25, "condition": "晴"}
4. 客户端把结果传回LLM,LLM基于结果生成最终回复
5. LLM: "北京今天天气晴朗,气温25°C。"

在Claude的API中,Tool Use定义包含:

  • name:工具名称
  • description:工具描述,帮助LLM理解何时使用
  • input_schema:JSON Schema格式的输入参数定义

本项目中的体现PluginAPI.h 第46-50行的 PluginTool 结构体正是对Tool Use概念的实现:

// PluginAPI.h 第46-50行
typedef struct {
    const char* name;          // 工具名称,如 "get_weather"
    const char* description;   // 工具描述
    const char* inputSchema;   // JSON Schema格式的参数定义
} PluginTool;

以Weather插件(plugins/weather/Weather.cpp 第31-47行)为例,它定义了一个 get_weather 工具,参数的JSON Schema描述了需要 citylatitudelongitude 三个字段。

RAG(检索增强生成)概念

RAG(Retrieval-Augmented Generation,检索增强生成)是解决LLM"知识截止日期"和"幻觉"问题的关键技术。

工作流程

1. 用户提问
2. 系统从外部知识库检索相关文档(向量搜索)
3. 将检索到的文档片段拼接到Prompt中
4. LLM基于检索的内容 + 自身知识生成回答
5. 返回带着引用的回答

为什么需要RAG

  • LLM的训练数据有截止日期(如Claude的训练数据可能截止于2024年初)
  • LLM会产生"幻觉"(自信地编造不存在的事实)
  • 企业内部知识、私有文档不在LLM训练数据中
  • 需要可溯源的引用

MCP与RAG的关系:MCP的Resources机制本质上就是一种RAG实现方式——MCP Server通过 resources/listresources/read 让LLM能够检索外部数据源,实现检索增强。

为什么LLM需要外部工具和资源

LLM是"缸中之脑"——只能在训练数据的范围内回答问题。外部工具和资源赋予LLM与真实世界交互的能力:

能力缺口 外部工具解决方案 本项目示例
实时数据 调用API获取 Weather插件调用Open-Meteo API
精确计算 调用计算器/代码执行 工具类型插件
文件操作 资源读写 BacioQuote资源URI
外部知识 RAG检索 Resources/read机制
持续通信 通知/推送 Notification插件
专业领域 提示模板/Prompt CodeReview提示模板

System Prompt vs User Prompt

在LLM交互中,消息有不同的角色(Role)

  • System Prompt(系统提示):设定AI的基础行为规则、人设、知识范围、输出格式等。这是最先发送的,权重最高。
  • User Prompt(用户提示):用户的实际问题或指令。
  • Assistant(助手回复):LLM之前生成的回复,在对话历史中回传。
// 一条典型的LLM API请求
{
  "model": "claude-sonnet-4-20250514",
  "system": "你是一个专业的C++代码审查助手。回答要简洁、技术精确。",
  "messages": [
    {"role": "user", "content": "这段代码有内存泄漏吗?"},
    {"role": "assistant", "content": "让我分析一下..."},
    {"role": "user", "content": "好的,请继续"}
  ]
}

System Prompt的变化

  • 实际API中,system参数是单独的顶层字段(Anthropic API)
  • 在对话消息格式中,system也作为 role: "system" 出现(OpenAI API)
  • MCP的Prompt机制返回的消息数组中可以使用 role: "system"role: "user"

本项目的体现:CodeReview插件生成的消息使用 role: "user",将提示作为用户消息嵌入对话中(plugins/code-review/CodeReview.cpp 第58-63行)。


MCP协议概述(由Anthropic制定)

MCP是什么:Model Context Protocol

MCP(Model Context Protocol,模型上下文协议) 是由Anthropic公司于2024年11月发布并开源的一套开放协议标准。它定义了LLM应用与外部工具、数据源之间的标准化通信方式。

核心思想可以用"AI世界的USB-C接口"来类比:

没有MCP之前:                      有了MCP之后:
                                   
LLM → 专用接口1 → 数据库A          LLM → MCP Client
LLM → 专用接口2 → 文件系统B                ↕ MCP协议(标准化JSON-RPC通信)
LLM → 专用接口3 → API服务C          MCP Server A → 数据库A
LLM → 专用接口4 → 搜索引擎D          MCP Server B → 文件系统B
                                     MCP Server C → API服务C
每个集成都需要单独开发                MCP Server D → 搜索引擎D
                                    一次对接,所有Server通用

本项目的MCP Server(版本0.8.0,见 build/version.h 第4行)正是MCP协议的C++实现,它提供了插件化的架构,允许通过动态库(.dll/.so)扩展功能。

MCP解决了什么问题(LLM孤岛问题)

:::color4
MCP解决的核心问题是LLM孤岛——每个LLM产品各自定义自己的工具/数据接入方式,导致:

  • 重复开发:为Claude开发一个天气查询插件后,想给GPT用就得重写
  • 厂商锁定:一旦在某个生态投入大量插件开发,迁移成本极高
  • 碎片化:没有统一标准,社区力量分散
  • 安全隐患:每个LLM产品都有自己的权限模型,安全审计困难

:::

:::color1
MCP通过标准化解决了这些问题:

  1. 统一接口:所有LLM Client使用相同的JSON-RPC 2.0协议与MCP Server通信
  2. 一次开发,多处运行:一个MCP Server可以被Claude Desktop、Zed、Sourcegraph Cody等所有MCP兼容客户端使用
  3. 能力抽象:通过Tools/Prompts/Resources三层抽象覆盖所有LLM需要的外部能力
  4. 传输无关:同一套协议可以通过stdio(标准输入输出)、SSE(Server-Sent Events)、HTTP Stream等多种传输方式工作

:::

MCP的三层抽象:Tools / Prompts / Resources

MCP协议将LLM需要的外部能力抽象为三个核心概念:

                    ┌──────────────────────────────┐
                    │       MCP Server              │
                    │                               │
    ┌─────────────┐ │  ┌─────────┐  ┌───────────┐ │
    │   Tools     │─┼─>│ 执行操作  │  │  天气查询  │ │
    │   工具       │ │  └─────────┘  │  发送邮件  │ │
    └─────────────┘ │               │  文件操作  │ │
                    │               └───────────┘ │
    ┌─────────────┐ │  ┌─────────┐  ┌───────────┐ │
    │  Prompts    │─┼─>│ 提供模板  │  │  代码审查  │ │
    │   提示模板   │ │  └─────────┘  │  文档生成  │ │
    └─────────────┘ │               └───────────┘ │
                    │                              │
    ┌─────────────┐ │  ┌─────────┐  ┌───────────┐ │
    │  Resources  │─┼─>│ 提供数据  │  │ 名言引用   │ │
    │   资源       │ │  └─────────┘  │  文件内容  │ │
    └─────────────┘ │               └───────────┘ │
                    └──────────────────────────────┘
Tools(工具)—— 让LLM执行操作

工具是面向LLM的可执行函数。LLM可以"决定"调用某个工具,服务器执行后返回结果。

特征

  • 有副作用(可以改变外部状态,如发送邮件)
  • 需要参数定义(JSON Schema)
  • 调用结果返回给LLM继续处理

本项目示例PluginAPI.h 第40行定义 PLUGIN_TYPE_TOOLS = 0。Weather插件(plugins/weather/Weather.cpp 第31-47行)定义了名为 get_weather 的工具。

Prompts(提示模板)—— 提供预定义的对话模板

提示模板是预定义的、参数化的对话模板。用户可以从中选择,填入参数后获得结构化的对话。

特征

  • 无副作用(只返回消息文本)
  • 包含参数定义(可选的占位符)
  • 返回messages数组,包含role(system/user/assistant)和content

本项目示例PluginAPI.h 第41行定义 PLUGIN_TYPE_PROMPTS = 1。CodeReview插件(plugins/code-review/CodeReview.cpp 第32-42行)定义了名为 code-review 的提示模板,接受 language 参数。

Resources(资源)—— 提供只读数据

资源是服务器向LLM暴露的只读数据。通过URI标识,支持文本和二进制格式。

特征

  • 只读(不能修改)
  • 通过URI访问(如 bacio:///quote
  • 支持多种MIME类型(text/plain, image/png, audio/wav等)
  • 支持订阅/更新通知

本项目示例PluginAPI.h 第42行定义 PLUGIN_TYPE_RESOURCES = 2。BacioQuote插件(plugins/bacio-quote/BacioQuote.cpp 第183-190行)定义了一个 bacio:///quote 资源。

MCP的价值:标准化LLM与外部世界的交互

价值维度 说明
开放标准 任何人可以实现MCP Server或Client,无需授权费
生态效应 社区可以共享插件,类似VSCode扩展市场
传输灵活性 支持stdio、SSE、HTTP Stream,适应不同部署场景
安全可控 权限由客户端管理,服务器不直接暴露给LLM
动态发现 LLM可通过tools/list等接口自动发现可用能力
热插拔 协议支持运行时添加/移除工具和资源

MCP生态:Server端(本项目)/ Client端(Claude Desktop等)

┌─────────────────────────────────────────────────────────┐
│                     MCP 生态                             │
│                                                          │
│  ┌─────────────────┐          ┌──────────────────────┐  │
│  │  MCP Client 端   │  JSON-RPC │  MCP Server 端       │  │
│  │                  │<────────>│                      │  │
│  │  Claude Desktop  │  2.0     │  本项目 (C++ Server) │  │
│  │  Zed Editor      │          │  社区 Python Server  │  │
│  │  Sourcegraph     │          │  社区 Node.js Server │  │
│  │  Continue.dev    │          │  ...                 │  │
│  └─────────────────┘          └──────────────────────┘  │
└─────────────────────────────────────────────────────────┘

Client端职责

  • 发现并连接MCP Server
  • 发起 initialize 握手,协商协议版本和能力
  • 将Server暴露的Tools注册给LLM
  • 代理LLM的Tool调用请求到Server
  • 管理权限和安全性

Server端职责(本项目)

  • 实现JSON-RPC 2.0消息处理(src/server/Server.cpp
  • 通过插件系统暴露Tools/Prompts/Resources(src/loader/PluginsLoader.cpp
  • 支持多种传输方式:stdio、SSE、HTTP Stream(src/transport/
  • 管理插件热加载和生命周期(src/loader/PluginsLoader.h

本项目的传输方式通过命令行参数切换(src/main.cpp 第120-126行):

  • 默认使用 stdio(标准输入输出),适用于本地进程通信
  • -s 参数启用 SSE Server,适用于Web/远程通信
  • -t 参数启用 HTTP Stream Server

JSON-RPC 2.0详解

MCP协议的通信层基于JSON-RPC 2.0规范。这是一种轻量级的远程过程调用协议,使用JSON格式编码消息。

JSON-RPC 2.0 四种消息类型

JSON-RPC 2.0定义了四种标准消息类型。所有消息都是JSON对象,必须包含 "jsonrpc": "2.0" 字段。

Request(请求)

Request是客户端向服务器发起的调用请求。必须包含唯一的 id 字段用于匹配响应。

{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "tools/call",
    "params": {
        "name": "get_weather",
        "arguments": {
            "city": "北京",
            "latitude": "39.9042",
            "longitude": "116.4074"
        }
    }
}

字段说明

字段 类型 必需 说明
jsonrpc string 固定为 “2.0”
id number/string 请求标识符,用于匹配Response
method string 调用的方法名(如 “tools/call”)
params object/array 方法参数
Response(成功响应)

当请求处理成功时,服务器返回Response。id 必须与对应Request的 id 匹配。

{
    "jsonrpc": "2.0",
    "id": 1,
    "result": {
        "content": [
            {"type": "text", "text": "北京今天天气晴朗,气温25°C。"}
        ],
        "isError": false
    }
}

本项目的实现src/utils/MCPBuilder.h 第45-51行

// MCPBuilder.h 第45-51行
static json Response(json request) {
    json response;
    response["jsonrpc"] = "2.0";
    response["id"] = request["id"];  // 回传请求id
    response["result"] = json::object();  // 初始化为空对象
    return response;
}
Error(错误响应)

当请求处理失败时,服务器返回Error。error对象必须包含 codemessage

{
    "jsonrpc": "2.0",
    "id": 1,
    "error": {
        "code": -32601,
        "message": "Method not found"
    }
}

JSON-RPC 2.0 标准错误码

错误码 常量名 含义
-32700 ParseError JSON解析失败(格式错误)
-32600 InvalidRequest 请求对象无效(如缺少method字段)
-32601 MethodNotFound 请求的方法不存在
-32602 InvalidParams 方法参数无效
-32603 InternalError 服务器内部错误
-32000 ~ -32099 (保留) 服务器自定义错误

本项目的错误码定义src/utils/MCPBuilder.h 第37-43行

// MCPBuilder.h 第37-43行
enum ErrorCode {
    ParseError = -32700,         // JSON解析错误
    InvalidRequest = -32600,     // 请求无效
    MethodNotFound = -32601,     // 方法未找到
    InvalidParams = -32602,      // 参数无效
    InternalError = -32603       // 内部错误
};

项目的Error构建方法src/utils/MCPBuilder.h 第53-59行

// MCPBuilder.h 第53-59行
static json Error(ErrorCode code, const std::string& id, const std::string &message) {
    return {
            {"jsonrpc", "2.0"},
            {"error", {{"code", code}, {"message", message}}},
            {"id", id}
    };
}

项目中的实际错误使用src/server/Server.cpp

  • 第273行:缺少method字段时返回 InvalidRequest 错误
if (!request.contains("method")) {
    return MCPBuilder::Error(MCPBuilder::InvalidRequest, request["id"], "Missing method");
}
  • 第293行:方法未注册时返回 MethodNotFound 错误
return MCPBuilder::Error(MCPBuilder::MethodNotFound, std::to_string(id), "Method not found");
  • 第434行:resources/subscribe 未实现时返回MethodNotFound
  • 第439行:resources/unsubscribe 未实现时返回MethodNotFound
  • 第451-463行:prompts/getlogging/setLevelcompletion/completeroots/list 默认返回MethodNotFound
Notification(通知)

Notification是没有 id 字段的Request。服务器收到后不需要响应。用于单向事件推送(进度、日志、状态变更)。

{
    "jsonrpc": "2.0",
    "method": "notifications/progress",
    "params": {
        "progressToken": "abc123",
        "progress": 50,
        "total": 100,
        "message": "Processing..."
    }
}

关键特征:没有 id 字段 —— 这是区分Notification和Request的唯一方式。

本项目中的Notification处理src/server/Server.cpp 第466-500行。所有通知处理方法都返回 nullptr(即不发送响应)。

项目中的JSON-RPC实际使用

HandleRequest —— 请求分发中枢

src/server/Server.cpp 第263-294行的 HandleRequest 方法是整个服务器的请求入口:

// Server.cpp 第263-294行
json Server::HandleRequest(const json &request) {
    // 1. 校验:必须有method字段
    if (!request.contains("method")) {
        return MCPBuilder::Error(MCPBuilder::InvalidRequest,
                                 request["id"], "Missing method");
    }

    // 2. 通过functionMap查找并执行处理器
    std::string methodName = request["method"];
    auto it = functionMap.find(methodName);
    if (it != functionMap.end()) {
        json response = it->second(request);
        // 如果响应为nullptr(通知),不发送
        if (response != nullptr) { /* 发送响应 */ }
        return response;
    }

    // 3. 方法未找到
    return MCPBuilder::Error(MCPBuilder::MethodNotFound,
                             std::to_string(id), "Method not found");
}
functionMap —— 方法注册表

src/server/Server.cpp 第34-58行的 functionMap 是核心方法路由表,将JSON-RPC method字符串映射到实际的处理函数:

// Server.cpp 第35-57行
functionMap = {
    {"initialize",                              → InitializeCmd},
    {"ping",                                    → PingCmd},
    {"tools/list",                              → ToolsListCmd},
    {"tools/call",                              → ToolsCallCmd},
    {"resources/list",                          → ResourcesListCmd},
    {"resources/read",                          → ResourcesReadCmd},
    {"resources/subscribe",                     → ResourcesSubscribeCmd},
    {"resources/unsubscribe",                   → ResourcesUnsubscribeCmd},
    {"prompts/list",                            → PromptsListCmd},
    {"prompts/get",                             → PromptsGetCmd},
    {"logging/setLevel",                        → LoggingSetLevelCmd},
    {"completion/complete",                     → CompletionCompleteCmd},
    {"roots/list",                              → RootsListCmd},
    // ... 通知类型
    {"notifications/initialized",               → NotificationInitializedCmd},
    {"notifications/cancelled",                 → NotificationCancelledCmd},
    {"notifications/progress",                  → NotificationProgressCmd},
    // ...
};
MCPBuilder —— 完整的消息构建工具集

src/utils/MCPBuilder.h 提供了所有MCP协议消息类型的构建方法:

方法 行号 用途
Response() 45-51 构建成功响应
Error() 53-59 构建错误响应
TextContent() 61-66 构建文本内容块
ImageContent() 68-75 构建图片内容块(Base64编码)
AudioContent() 77-84 构建音频内容块(Base64编码)
ResourceText() 86-92 构建资源文本内容
NotificationLog() 94-100 构建日志通知
NotificationProgress() 102-108 构建进度通知
NotificationToolsListChanged() 111-116 构建工具列表变更通知
NotificationPromptsListChanged() 118-123 构建提示列表变更通知
NotificationResourcesListChanged() 125-130 构建资源列表变更通知

MCP协议核心方法

MCP协议定义了一系列标准方法(method),分为请求(Request)和通知(Notification)两类。以下分析所有核心方法和本项目中的实现。

initialize —— 能力协商握手

用途:客户端连接后必须首先调用的方法,用于协商协议版本和能力集。

请求参数

{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "initialize",
    "params": {
        "protocolVersion": "2024-11-05",
        "capabilities": { /* 客户端能力 */ },
        "clientInfo": {
            "name": "Claude Desktop",
            "version": "1.0.0"
        }
    }
}

服务器响应(包含服务器的能力声明):

{
    "jsonrpc": "2.0",
    "id": 1,
    "result": {
        "protocolVersion": "2024-11-05",
        "capabilities": {
            "tools": {"listChanged": true},
            "prompts": {"listChanged": true},
            "resources": {"subscribe": true, "listChanged": true},
            "logging": {}
        },
        "serverInfo": {
            "name": "mcp-server",
            "version": "0.8.0"
        }
    }
}

项目实现src/server/Server.cpp 第304-385行的 InitializeCmd 方法。这个方法是最复杂的处理器之一,完整解析客户端发送的参数(rootUri、rootPath、initializationOptions、capabilities、trace、workspaceFolders),并在第374-384行构建响应声明服务器的能力集。

// Server.cpp 第374-384行 —— 响应构建核心代码
response["result"]["protocolVersion"] = request["params"]["protocolVersion"];
response["result"]["capabilities"]["tools"] = json::object({{"listChanged", true}});
response["result"]["capabilities"]["prompts"] = json::object({{"listChanged", true}});
response["result"]["capabilities"]["resources"]["subscribe"] = true;
response["result"]["capabilities"]["resources"]["listChanged"] = true;
response["result"]["capabilities"]["logging"] = json::object();
response["result"]["serverInfo"]["name"] = name_;
response["result"]["serverInfo"]["version"] = PROJECT_VERSION;

tools/list —— 列出所有可用工具

用途:让客户端(LLM)发现服务器提供的所有工具及其参数定义。

请求

{"jsonrpc": "2.0", "id": 2, "method": "tools/list", "params": {}}

响应示例

{
    "jsonrpc": "2.0",
    "id": 2,
    "result": {
        "tools": [
            {
                "name": "get_weather",
                "description": "获取指定城市的天气预报",
                "inputSchema": {
                    "type": "object",
                    "properties": {
                        "city": {"type": "string"},
                        "latitude": {"type": "string"},
                        "longitude": {"type": "string"}
                    },
                    "required": ["city", "latitude", "longitude"]
                }
            }
        ]
    }
}

项目实现:Server.cpp第407-413行的默认实现返回空数组。在 main.cpp 第194-214行通过 OverrideCallback 被替换为实际从插件中收集所有 PLUGIN_TYPE_TOOLS 类型的工具:

// main.cpp 第194-214行
server->OverrideCallback("tools/list", [](const json& request) {
    nlohmann::ordered_json response = MCPBuilder::Response(request);
    response["result"]["tools"] = json::array();

    auto plugins = loader->GetPluginsSnapshot();  // 获取插件快照
    for (const auto& plugin : plugins) {
        if (plugin->instance->GetType() == PLUGIN_TYPE_TOOLS) {
            for (int i = 0; i < plugin->instance->GetToolCount(); i++) {
                nlohmann::ordered_json tool;
                auto pluginTool = plugin->instance->GetTool(i);
                tool["name"] = pluginTool->name;
                tool["description"] = pluginTool->description;
                tool["inputSchema"] = json::parse(pluginTool->inputSchema);
                response["result"]["tools"].push_back(tool);
            }
        }
    }
    return response;
});

tools/call —— 调用指定工具

用途:让客户端触发具体的工具执行。

请求

{
    "jsonrpc": "2.0",
    "id": 3,
    "method": "tools/call",
    "params": {
        "name": "get_weather",
        "arguments": {"city": "北京", "latitude": "39.9042", "longitude": "116.4074"}
    }
}

响应(MCP规范定义的内容格式):

{
    "jsonrpc": "2.0",
    "id": 3,
    "result": {
        "content": [
            {"type": "text", "text": "北京今天天气晴朗,25°C。"}
        ],
        "isError": false
    }
}

项目实现src/server/Server.cpp 第415-430行默认实现返回占位提示。在 main.cpp 第215-245行通过 OverrideCallback 替换为实际执行逻辑:

// main.cpp 第215-245行 —— 核心逻辑
server->OverrideCallback("tools/call", [](const json& request) {
    // ...
    for (const auto& plugin : plugins) {
        if (plugin->instance->GetType() == PLUGIN_TYPE_TOOLS) {
            for (int i = 0; i < plugin->instance->GetToolCount(); i++) {
                auto pluginTool = plugin->instance->GetTool(i);
                if (pluginTool->name == request["params"]["name"]) {
                    // 找到匹配的工具,调用插件的HandleRequest
                    char* res_ptr = plugin->instance->HandleRequest(request.dump().c_str());
                    // ...
                    response["result"] = json::parse(res_ptr);
                    response["result"]["isError"] = false;
                    delete[] res_ptr;
                    return response;
                }
            }
        }
    }
});

内容类型:MCP支持三种内容块的构建(MCPBuilder.h):

  • TextContent()(第61-66行)—— 纯文本
  • ImageContent()(第68-75行)—— 图片(Base64编码)
  • AudioContent()(第77-84行)—— 音频(Base64编码)

prompts/list —— 列出所有可用提示模板

用途:列出服务器提供的所有预定义提示模板及其参数。

响应

{
    "result": {
        "prompts": [
            {
                "name": "code-review",
                "description": "让LLM分析代码质量并提出改进建议",
                "arguments": [
                    {
                        "name": "language",
                        "description": "代码的编程语言",
                        "required": true
                    }
                ]
            }
        ]
    }
}

项目实现main.cpp 第246-265行,遍历所有 PLUGIN_TYPE_PROMPTS 类型的插件收集提示模板。

prompts/get —— 获取指定提示模板

用途:获取特定提示模板的完整消息内容(含参数填充)。

请求

{
    "method": "prompts/get",
    "params": {
        "name": "code-review",
        "arguments": {"language": "C++"}
    }
}

响应返回messages数组,包含role和content。

项目实现main.cpp 第266-291行,匹配提示名称后调用插件的 HandleRequest

resources/list —— 列出所有可读资源

用途:列出服务器提供的所有只读数据资源(URI列表)。

响应

{
    "result": {
        "resources": [
            {
                "name": "bacio-quote",
                "description": "A list of the famous italian bacio perugina quotes",
                "uri": "bacio:///quote",
                "mimeType": "text/plain"
            }
        ]
    }
}

项目实现main.cpp 第292-312行,收集所有 PLUGIN_TYPE_RESOURCES 类型插件的资源。

resources/read —— 读取指定URI资源

用途:读取指定URI的资源内容(文本或二进制)。

请求

{
    "method": "resources/read",
    "params": {"uri": "bacio:///quote"}
}

响应返回contents数组,每个元素包含uri、mimeType和实际内容(text字段用于文本,blob字段用于二进制)。

项目实现main.cpp 第313-337行,匹配URI后调用插件的 HandleRequest

resources/subscribe 和 resources/unsubscribe

用途:客户端订阅或取消订阅特定URI的资源变更通知。

项目实现src/server/Server.cpp 第432-440行 —— 这两个方法目前标记为"NOT YET IMPLEMENTED",返回MethodNotFound错误。

ping —— 心跳检测

用途:用于检测连接是否存活。

项目实现src/server/Server.cpp 第387-393行

json Server::PingCmd(const json &request) {
    nlohmann::ordered_json response = {};
    response["jsonrpc"] = "2.0";
    response["id"] = request["id"];
    response["result"] = json::object();  // 空对象表示成功
    return response;
}

logging/setLevel —— 设置日志级别

用途:客户端请求服务器调整日志详细程度。

项目实现src/server/Server.cpp 第454-456行 —— 当前返回MethodNotFound(未实现)。

completion/complete —— 自动补全

用途:为提示参数和资源URI模板提供自动补全候选项。

项目实现src/server/Server.cpp 第458-460行 —— 当前返回MethodNotFound(未实现)。

roots/list —— 根URI列表

用途:提示服务器哪些根URI是客户端当前关心的。

项目实现src/server/Server.cpp 第462-464行 —— 当前返回MethodNotFound(未实现)。


MCP通知机制

MCP通知(Notification)是没有 id 字段的JSON-RPC消息,服务器主动推送,客户端不回复。本项目中共有9种通知类型。

notifications/initialized

用途:客户端发送给服务器,告知初始化完成,可以开始正常通信。这是initialize握手的最后一步。

项目实现src/server/Server.cpp 第466-468行 —— 收到后返回 nullptr(无响应)。

json Server::NotificationInitializedCmd(const json &request) {
    return nullptr;  // 通知不需要响应
}

notifications/progress —— 带进度令牌的进度通知

用途:在执行长时间任务时,服务器向客户端报告进度。

有效载荷

{
    "jsonrpc": "2.0",
    "method": "notifications/progress",
    "params": {
        "progressToken": "abc123",
        "progress": 50,
        "total": 100,
        "message": "正在处理第5/10项..."
    }
}

Builder构建方法src/utils/MCPBuilder.h 第102-108行

static json NotificationProgress(const std::string& message,
     const std::string& progressToken, const int progress, const int total) {
    return json::object({
        {"jsonrpc", "2.0"},
        {"method","notifications/progress"},
        {"params", {
            {"progressToken", progressToken},
            {"progress", progress},
            {"total", total},
            {"message", message}
        }}
    });
}

实际使用:Notification插件(plugins/notification/Notification.cpp 第78-115行)。在 progress_test 工具中,它模拟一个10秒的长任务,每秒发送一次进度通知:

// Notification.cpp 第101-113行
for (int i = 1; i <= totalDuration; i++) {
    std::this_thread::sleep_for(std::chrono::seconds(1));
    int progressPercent = (i * 100) / totalDuration;
    std::string progressMessage = "Progress: " + std::to_string(progressPercent) + "%";
    std::string message = MCPBuilder::NotificationProgress(
        progressMessage, progressToken, progressPercent, 100).dump();
    g_plugin->notifications->SendToClient(GetNameImpl(), message.c_str());
}

notifications/message —— 日志消息推送

用途:服务器向客户端发送日志消息,支持多个级别(debug、info、notice、warning、error等)。

Builder构建方法src/utils/MCPBuilder.h 第94-100行

static json NotificationLog(const std::string& level, const std::string& data) {
    return json::object({
        {"jsonrpc", "2.0"},
        {"method","notifications/message"},
        {"params", { {"level",level}, {"data", data} }}
    });
}

实际使用:Notification插件第74-76行。在 logging_test 工具中发送测试日志。

notifications/tools/list_changed —— 工具列表变更

用途:当服务器的工具列表发生变化时(如插件热加载),通知客户端重新调用 tools/list

Builder构建方法src/utils/MCPBuilder.h 第111-116行

static json NotificationToolsListChanged() {
    return json::object({
        {"jsonrpc", "2.0"},
        {"method", "notifications/tools/list_changed"}
    });
}

触发时机main.cpp 第165-179行,插件热加载后自动发送:

loader->SetOnPluginsChanged([](bool toolsChanged, bool promptsChanged, bool resourcesChanged) {
    if (server && server->IsValid()) {
        if (toolsChanged) {
            server->SendNotification("mcp-server",
                MCPBuilder::NotificationToolsListChanged().dump().c_str());
        }
        // ... promptsChanged, resourcesChanged 同理
    }
});

notifications/prompts/list_changed —— 提示列表变更

用途:提示模板列表变化通知。构建方法:MCPBuilder.h 第118-123行。

notifications/resources/list_changed —— 资源列表变更

用途:资源列表变化通知。构建方法:MCPBuilder.h 第125-130行。

notifications/cancelled

用途:客户端通知服务器取消正在进行的操作。

项目实现Server.cpp 第470-472行 —— 返回nullptr(无响应)。

notifications/roots/list_changed

用途:客户端通知服务器根URI列表发生了变化。

项目实现Server.cpp 第478-480行 —— 返回nullptr。

notifications/resources/updated

用途:通知客户端特定URI的资源内容已更新。

项目实现Server.cpp 第482-484行 —— 返回nullptr。

通知发送机制分析

项目的通知发送通过以下组件协作完成:

  1. 插件通知网关:插件通过 NotificationSystem.SendToClient 回调发送(PluginAPI.h 第66-67行)
  2. Bridge回调main.cpp 第61-66行的 ClientNotificationCallbackImpl 桥接
  3. 通知队列Server.h 第114行的 notification_queue_ —— 线程安全的生产者-消费者队列
  4. Writer线程Server.cpp 第65-105行的 WriterLoop —— 从队列取出通知并写入transport
插件 → SendToClient() → ClientNotificationCallbackImpl() → Server::SendNotification()
                                                              ↓
                                                        notification_queue_
                                                              ↓
                                                        WriterLoop线程
                                                              ↓
                                                        Transport::Write()

MCP能力协商

服务器capabilities结构详解

能力协商(Capability Negotiation)是MCP协议中的关键机制。服务器在 initialize 响应中声明自己支持哪些功能,客户端据此决定后续可以调用哪些方法。

本项目的capabilities声明Server.cpp 第377-381行

// 服务器声明五种能力:
response["result"]["capabilities"]["tools"]     = { {"listChanged", true} };
response["result"]["capabilities"]["prompts"]   = { {"listChanged", true} };
response["result"]["capabilities"]["resources"]["subscribe"]   = true;
response["result"]["capabilities"]["resources"]["listChanged"] = true;
response["result"]["capabilities"]["logging"]   = json::object();

每项能力的具体含义:

能力声明 含义
tools: {listChanged: true} 服务器支持tools/list_changed通知,工具列表可能动态变化
prompts: {listChanged: true} 服务器支持prompts/list_changed通知,提示列表可能动态变化
resources: {subscribe: true} 服务器支持资源订阅功能
resources: {listChanged: true} 服务器支持resources/list_changed通知
logging: {} 服务器支持日志功能(logging/setLevel方法)

Server.h中的Capabilities枚举src/server/Server.h 第41-45行

enum Capabilities {
    RESOURCES = 0 << 1,  // 位0:资源能力
    TOOLS     = 0 << 2,  // 位1:工具能力
    PROMPTS   = 0 << 3,  // 位2:提示能力
};

协议版本协商

initialize 请求和响应中都有 protocolVersion 字段。当前MCP协议版本为 "2024-11-05"(以发布日期命名)。

服务器在响应中回传客户端请求的协议版本号(Server.cpp 第376行):

response["result"]["protocolVersion"] = request["params"]["protocolVersion"];

如果服务器不支持客户端请求的协议版本,应返回错误。

ServerInfo元数据

服务器在 initialize 响应中提供自身元数据(Server.cpp 第382-383行):

response["result"]["serverInfo"]["name"]    = name_;        // 从命令行 -n 参数获取,默认 "mcp-server"
response["result"]["serverInfo"]["version"] = PROJECT_VERSION; // 来自 build/version.h,当前为 "0.8.0"

完整握手流程分析

一个完整的MCP连接生命周期如下:

                        MCP Client                     MCP Server
                        (Claude Desktop)              (本项目)
                            │                              │
    ┌───────── 连接建立 ──────────────────────────────────────┐
    │                       │                              │
    │   stdio/SSE连接       │ ───── TCP/进程通信 ──────→   │  启动 Server
    │                       │                              │
    ├───────── 能力协商 ──────────────────────────────────────┤
    │                       │                              │
    │   ① initialize        │ ─── Request(id=1) ──────→   │  Server.cpp:304
    │                       │                              │  解析客户端能力
    │                       │                              │  声明服务器能力
    │                       │ ←── Response(id=1) ───────   │  返回 capabilities
    │                       │                              │
    │   ② initialized       │ ─── Notification ───────→   │  Server.cpp:466
    │                       │                              │  返回 nullptr
    │                       │                              │
    ├───────── 发现阶段 ──────────────────────────────────────┤
    │                       │                              │
    │   ③ tools/list        │ ─── Request(id=2) ──────→   │  main.cpp:194
    │                       │                              │  遍历工具插件
    │                       │ ←── Response(id=2) ───────   │  返回工具列表
    │                       │                              │
    │   ④ prompts/list      │ ─── Request(id=3) ──────→   │  main.cpp:246
    │                       │                              │  遍历提示插件
    │                       │ ←── Response(id=3) ───────   │  返回提示列表
    │                       │                              │
    │   ⑤ resources/list    │ ─── Request(id=4) ──────→   │  main.cpp:292
    │                       │                              │  遍历资源插件
    │                       │ ←── Response(id=4) ───────   │  返回资源列表
    │                       │                              │
    ├───────── 工作阶段 ──────────────────────────────────────┤
    │                       │                              │
    │   ⑥ tools/call        │ ─── Request(id=5) ──────→   │  main.cpp:215
    │   (LLM决定调用工具)    │                              │  插件HandleRequest
    │                       │ ←── Response(id=5) ───────   │  返回执行结果
    │                       │                              │
    │   ⑦ (可选) 通知       │ ←── Notification ─────────   │  进度/日志/变更
    │                       │                              │
    │   ⑧ ping              │ ─── Request(id=N) ──────→   │  Server.cpp:387
    │   (心跳保活)           │ ←── Response(id=N) ───────   │  返回空result
    │                       │                              │
    ├───────── 终止 ──────────────────────────────────────────┤
    │                       │                              │
    │   连接断开/Ctrl+C      │                              │  Stop/StopAsync
    │                       │                              │  清理插件和线程
    └───────────────────────────────────────────────────────┘

关键代码路径:

  • 连接建立:main.cpp 第339行 server->Connect(transport)
  • 连接循环:Server.cpp 第127-154行 while (!isStopping_) 循环
  • 通知处理:Server.cpp 第65-105行 WriterLoop
  • 服务器停止:Server.cpp 第220-243行 Stop()

提示工程基础

提示模板的JSON结构

MCP的Prompt(提示模板)是一个预定义的、参数化的对话模板。其核心结构定义在 PluginAPI.h 第52-56行:

typedef struct {
    const char* name;         // 提示模板名称
    const char* description;  // 模板描述
    const char* arguments;    // JSON格式的参数定义(JSON数组字符串)
} PluginPrompt;

当客户端调用 prompts/get 获取完整提示内容时,服务器返回:

{
    "description": "模板的描述文本",
    "messages": [
        {
            "role": "user",
            "content": {
                "type": "text",
                "text": "实际的消息内容,参数已被替换"
            }
        }
    ]
}

messages数组格式(role: system/user/assistant)

Messages数组是提示模板的核心内容。每个消息对象包含两个字段:

字段 类型 说明
role string 消息角色:"system""user""assistant"
content object 消息内容,包含 type 和实际内容(text/image/audio)

三种角色

  • system:设定AI的行为规则、专业领域、输出格式。权重最高。
  • user:用户的实际输入或问题。
  • assistant:之前AI的回复(用于多轮对话上下文或few-shot示例)。
// 一个包含三种角色的提示模板
{
    "messages": [
        {
            "role": "system",
            "content": {
                "type": "text",
                "text": "你是一个专业的C++代码审查助手。回答要简洁、技术精确。"
            }
        },
        {
            "role": "user",
            "content": {
                "type": "text",
                "text": "请审查以下C++代码的质量:\n[代码内容]"
            }
        },
        {
            "role": "assistant",
            "content": {
                "type": "text",
                "text": "以下是代码审查结果:\n1. 内存管理..."
            }
        }
    ]
}

内容类型:Message content支持三种类型(对应MCPBuilder.h的构建方法):

  • "type": "text" —— 纯文本内容(MCPBuilder::TextContent()
  • "type": "image" —— 图片内容,data字段为Base64编码(MCPBuilder::ImageContent()
  • "type": "audio" —— 音频内容,data字段为Base64编码(MCPBuilder::AudioContent()

参数化提示

提示模板支持参数替换。在模板定义中声明参数,运行时用户填入具体值。

参数定义格式(JSON数组):

[
    {
        "name": "language",
        "description": "代码的编程语言",
        "required": true
    }
]

CodeReview插件的参数使用plugins/code-review/CodeReview.cpp 第36-41行):

{
    "code-review",
    "Asks the LLM to analyze code quality and suggest improvements",
    R"([{
        "name" : "language",
        "description" : "The programming language of the code",
        "required": true
    }])"
}

HandleRequestImpl 中获取参数值(第53-54行):

auto request = json::parse(req);
auto language = request["params"]["arguments"]["language"].get<std::string>();

然后拼接到提示文本中(第62行):

{"text", "Please analyze code quality and suggest improvements of this code written in " + language}

CodeReview插件详解

plugins/code-review/CodeReview.cpp 是一个完整的提示模板类型插件示例。

插件结构分析(按代码行号):

  1. 模板定义(第32-42行):
static PluginPrompt prompts[] = {
    {
        "code-review",                                    // name
        "Asks the LLM to analyze code quality...",        // description
        R"([{"name":"language","description":"...","required":true}])" // arguments (JSON数组)
    }
};
  1. 插件元数据(第44-46行):
const char* GetNameImpl() { return "code-review"; }
const char* GetVersionImpl() { return "1.0.0"; }
PluginType GetTypeImpl() { return PLUGIN_TYPE_PROMPTS; }  // 声明为提示类型
  1. 请求处理(第52-76行)—— 核心逻辑:
char* HandleRequestImpl(const char* req) {
    auto request = json::parse(req);
    auto language = request["params"]["arguments"]["language"].get<std::string>();

    // 构建响应
    nlohmann::json response = json::object();
    nlohmann::json messages = json::array();
    messages.push_back(json::object({
        {"role", "user"},       // 使用user角色
        {"content", json::object({
            {"type", "text"},   // 文本类型内容
            {"text", "Please analyze code quality and suggest improvements
                      of this code written in " + language}
        })}
    }));
    response["description"] = "this is the code review prompt";
    response["messages"] = messages;

    // 转为C字符串返回 (MCP插件接口约定)
    std::string result = response.dump();
    char* buffer = new char[result.length() + 1];
    strcpy_s(buffer, result.length() + 1, result.c_str());
    return buffer;
}

返回的消息将被客户端合并到LLM的对话上下文中。LLM会收到一条 role: "user" 的消息:“Please analyze code quality and suggest improvements of this code written in C++”,然后基于此进行分析。

设计要点

  • 提示模板是"无副作用"的——只返回消息文本,不修改服务器状态
  • 参数支持类型化定义(required、description等元数据)
  • 消息内容支持多模态(text/image/audio)
  • 返回的消息role可以是system或user(本项目中使用user)

资源URI方案

URI格式:协议://主机/路径

URI(Uniform Resource Identifier,统一资源标识符)是标识资源的字符串。MCP协议中使用URI来标识和访问服务器提供的资源。

标准URI格式:

scheme://authority/path?query#fragment

示例:
  https://example.com/path/to/file      (HTTP URI)
  file:///home/user/document.txt        (文件URI)
  bacio:///quote                        (MCP自定义URI)

MCP中的资源URI:bacio:///quote

MCP资源URI使用自定义scheme,格式为 scheme:///path。注意三个斜杠——这是"无authority"URI的标准写法。

BacioQuote插件的资源定义plugins/bacio-quote/BacioQuote.cpp 第183-190行):

static PluginResource resources[] = {
    {
        "bacio-quote",                                   // name:资源名称
        "A list of the famous italian bacio perugina quotes", // description
        "bacio:///quote",                                // uri:资源URI
        "text/plain",                                    // mime:MIME类型
    }
};

这里定义了一个URI为 bacio:///quote 的资源:

  • scheme: bacio
  • authority: (空,使用三个斜杠表示)
  • path: quote

资源内容类型(MIME Type)

资源可以是文本或二进制,通过MIME类型声明:

MIME Type 说明 内容载体
text/plain 纯文本 text 字段
text/html HTML文本 text 字段
text/markdown Markdown文本 text 字段
image/png PNG图片 blob 字段(Base64)
image/jpeg JPEG图片 blob 字段(Base64)
audio/wav WAV音频 blob 字段(Base64)
application/json JSON数据 text 字段

资源内容结构(由 MCPBuilder::ResourceText 构建):

{
    "contents": [
        {
            "uri": "bacio:///quote",
            "mimeType": "text/plain",
            "text": "Amore e tosse non si possono nascondere. (Ovidio)"
        }
    ]
}

BacioQuote插件的资源读取(第200-223行):

char* HandleRequestImpl(const char* req) {
    auto request = json::parse(req);
    nlohmann::json response = json::object();

    // 随机选择一条名言
    std::random_device rd;
    std::mt19937 gen(rd());
    std::uniform_int_distribution<> distr(0, messages.size() - 1);

    // 使用 MCPBuilder::ResourceText 构建资源内容
    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;
    // ... 返回结果
}

每次调用 resources/read 读取 bacio:///quote 时,服务器都会从178条意大利名言中随机选取一条返回(通过 std::mt19937 随机数生成器)。

资源的订阅与更新通知

MCP协议支持资源订阅机制,允许客户端订阅特定URI的变更通知。

资源结构定义PluginAPI.h 第58-63行):

typedef struct {
    const char* name;         // 资源名称
    const char* description;  // 资源描述
    const char* uri;          // 资源URI
    const char* mime;         // MIME类型
} PluginResource;

订阅相关方法

  • resources/subscribeServer.cpp 第432-434行):客户端订阅资源变更
  • resources/unsubscribe(第437-439行):取消订阅
  • notifications/resources/updated(第482-484行):资源内容变更后通知订阅者
  • notifications/resources/list_changed(第479-481行):资源列表变化通知

在本项目当前版本中,subscribe/unsubscribe标记为"未实现",但框架已预留接口。热加载机制已完整实现资源列表变更通知 —— 当 ScanForChanges 检测到新资源插件加载或旧资源插件卸载时,通过 SetOnPluginsChanged 回调(main.cpp 第165-179行)自动发送 notifications/resources/list_changed 通知。

插件的资源类型注册

插件加载器通过 PluginAPI.GetType() 判断插件类型。PluginType 枚举(PluginAPI.h 第40-44行):

typedef enum {
    PLUGIN_TYPE_TOOLS     = 0,  // 工具类插件
    PLUGIN_TYPE_PROMPTS   = 1,  // 提示模板类插件
    PLUGIN_TYPE_RESOURCES = 2   // 资源类插件
} PluginType;

main.cpp 中,不同的 overrideCallback 各自只处理对应类型的插件:

  • tools/list(第194行):只处理 PLUGIN_TYPE_TOOLS
  • prompts/list(第246行):只处理 PLUGIN_TYPE_PROMPTS
  • resources/list(第292行):只处理 PLUGIN_TYPE_RESOURCES

这种按类型隔离的设计确保了每个插件只贡献其声明的能力类型,不会跨类型混淆。

资源URI方案扩展建议

虽然本项目当前只有 bacio:///quote 一个资源URI示例,但MCP协议的URI方案可以根据需求扩展:

bacio:///quote                → 文本资源(名言引用)
weather:///forecast/beijing   → 天气数据资源
file:///home/user/doc.txt     → 本地文件资源
db:///query/users             → 数据库查询资源
api:///github/issues          → 外部API资源

每种URI scheme应该:

  1. PluginResource.uri 中正确定义
  2. PluginResource.mime 中声明正确的MIME类型
  3. HandleRequestImpl 中根据URI前缀分发处理逻辑

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐