一文掌握 MCP 上下文協(xié)議:從理論到實踐
MCP
模型上下文協(xié)議(Model Context Protocol,簡稱 MCP)是一種開放標準,旨在標準化大型語言模型(LLM)與外部數(shù)據(jù)源和工具之間的交互方式。由 Anthropic 于 2024 年 11 月推出,MCP 通過定義統(tǒng)一的接口,使 AI 應用能夠安全、靈活地訪問和操作本地及遠程數(shù)據(jù)資源,提升模型的功能性和可擴展性。
圖片
Transports(傳輸層)
在 MCP 協(xié)議中,傳輸層提供了客戶端與服務器之間通信的基礎,其負責處理消息的發(fā)送與接收的底層機制。
消息格式
MCP 協(xié)議使用 JSON-RPC 2.0 作為消息傳輸格式,包含以下三種類型的 JSON-RPC 消息:
- Request 請求:
{
"jsonrpc": "2.0",
"id": 1, // 請求 ID(數(shù)字或字符串)
"method": "string", // 方法名
"params": {} // 可選,參數(shù)對象
}
- Response 響應:
{
"jsonrpc": "2.0",
"id": 1, // 對應請求的 ID
"result": {}, // 可選,成功結果
"error": { // 可選,錯誤信息
"code": 123,
"message": "錯誤描述",
"data": {} // 可選,附加數(shù)據(jù)
}
}
- Notification通知:
{
"jsonrpc": "2.0",
"method": "string", // 通知方法名
"params": {} // 可選,參數(shù)對象
}
內置傳輸類型
MCP 協(xié)議內置了兩種標準傳輸方式:標準輸入/輸出(stdio) 和 Server-Sent Events(SSE) 。
標準輸入/輸出(stdio)
stdio 傳輸通過 標準輸入輸出流 實現(xiàn)客戶端與服務器之間的通信,適用于本地集成與命令行工具。
推薦在以下場景使用 stdio:
- 構建命令行工具
- 本地系統(tǒng)集成
- 簡單進程間通信
- 與 shell 腳本協(xié)作
Go server 示例:
s := server.NewMCPServer(
"My Server", // Server name
"1.0.0", // Version
)
if err := server.ServeStdio(s); err != nil {
log.Fatalf("Server error: %v", err)
}
Server-Sent Events (SSE)
SSE 傳輸 通過 HTTP POST 請求實現(xiàn) 客戶端到服務器通信,同時支持 服務器到客戶端流式傳輸 。
推薦在以下場景使用 SSE:
- 僅需要服務器到客戶端的流式通信
- 運行在受限網(wǎng)絡環(huán)境
- 實現(xiàn)簡單的推送更新
Go server 示例:
s := server.NewMCPServer(
"My Server", // Server name
"1.0.0", // Version
)
sseServer := server.NewSSEServer(s)
err := sseServer.Start(":8080")
if err != nil {
panic(err)
}
MCP 的通用架構
MCP 遵循 CS 架構(client-server),具體包含的組件如下:
- Host 主機:發(fā)起連接 LLM 的應用程序,例如 Claude for Desktop 或其他的 AI 應用。
- MCP Client 客戶端:運行在主機里的客戶端,與 MCP Server 服務器保持 1:1 連接,負責協(xié)議通信。
- MCP Server 服務器:負責向客戶端提供 資源、提示 和 工具 的服務器。
圖片
MCP 客戶端
客戶端可以實現(xiàn)額外的功能來與 MCP 服務器進行交互。
Roots 根
Roots 是MCP 協(xié)議中的一個概念,用于界定服務器可操作的邊界。客戶端可以定義 Roots,以告知服務器相關的資源信息及其位置。
root 是客戶端建議服務器應關注的 URI。當 客戶端 連接到服務器時,它會聲明 服務器 應處理哪些 root。雖然這些 root 主要是文件系統(tǒng)路徑,但 root 也可以是 HTTP URL。
# 文件系統(tǒng)路徑
file:///home/chenmingyong/workspace
# HTTP URL
https://chenmingyong.cn/
功能
Roots 有以下幾個功能:
- 引導:指示服務器相關資源及其位置。
- 明確歸屬:清楚標識哪些資源屬于當前工作區(qū)。
- 便于管理:支持同時處理多個不同的資源。
工作機制
當客戶端具備 Roots 功能時,通常會:
- 在連接服務器時聲明自身的 Roots 支持能力
- 向服務器發(fā)送建議的 Roots 列表
- 在 Roots 發(fā)生變更時(若協(xié)議支持)主動通知服務器
示例
{
"roots": [
{
"uri": "file:///home/chenmingyong/workspace/frontend",
"name": "Frontend Repository"
},
{
"uri": "https://chenmingyong.cn/",
"name": "API Endpoint"
}
]
}
Sampling 采樣
采樣是 MCP 協(xié)議中一項強大的功能,允許服務器通過客戶端向 LLM 大模型請求補全結果,從而實現(xiàn)更復雜的代理行為,同時確保安全性與隱私性。
工作原理
采樣流程遵循以下步驟:
- 服務器發(fā)送請求:服務器向客戶端發(fā)送 sampling/createMessage 請求。
- 客戶端審核請求:客戶端收到請求后可以對其審查和修改。
- 客戶端發(fā)起采樣:客戶端向 LLM 發(fā)送采樣請求。
- 客戶端審核結果:客戶端審核 LLM 返回的補全內容。
- 客戶端返回結果:客戶端將最終結果發(fā)送回服務器。
圖片
這種 人機協(xié)作(Human-in-the-loop) 設計,確保用戶能控制 LLM 看到的內容以及生成的結果,兼顧自動化與安全性。
消息格式
采樣請求使用同一的消息結構,示例如下:
{
"messages": [
{
"role": "user" | "assistant", // 消息角色
"content": {
"type": "text" | "image", // 內容類型
// 文本內容
"text": "string",
// 圖片內容
"data": "string", // Base64 編碼
"mimeType": "string" // MIME 類型
}
}
],
"modelPreferences": { // 可選,模型偏好設置
"hints": [ // 模型提示
{
"name": "string" // 建議的模型名稱
}
],
"costPriority": 0.0, // 降低成本的優(yōu)先級 (0-1)
"speedPriority": 0.0, // 低延遲的優(yōu)先級 (0-1)
"intelligencePriority": 0.0 // 模型能力的優(yōu)先級 (0-1)
},
"systemPrompt": "string", // 可選,系統(tǒng)提示詞
"includeContext": "none" | "thisServer" | "allServers", // 上下文包含范圍
"temperature": 0.0, // 隨機性控制
"maxTokens": 100, // 最大生成 Token 數(shù)
"stopSequences": ["string"], // 停止序列
"metadata": {} // 其他元數(shù)據(jù)
}
響應格式
客戶端返回采樣結果的結構如下:
{
"model": "string", // 使用的模型名稱
"stopReason": "endTurn" | "stopSequence" | "maxTokens" | "string", // 停止原因
"role": "user" | "assistant", // 消息角色
"content": {
"type": "text" | "image", // 內容類型
"text": "string", // 文本內容
"data": "string", // 圖片內容 (Base64)
"mimeType": "string" // MIME 類型
}
}
示例請求
以下是一個示例采樣請求:
{
"method": "sampling/createMessage",
"params": {
"messages": [
{
"role": "user",
"content": {
"type": "text",
"text": "當前目錄下有哪些文件?"
}
}
],
"systemPrompt": "你是一名文件系統(tǒng)助手。",
"includeContext": "thisServer",
"maxTokens": 100
}
}
MCP 服務器
在 MCP 協(xié)議中,服務器提供了為 LLM 大模型添加上下文的基礎構件。通過 Propmts(提示詞)、Resources(資源)和 Tools(工具)這三種 原語(Primitives), 客戶端、服務器與語言模型之間能夠實現(xiàn) 高效且靈活的交互。
Prompts 提示詞
提示詞 允許服務器定義可復用的提示詞模板和工作流,客戶端可以輕松將這些模板呈現(xiàn)給用戶或 LLM。
提示詞結構
一個提示詞的結構定義如下所示:
{
"name": "string", // 提示詞唯一標識符
"description": "string", // 可選,人類可讀的描述
"arguments": [ // 可選參數(shù)列表
{
"name": "string", // 參數(shù)標識符
"description": "string", // 可選,參數(shù)描述
"required": "boolean" // 是否為必填參數(shù)
}
]
}
獲取提示詞
客戶端可以通過調用 prompts/list 獲取可用的提示詞列表:
請求示例:
{
method: "prompts/list"
}
響應示例:
{
"prompts": [
{
"name": "analyze-code", // 提示詞唯一標識符
"description": "分析代碼以發(fā)現(xiàn)潛在改進點", // 提示詞描述
"arguments": [
{
"name": "language", // 參數(shù)名稱
"description": "編程語言", // 參數(shù)描述
"required": true // 是否必填
}
]
}
]
}
獲取提示詞詳情
如果客戶端要使用提示詞,需要通過 prompts/get 接口獲取提示詞詳情。
請求示例:
{
"method": "prompts/get",
"params": {
"name": "analyze-code", // 要使用的提示詞名稱
"arguments": {
"language": "go" // 動態(tài)參數(shù)
}
}
}
響應示例:
{
"description": "分析 Go 代碼以發(fā)現(xiàn)潛在改進點", // 提示詞描述
"messages": [
{
"role": "user", // 消息發(fā)送方
"content": {
"type": "text", // 內容類型
"text": "請分析以下 Go 代碼,找出可能的改進空間:\n\n```go\npackage main\n\nimport "fmt"\n\nfunc main() {\n\n fmt.Println("Hello, World!")\n}\n```"
}
}
]
}
動態(tài)提示詞
提示詞不僅可以是靜態(tài)模板,也支持根據(jù)參數(shù)動態(tài)生成內容,甚至可以嵌入 資源上下文。
提示詞定義示例:
{
"name": "analyze-project", // 提示詞名稱
"description": "分析項目日志與代碼", // 描述
"arguments": [
{
"name": "timeframe", // 參數(shù)名稱
"description": "要分析的日志時間范圍", // 參數(shù)描述
"required": true // 是否必填
},
{
"name": "fileUri", // 參數(shù)名稱
"description": "待審查的代碼文件 URI", // 參數(shù)描述
"required": true // 是否必填
}
]
}
prompts/get 請求返回示例:
{
"messages": [
{
"role": "user",
"content": {
"type": "text",
"text": "請分析以下系統(tǒng)日志和代碼文件,找出潛在問題:"
}
},
{
"role": "user",
"content": {
"type": "resource",
"resource": {
"uri": "logs://chenmingyong/recent?timeframe=1h", // 資源 URI
"text": "[2024-03-14 15:32:11] ERROR: network.py:127 連接超時\n[2024-03-14 15:32:15] WARN: 正在重試連接 (第 2/3 次)\n[2024-03-14 15:32:20] ERROR: 達到最大重試次數(shù)",
"mimeType": "text/plain" // 資源類型
}
}
},
{
"role": "user",
"content": {
"type": "resource",
"resource": {
"uri": "file:///path/chenmingyong/code.py", // 代碼文件 URI
"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 # 連接實現(xiàn)\n pass",
"mimeType": "text/x-python"
}
}
}
]
}
多輪提示工作流
提示詞還支持定義多輪對話流程,通過預設的多步提示,引導用戶完成復雜任務。
const debugWorkflow = {
name: "debug-error",
async getMessages(error: string) {
return [
{
role: "user",
content: {
type: "text",
text: `我遇到了一個錯誤:${error}`
}
},
{
role: "assistant",
content: {
type: "text",
text: "我來幫你分析這個錯誤。你之前嘗試過哪些操作?"
}
},
{
role: "user",
content: {
type: "text",
text: "我嘗試重啟服務,但錯誤依然存在。"
}
}
];
}
};
Resources 資源
Resources(資源)是 MCP 協(xié)議中的核心原語之一,服務器通過它可以向客戶端提供可讀的數(shù)據(jù)或內容,用作 LLM 交互的上下文信息。
資源 URI
每個資源通過 URI 進行標識,格式如下:
[協(xié)議]://[主機]/[路徑]
示例:
file:///home/chenmingyong/documents/go.pdf
postgres://database/chenmingyong/schema
screen://localhost/chenmingyong/display1
具體的協(xié)議(protocol)與路徑結構由 MCP 服務器自行定義,服務器也可以設計自定義的 URI 格式。
資源類型
資源內容分為兩種類型:
文本類型
包含 UTF_8 編碼的文本數(shù)據(jù),例如:
- 源代碼
- 配置文件
- 日志文件
- JSON / XML 數(shù)據(jù)
- 普通文本
二進制資源
包含 Base64 編碼的原始二進制數(shù)據(jù),例如:
- 圖片
- PDF 文件
- 音頻 / 視頻文件
- 其他非文本格式
獲取資源列表
客戶端可以通過調用服務器的 resources/list 獲取資源列表,每個資源包含以下信息:
{
"uri": "string", // 資源唯一標識符
"name": "string", // 資源名稱(人類可讀)
"description": "string", // 可選,資源描述
"mimeType": "string" // 可選,資源 MIME 類型
}
對于動態(tài)資源,服務器可以通過 URI 模板方式暴露資源,客戶端可根據(jù)模板構建有效的資源 URI:
{
"uriTemplate": "string", // 符合 RFC 6570 的 URI 模板
"name": "string", // 模板名稱(人類可讀)
"description": "string", // 可選,模板描述
"mimeType": "string" // 可選,匹配資源的 MIME 類型
}
獲取資源詳情
客戶端可以通過 resources/read 接口讀取資源內容,只需傳入資源的 URI。
服務器會返回資源內容列表:
{
contents: [
{
uri: string; // 資源唯一標識符
mimeType?: string; // 可選,資源 MIME 類型
// 以下二選一
text?: string; // 文本資源
blob?: string; // 二進制資源
}
]
}
資源更新
MCP 支持通過兩種方式通知資源變更。
列表變更
當可用資源列表發(fā)生變更時,服務器會通過 notifications/resources/list_changed 通知客戶端。
內容變更
客戶端可以訂閱特定資源的更新流程如下:
- 客戶端發(fā)送 resources/subscribe 請求,指定資源 URI
- 資源發(fā)生變更時,服務器通過 notifications/resources/updated 通知客戶端
- 客戶端可通過 resources/read 獲取最新內容
- 客戶端可通過 resources/unsubscribe 取消訂閱
Tools 工具
Tools(工具) 是 MCP 協(xié)議中的一項關鍵原語,服務器可通過它向客戶端暴露可執(zhí)行功能,供 LLM 使用(通常需要用戶批準,確保人類參與決策)。Tools 的核心概念包括:
- 發(fā)現(xiàn)(Discovery):客戶端可通過 tools/list 接口獲取可用工具列表。
- 調用(Invocation):客戶端可通過 tools/call接口發(fā)起工具調用請求,由服務器執(zhí)行具體操作并返回結果。
- 靈活性(Flexibility):工具既可以是簡單的計算函數(shù),也可以是復雜的 API 集成。
工具結構定義
每個工具的結構定義如下所示:
{
"name": "string", // 工具唯一標識符
"description": "string", // 可選,工具描述
"inputSchema": { // 工具參數(shù)的 JSON Schema
"type": "object",
"properties": { ... } // 工具參數(shù)定義
}
}
工具實現(xiàn)示例
- 與本地系統(tǒng)交互的工具
{
"name": "execute_command", // 工具名稱
"description": "執(zhí)行 shell 命令", // 描述
"inputSchema": {
"type": "object",
"properties": {
"command": { "type": "string" }, // 命令名稱
"args": {
"type": "array",
"items": { "type": "string" } // 命令參數(shù)
}
}
}
}
- 外部 API 集成類工具
{
"name": "github_create_issue", // 工具名稱
"description": "在 GitHub 創(chuàng)建 Issue", // 描述
"inputSchema": {
"type": "object",
"properties": {
"title": { "type": "string" }, // Issue 標題
"body": { "type": "string" }, // Issue 內容
"labels": {
"type": "array",
"items": { "type": "string" } // Issue 標簽
}
}
}
}
MCP Server 原語控制層級
每個原語的 控制層級 可總結如下:
原語 | 控制方 | Description 描述 | Example 示例 |
Prompts | 用戶控制 | 由用戶選擇調用的交互式模板 | 斜杠命令(/command)、菜單選項 |
Resources | 應用控制 | 由客戶端附加并管理的上下文數(shù)據(jù) | 文件內容,git 歷史記錄 |
Tools | 模型控制 | 暴露給 LLM 以便其執(zhí)行操作的功能接口 | API POST 請求、文件寫入 |
服務器實現(xiàn)
以下是基于 Go 語言實現(xiàn)的 MCP 服務器代碼示例:
package main
import (
"context"
"errors"
"fmt"
"os"
"github.com/mark3labs/mcp-go/mcp"
"github.com/mark3labs/mcp-go/server"
)
func main() {
s := server.NewMCPServer(
"Server Demo",
"1.0.0",
)
// 添加工具
{
calculatorTool := mcp.NewTool("calculate",
mcp.WithDescription("執(zhí)行基本的算術運算"),
mcp.WithString("operation",
mcp.Required(),
mcp.Description("要執(zhí)行的算術運算類型"),
mcp.Enum("add", "subtract", "multiply", "divide"), // 保持英文
),
mcp.WithNumber("x",
mcp.Required(),
mcp.Description("第一個數(shù)字"),
),
mcp.WithNumber("y",
mcp.Required(),
mcp.Description("第二個數(shù)字"),
),
)
s.AddTool(calculatorTool, func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
op := request.Params.Arguments["operation"].(string)
x := request.Params.Arguments["x"].(float64)
y := request.Params.Arguments["y"].(float64)
var result float64
switch op {
case"add":
result = x + y
case"subtract":
result = x - y
case"multiply":
result = x * y
case"divide":
if y == 0 {
returnnil, errors.New("不允許除以零")
}
result = x / y
}
return mcp.FormatNumberResult(result), nil
})
}
// 添加資源
{
// 靜態(tài)資源示例 - 暴露一個 README 文件
resource := mcp.NewResource(
"docs://readme",
"項目說明文檔",
mcp.WithResourceDescription("項目的 README 文件"),
mcp.WithMIMEType("text/markdown"),
)
// 添加資源及其處理函數(shù)
s.AddResource(resource, func(ctx context.Context, request mcp.ReadResourceRequest) ([]mcp.ResourceContents, error) {
content, err := os.ReadFile("README.md")
if err != nil {
returnnil, err
}
return []mcp.ResourceContents{
mcp.TextResourceContents{
URI: "docs://readme",
MIMEType: "text/markdown",
Text: string(content),
},
}, nil
})
}
// 添加提示詞
{
// 簡單問候提示
s.AddPrompt(mcp.NewPrompt("greeting",
mcp.WithPromptDescription("一個友好的問候提示"),
mcp.WithArgument("name",
mcp.ArgumentDescription("要問候的人的名字"),
),
), func(ctx context.Context, request mcp.GetPromptRequest) (*mcp.GetPromptResult, error) {
name := request.Params.Arguments["name"]
if name == "" {
name = "朋友"
}
return mcp.NewGetPromptResult(
"友好的問候",
[]mcp.PromptMessage{
mcp.NewPromptMessage(
mcp.RoleAssistant,
mcp.NewTextContent(fmt.Sprintf("你好,%s!今天有什么可以幫您的嗎?", name)),
),
},
), nil
})
}
// 啟動基于 stdio 傳輸類型的服務
if err := server.ServeStdio(s); err != nil {
fmt.Printf("Server error: %v\n", err)
}
}
上述代碼示例演示了如何創(chuàng)建一個 MCP Server,并展示了添加工具、資源和提示詞的方法。
需要注意的是,由于 mcp-go 庫當前尚未支持 Sampling(采樣)功能,示例中未包含該功能的相關用法。
客戶端實現(xiàn)
基于上面定義的服務器,以下是基于 Go 語言實現(xiàn)的 MCP 客戶端代碼示例:
package main
import (
"context"
"fmt"
"time"
"github.com/mark3labs/mcp-go/client"
"github.com/mark3labs/mcp-go/mcp"
)
func main() {
// 創(chuàng)建一個基于 stdio 的MCP客戶端
mcpClient, err := client.NewStdioMCPClient(
"./client/server",
[]string{},
)
if err != nil {
panic(err)
}
defer mcpClient.Close()
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
fmt.Println("初始化 mcp 客戶端...")
initRequest := mcp.InitializeRequest{}
initRequest.Params.ProtocolVersion = mcp.LATEST_PROTOCOL_VERSION
initRequest.Params.ClientInfo = mcp.Implementation{
Name: "Client Demo",
Version: "1.0.0",
}
// 初始化MCP客戶端并連接到服務器
initResult, err := mcpClient.Initialize(ctx, initRequest)
if err != nil {
panic(err)
}
fmt.Printf(
"\n初始化成功,服務器信息: %s %s\n\n",
initResult.ServerInfo.Name,
initResult.ServerInfo.Version,
)
// 從服務器獲取提示詞列表
fmt.Println("提示詞列表:")
promptsRequest := mcp.ListPromptsRequest{}
prompts, err := mcpClient.ListPrompts(ctx, promptsRequest)
if err != nil {
panic(err)
}
for _, prompt := range prompts.Prompts {
fmt.Printf("- %s: %s\n", prompt.Name, prompt.Description)
fmt.Println("參數(shù):", prompt.Arguments)
}
// 從服務器獲取資源列表
fmt.Println()
fmt.Println("資源列表:")
resourcesRequest := mcp.ListResourcesRequest{}
resources, err := mcpClient.ListResources(ctx, resourcesRequest)
if err != nil {
panic(err)
}
for _, resource := range resources.Resources {
fmt.Printf("- uri: %s, name: %s, description: %s, MIME類型: %s\n", resource.URI, resource.Name, resource.Description, resource.MIMEType)
}
// 從服務器獲取工具列表
fmt.Println()
fmt.Println("可用工具列表:")
toolsRequest := mcp.ListToolsRequest{}
tools, err := mcpClient.ListTools(ctx, toolsRequest)
if err != nil {
panic(err)
}
for _, tool := range tools.Tools {
fmt.Printf("- %s: %s\n", tool.Name, tool.Description)
fmt.Println("參數(shù):", tool.InputSchema.Properties)
}
fmt.Println()
// 調用工具
fmt.Println("調用工具: calculate")
toolRequest := mcp.CallToolRequest{
Request: mcp.Request{
Method: "tools/call",
},
}
toolRequest.Params.Name = "calculate"
toolRequest.Params.Arguments = map[string]any{
"operation": "add",
"x": 1,
"y": 1,
}
// Call the tool
result, err := mcpClient.CallTool(ctx, toolRequest)
if err != nil {
panic(err)
}
fmt.Println("調用工具結果:", result.Content[0].(mcp.TextContent).Text)
}
運行上述代碼的結果如下所示:
初始化 mcp 客戶端...
初始化成功,服務器信息: Server Demo 1.0.0
提示詞列表:
- greeting: 一個友好的問候提示
參數(shù): [{name 要問候的人的名字 false}]
資源列表:
- uri: docs://readme, name: 項目說明文檔, description: 項目的 README 文件, MIME類型: text/markdown
可用工具列表:
- calculate: 執(zhí)行基本的算術運算
參數(shù): map[operation:map[description:要執(zhí)行的算術運算類型 enum:[add subtract multiply divide] type:string] x:map[description:第一個數(shù)字 type:number] y:map[description:第二個數(shù)字 type:number]]
調用工具: calculate
調用工具結果: 2.00
提示詞、資源以及工具列表和之前定義 MCP Server 時所設置的數(shù)據(jù)一致。
★
若想獲取完整代碼鏈接,可關注本公眾號,回復 MCP。
小結
本文介紹了 模型上下文協(xié)議(Model Context Protocol,MCP),一種用于規(guī)范大型語言模型(LLM)與外部數(shù)據(jù)源及工具之間交互的開放標準。內容涵蓋了 MCP 協(xié)議的整體架構(客戶端與服務器的一對一連接模式)、消息傳輸機制(采用 JSON-RPC 2.0 格式)、以及客戶端與服務器支持的核心原語,包括:
- 客戶端原語:roots(根路徑)、sampling(采樣)
- 服務器原語:prompts(提示詞)、resources(資源)、tools(工具)
- Server原語的控制層級分類
最后,本文提供了基于 Go 語言 的 MCP 客戶端與服務器的示例實現(xiàn),幫助開發(fā)者快速理解和應用該協(xié)議。