【6G技术探索】MCP协议整理分享
博主未授权任何人或组织机构转载博主任何原创文章,感谢各位对原创的支持!
博主链接
本人就职于国际知名终端厂商,负责modem芯片研发。
在5G早期负责终端数据业务层、核心网相关的开发工作,目前牵头6G技术研究。
博客内容主要围绕:
5G/6G协议讲解
高级C语言讲解
Rust语言讲解
文章目录
- MCP协议整理
- 一、MCP是什么,为什么需要?
- 二、基础架构
- 三、客户端、服务端和LLM是如何通过MCP连接起来的?
- 3.1 核心组件介绍
- 3.1.1 协议层
- 3.1.2 传输层
- 3.1.3 消息类型
- 3.2 连接生命周期管理
- 3.2.1 初始化
- 3.2.2 消息交换
- 3.2.3 连接终止
- 3.3 错误处理
- 四、将资源注册到LLM
- 4.1 资源URI
- 4.2 资源类型
- 4.3 资源发现
- 4.4 获取资源
- 4.5 更新资源
- 五、prompt模版和工作流(用户去使用)
- 5.1 prompt结构
- 5.2 发现prompt
- 5.3 使用prompt
- 5.4 动态prompt
- 5.4.1 嵌入资源信息
- 5.4.2 多步骤工作流
- 5.5 prompt更新
- 5.6 完整的python用例
- 六、工具(LLM去调用)
- 6.1 工具的定义
- 6.1.1 系统操作工具示例
- 6.1.2 集成API工具示例
- 6.1.3 数据处理工具示例
- 6.2 工具的发现与更新
- 6.3 工具注释
- 七、Sampling:让服务端请求LLM完成工作(类似Agent)
- 7.1 Sampling工作原理
- 7.2 消息格式
- 7.2.1 请求消息
- 7.2.2 响应消息
- 八、roots功能
- 8.1 为什么使用roots?
- 8.2 roots如何工作?
MCP协议整理
一、MCP是什么,为什么需要?
MCP HomePage
MCP是一个开源协议,它标准化了应用程序如何为LLM提供上下文。将MCP视为AI应用程序的USB-C端口。正如USB-C提供了一种将设备连接到各种外设和配件的标准化方法一样,MCP提供了一种将AI模型连接到不同数据源和工具的标准化方法。
如果我们想通过llm构建Agent和复杂的工作流,首先要解决的是如何实现LLM与各种不同的数据源和工具间的通信问题,而MCP正好提供了这些功能,包括:
- 不断增长的适配了MCP的数据源和工具列表,可以直接plug into在你的LLM中;
- 可以灵活切换不同的LLM;
- 原生数据安全;
二、基础架构
MCP遵循客户端-服务器架构,其中主机应用程序可以连接到多个服务器,如下图所示:
- MCP Hosts:想要通过MCP访问数据的程序,如Claude Desktop、ide或AI工具;
- MCP Clients:与服务器保持1:1连接的MCP客户端;
- MCP Servers:通过标准化的MCP协议公开特定的功能;
- Local Data Sources:MCP服务器可以安全访问的计算机文件、数据库和服务;
- Remote Services:MCP服务器可以通过internet连接(例如通过api)到的外部系统;
三、客户端、服务端和LLM是如何通过MCP连接起来的?
模型上下文协议(MCP)建立在一个灵活的、可扩展的体系结构上,实现了LLM应用程序间的无缝集成和通信。MCP遵循客户端-服务端架构
,如下图所示,其中
- Host:是发起连接的LLM应用程序(例如Claude Desktop);
- Client:位于host应用程序内部,与Server之间是1:1的连接关系;
- Server:为Client提供context、tools和prompts;
3.1 核心组件介绍
3.1.1 协议层
协议层处理消息框架、请求/响应链,消息模版如下:
class Session(BaseSession[RequestT, NotificationT, ResultT]):async def send_request(self,request: RequestT,result_type: type[Result]) -> Result:"""Send request and wait for response. Raises McpError if response contains error."""# Request handling implementationasync def send_notification(self,notification: NotificationT) -> None:"""Send one-way notification that doesn't expect response."""# Notification handling implementationasync def _received_request(self,responder: RequestResponder[ReceiveRequestT, ResultT]) -> None:"""Handle incoming request from other side."""# Request handling implementationasync def _received_notification(self,notification: ReceiveNotificationT) -> None:"""Handle incoming notification from other side."""# Notification handling implementation
3.1.2 传输层
传输层处理客户机和服务器之间的实际通信。MCP支持多种传输机制:
- Stdio transport:使用标准输入/输出进行通信,适用范围如下,
- 构建命令行工具;
- 实现本地集成;
- 需要简单的进程通信;
- 使用shell脚本;
- Streamable HTTP transport:使用HTTP POST请求进行客户端到服务器通信,使用可选的服务器发送事件(Server-Sent Events, SSE)流进行服务器到客户端通信,适用范围如下,
- 构建基于web的集成;
- 需要通过HTTP进行客户机-服务器通信;
- 需要有状态会话;
- 支持多个并发客户端;
- 实现可恢复连接;
所有传输都使用JSON-RPC 2.0来交换消息。有关模型上下文协议消息格式的详细信息,请参阅规范。
3.1.3 消息类型
MCP有下面几种主要的消息类型:
- Request消息,期待对方的回应,其消息模版如下,
interface Request {method: string;params?: { ... }; }
- Result消息,Request消息的成功响应,其消息模版如下,
interface Result {[key: string]: unknown; }
- Error消息,Request消息的失败响应,其消息模版如下,
interface Error {code: number;message: string;data?: unknown; }
- Notification消息,不期望回复的单向消息,其消息模版如下,
interface Notification {method: string;params?: { ... }; }
3.2 连接生命周期管理
3.2.1 初始化
- 客户端发送带有协议版本和功能的初始化请求;
- 服务器响应其协议版本和功能;
- 客户端发送初始化通知作为确认;
- 正常消息交换开始;
3.2.2 消息交换
初始化后,支持以下模式:
- Request-Response:客户端或服务器发送请求,另一方响应;
- Notification:任何一方发送单向消息;
3.2.3 连接终止
任何一方都可以终止连接:
- 通过close()关闭;
- 连接断开;
- 发生错误;
3.3 错误处理
MCP定义了以下标准错误代码:
enum ErrorCode {// Standard JSON-RPC error codesParseError = -32700,InvalidRequest = -32600,MethodNotFound = -32601,InvalidParams = -32602,InternalError = -32603,
}
SDK和应用程序可以在-32000
以上定义自己的错误码。错误通过以下方式传播:
- 对请求消息的错误响应;
- 传输上的错误事件;
- 协议级错误处理;
四、将资源注册到LLM
资源是模型上下文协议(Model Context Protocol,MCP)中的核心原语,它允许服务器公开客户端可以读取的数据和内容,并将其用作LLM交互的上下文。资源可以是:
- 文件内容;
- 数据库记录;
- API的响应;
- 实时系统数据;
- 截图和图片;
- 日志文件;
每个资源由唯一的URI标识,可以包含文本或二进制数据。
4.1 资源URI
使用遵循以下格式的URI来标识资源:
[protocol]://[host]/[path]
例如,
file:///home/user/documents/report.pdf
postgres://database/customers/schema
screen://localhost/display1
协议和路径结构由MCP服务器实现定义。服务器可以自定义URI。
4.2 资源类型
资源可以包含两种类型的内容:
- 文本资源:包含UTF-8编码的文本数据,例如源代码、配置文件、日志文件、JSON和XML数据、纯文本;
- 二进制资源:包含以base64编码的原始二进制数据,例如图片、PDF文档、音频文件、视频文件、其他非文本格式;
4.3 资源发现
客户端主要可以通过两种方法发现可用资源:
- 直接配置资源:服务器通过
resources/list
端点公开一个具体的资源列表。每种资源包括,{uri: string; // Unique identifier for the resourcename: string; // Human-readable namedescription?: string; // Optional descriptionmimeType?: string; // Optional MIME typesize?: number; // Optional size in bytes }
- 资源模版:对于动态资源,服务器可以公开URI模板,客户端可以使用它来构造有效的资源URI,
{uriTemplate: string; // URI template following RFC 6570name: string; // Human-readable name for this typedescription?: string; // Optional descriptionmimeType?: string; // Optional MIME type for all matching resources }
4.4 获取资源
为了获取资源,客户端使用资源URI发起一个resources/read
请求。服务器使用一个资源列表来响应该请求消息:
{contents: [{uri: string; // The URI of the resourcemimeType?: string; // Optional MIME type// One of:text?: string; // For text resourcesblob?: string; // For binary resources (base64 encoded)}]
}
4.5 更新资源
MCP通过两种机制支持资源的实时更新:
- List changes:当可用资源列表发生变化时,服务器可以通过
notifications/resources/list_changed
通知消息,通知客户端; - Content changes:客户端可以订阅特定资源的更新,大致流程如下,
- 客户端发送包含资源URI的
resources/subscribe
订阅消息; - 当资源发生变化时,服务器发送
notifications/resources/updated
; - 客户端可以通过
resources/read
获取最新的内容; - 客户端可以发送
resources/unsubscribe
消息取消订阅;
- 客户端发送包含资源URI的
五、prompt模版和工作流(用户去使用)
创建可重用的提示模板和工作流,客户端可以轻松地将其呈现给用户和LLM。它们提供了一种强大的方式来标准化和共享常见的LLM交互。MCP中的prompt是预定义的模板,可以:
- 接受动态参数;
- 来自资源的上下文信息;
- 多轮交互链;
- 特定的工作流程;
- UI元素;
5.1 prompt结构
每一个prompt都按照下面的格式定义:
{name: string; // Unique identifier for the promptdescription?: string; // Human-readable descriptionarguments?: [ // Optional list of arguments{name: string; // Argument identifierdescription?: string; // Argument descriptionrequired?: boolean; // Whether argument is required}]
}
5.2 发现prompt
客户端可以通过发送prompts/list
请求消息发现可用的prompt,
// Request
{method: "prompts/list";
}// Response
{prompts: [{name: "analyze-code",description: "Analyze code for potential improvements",arguments: [{name: "language",description: "Programming language",required: true,},],},];
}
5.3 使用prompt
要使用prompt,客户端可以发出一个prompts/get
请求消息:
// Request
{method: "prompts/get",params: {name: "analyze-code",arguments: {language: "python"}}
}// Response
{description: "Analyze Python code for potential improvements",messages: [{role: "user",content: {type: "text",text: "Please analyze the following Python code for potential improvements:\n\n```python\ndef calculate_sum(numbers):\n total = 0\n for num in numbers:\n total = total + num\n return total\n\nresult = calculate_sum([1, 2, 3, 4, 5])\nprint(result)\n```"}}]
}
5.4 动态prompt
5.4.1 嵌入资源信息
在prompt中动态包含资源信息。
{"name": "analyze-project","description": "Analyze project logs and code","arguments": [{"name": "timeframe","description": "Time period to analyze logs","required": true},{"name": "fileUri","description": "URI of code file to review","required": true}]
}
在处理prompts/get
请求消息时:
{"messages": [{"role": "user","content": {"type": "text","text": "Analyze these system logs and the code file for any issues:"}},{"role": "user","content": {"type": "resource","resource": {"uri": "logs://recent?timeframe=1h","text": "[2024-03-14 15:32:11] ERROR: Connection timeout in network.py:127\n[2024-03-14 15:32:15] WARN: Retrying connection (attempt 2/3)\n[2024-03-14 15:32:20] ERROR: Max retries exceeded","mimeType": "text/plain"}}},{"role": "user","content": {"type": "resource","resource": {"uri": "file:///path/to/code.py","text": "def connect_to_service(timeout=30):\n retries = 3\n for attempt in range(retries):\n try:\n return establish_connection(timeout)\n except TimeoutError:\n if attempt == retries - 1:\n raise\n time.sleep(5)\n\ndef establish_connection(timeout):\n # Connection implementation\n pass","mimeType": "text/x-python"}}}]
}
5.4.2 多步骤工作流
const debugWorkflow = {name: "debug-error",async getMessages(error: string) {return [{role: "user",content: {type: "text",text: `Here's an error I'm seeing: ${error}`,},},{role: "assistant",content: {type: "text",text: "I'll help analyze this error. What have you tried so far?",},},{role: "user",content: {type: "text",text: "I've tried restarting the service, but the error persists.",},},];},
};
5.5 prompt更新
服务端可以通过下面的过程通知客户端更新prompt:
- 服务器具备
prompts.listChanged
能力; - 服务器向客户端发送
notifications/prompts/list_changed
通知消息; - 客户端重新获取prompt列表;
5.6 完整的python用例
from mcp.server import Server
import mcp.types as types# Define available prompts
PROMPTS = {"git-commit": types.Prompt(name="git-commit",description="Generate a Git commit message",arguments=[types.PromptArgument(name="changes",description="Git diff or description of changes",required=True)],),"explain-code": types.Prompt(name="explain-code",description="Explain how code works",arguments=[types.PromptArgument(name="code",description="Code to explain",required=True),types.PromptArgument(name="language",description="Programming language",required=False)],)
}# Initialize server
app = Server("example-prompts-server")@app.list_prompts()
async def list_prompts() -> list[types.Prompt]:return list(PROMPTS.values())@app.get_prompt()
async def get_prompt(name: str, arguments: dict[str, str] | None = None
) -> types.GetPromptResult:if name not in PROMPTS:raise ValueError(f"Prompt not found: {name}")if name == "git-commit":changes = arguments.get("changes") if arguments else ""return types.GetPromptResult(messages=[types.PromptMessage(role="user",content=types.TextContent(type="text",text=f"Generate a concise but descriptive commit message "f"for these changes:\n\n{changes}"))])if name == "explain-code":code = arguments.get("code") if arguments else ""language = arguments.get("language", "Unknown") if arguments else "Unknown"return types.GetPromptResult(messages=[types.PromptMessage(role="user",content=types.TextContent(type="text",text=f"Explain how this {language} code works:\n\n{code}"))])raise ValueError("Prompt implementation not found")
六、工具(LLM去调用)
工具是模型上下文协议(MCP)中的一个强大的原语,它使服务端能够向客户端公开可执行功能。通过工具,LLM可以与外部系统进行交互,执行计算,并在现实世界中采取行动。MCP中的工具允许服务端公开可执行的功能,这些功能可以被客户端调用,并被LLM用于执行操作。工具的关键点包括:
- 发现:客户端可以通过
tools/list
获取可用的工具; - 调用:使用
tools/call
调用工具,服务端执行请求的操作并返回结果; - 灵活性:工具的范围从简单的计算到复杂的API交互;
与资源一样,工具也通过唯一的名称进行标识,并可以包括指导其使用的描述。然而与资源不同的是工具表示可以修改状态或与外部系统交互的动态操作。
6.1 工具的定义
每个工具都用以下结构定义:
{name: string; // Unique identifier for the tooldescription?: string; // Human-readable descriptioninputSchema: { // JSON Schema for the tool's parameterstype: "object",properties: { ... } // Tool-specific parameters},annotations?: { // Optional hints about tool behaviortitle?: string; // Human-readable title for the toolreadOnlyHint?: boolean; // If true, the tool does not modify its environmentdestructiveHint?: boolean; // If true, the tool may perform destructive updatesidempotentHint?: boolean; // If true, repeated calls with same args have no additional effectopenWorldHint?: boolean; // If true, tool interacts with external entities}
}
6.1.1 系统操作工具示例
与本地系统交互的工具:
{name: "execute_command",description: "Run a shell command",inputSchema: {type: "object",properties: {command: { type: "string" },args: { type: "array", items: { type: "string" } }}}
}
6.1.2 集成API工具示例
包装外部API的工具:
{name: "github_create_issue",description: "Create a GitHub issue",inputSchema: {type: "object",properties: {title: { type: "string" },body: { type: "string" },labels: { type: "array", items: { type: "string" } }}}
}
6.1.3 数据处理工具示例
转换或分析数据的工具:
{name: "analyze_csv",description: "Analyze a CSV file",inputSchema: {type: "object",properties: {filepath: { type: "string" },operations: {type: "array",items: {enum: ["sum", "average", "count"]}}}}
}
6.2 工具的发现与更新
MCP支持动态工具发现功能,
- 客户端可以随时列出可用的工具;
- 服务端可以使用
notifications/tools/list_changed
通知消息通知客户端工具更新; - 可以在运行时添加或删除工具;
- 可以更新工具的定义;
6.3 工具注释
工具注释提供了关于工具行为的额外元数据,帮助客户端理解如何呈现和管理工具。工具注释有几个主要用途:
- 在不影响模型上下文的情况下提供特定于UX的信息;
- 帮助客户端适当地对工具进行分类和展示;
- 传达有关工具潜在副作用的信息;
- 协助开发工具审批的直观界面;
MCP规范为工具定义了以下注释:
注释名称 | 类型 | 默认值 | 说明 |
---|---|---|---|
title | string | 工具的可读标题,对UI显示有用 | |
readOnlyHint | boolean | false | 如果为true,表示该工具不修改其环境 |
destructiveHint | boolean | true | 如果为true,该工具可能执行破坏性更新(仅当readOnlyHint为false时有意义) |
idempotentHint | boolean | false | 如果为true,则使用相同的参数重复调用该工具没有额外的效果(只有当readOnlyHint为false时才有意义) |
openWorldHint | boolean | true | 如果为true,则该工具可以与外部实体交互 |
下面的例子展示了如何为不同的场景定义带有注释的工具:
// A read-only search tool
{name: "web_search",description: "Search the web for information",inputSchema: {type: "object",properties: {query: { type: "string" }},required: ["query"]},annotations: {title: "Web Search",readOnlyHint: true,openWorldHint: true}
}// A destructive file deletion tool
{name: "delete_file",description: "Delete a file from the filesystem",inputSchema: {type: "object",properties: {path: { type: "string" }},required: ["path"]},annotations: {title: "Delete File",readOnlyHint: false,destructiveHint: true,idempotentHint: true,openWorldHint: false}
}// A non-destructive database record creation tool
{name: "create_record",description: "Create a new record in the database",inputSchema: {type: "object",properties: {table: { type: "string" },data: { type: "object" }},required: ["table", "data"]},annotations: {title: "Create Database Record",readOnlyHint: false,destructiveHint: false,idempotentHint: false,openWorldHint: false}
}
七、Sampling:让服务端请求LLM完成工作(类似Agent)
Sampling是一项强大的MCP功能,它允许服务端通过客户端请求LLM完成特定工作,在保持安全性和隐私性的同时实现复杂的Agent行为。
7.1 Sampling工作原理
Sampling流程遵循以下步骤:
- 服务端向客户端发送一个
sampling/createMessage
请求消息; - 客户端审查请求并可以修改它;
- 客户端接收LLM的响应;
- 客户端审核响应;
- 客户端将结果返回给服务端;
在过程中引入客户端的设计是为了保持对LLM看到和生成内容的控制。
7.2 消息格式
7.2.1 请求消息
Sampling请求使用标准化的消息格式:
{messages: [{role: "user" | "assistant",content: {type: "text" | "image",// For text:text?: string,// For images:data?: string, // base64 encodedmimeType?: string}}],modelPreferences?: {hints?: [{name?: string // Suggested model name/family}],costPriority?: number, // 0-1, importance of minimizing costspeedPriority?: number, // 0-1, importance of low latencyintelligencePriority?: number // 0-1, importance of capabilities},systemPrompt?: string,includeContext?: "none" | "thisServer" | "allServers",temperature?: number,maxTokens: number,stopSequences?: string[],metadata?: Record<string, unknown>
}
其中,
- messages:messages数组包含要发送给LLM的对话历史记录。每条消息有:
- role:user或者assistant;
- content:消息内容,可以是:
- text字段标识的文本内容;
- 带有data (base64)和mimeType字段的图像内容;
- modelPreferences:允许服务端指定他们的模型选择首选项,客户端根据这些首选项及其可用的模型做出最终的模型选择。其中:
- hints:客户端可以使用模型名称建议数组来选择合适的模型:
- name:可以匹配全部或部分模型名称的字符串(例如“claude-3”,“sonnet”);
- 客户端可以将hint映射到来自不同提供者的等效模型;
- 多个hint按优先顺序计算;
- 优先级值(0-1):
- costPriority:最小化成本的重要性;
- speedPriority:低延迟响应的重要性;
- intelligencePriority:高级模型能力的重要性;
- systemPrompt:可选字段,允许服务端携带特定的系统提示,客户端可以修改或忽略这个字段;
- includeContext:指定要包含哪些MCP上下文,客户端控制实际包含的上下文,
- none:没有额外的上下文;
- thisServer:包含来自请求服务端的上下文;
- allServers:包含来自所有连接的MCP服务器的上下文;
- sampling parameters:微调LLM,其中:
- temperature:控制随机性(0.0到1.0);
- maxTokens:可以生成的最大token数量;
- stopSequences:停止生成的序列数组;
- metadata:额外的provider-specific参数;
7.2.2 响应消息
客户端返回一个完成结果,其格式如下:
{model: string, // Name of the model usedstopReason?: "endTurn" | "stopSequence" | "maxTokens" | string,role: "user" | "assistant",content: {type: "text" | "image",text?: string,data?: string,mimeType?: string}
}
八、roots功能
roots是MCP中的一个概念,它定义了服务端的边界。roots功能为客户端提供了一种通知服务端相关资源及其位置的方法。客户端使用roots功能建议服务端应该关注的URI。当客户端连接到服务端时,它声明服务端应该使用哪些roots。虽然roots主要用于文件系统路径,但它可以是任何有效的URI,包括HTTP url。例如:
file:///home/user/projects/myapp
https://api.example.com/v1
8.1 为什么使用roots?
roots有几个重要的作用:
- 通知服务端有关资源和位置的信息;
- 说明哪些资源是工作空间的一部分;
- 允许多个roots存在,扩展可以使用的资源范围;
8.2 roots如何工作?
当客户端支持roots时:
- 在连接期间声明
roots
能力; - 向服务端提供建议的roots列表;
- 当roots发生变化时通知服务端(如果支持);
虽然roots是信息并且不是严格强制的,但服务段应该:
- 尊重所提供的roots;
- 使用roots URI来定位和访问资源;
- 优先考虑roots边界内的操作;
下面是典型的MCP客户端提供roots的方式:
{"roots": [{"uri": "file:///home/user/projects/frontend","name": "Frontend Repository"},{"uri": "https://api.example.com/v1","name": "API Endpoint"}]
}
这种配置建议服务端同时关注本地存储库和API端点,但保持它们在逻辑上的分离。