全網最細,一文帶你弄懂 MCP 的核心原理!
MCP 是如何做到統一工具調用方式的?
MCP 客戶端和 MCP Server 到底是怎麼交互的?
爲什麼有的 MCP 客戶端支持所有模型,有的確不行?
大家好,歡迎來到 code 祕密花園,我是花園老師(ConardLi
)。
爲了方便大家更深度的理解 MCP ,在今天這一期,我們用幾個例子來一起學習 MCP 的核心原理。
在上一期 《MCP + 數據庫:一種提高結構化數據檢索效果的新方式》,我們一起學習了 MCP 的基礎知識,包括
-
MCP 基礎知識,和 Function Call 的關係
-
瞭解 MCP Clinet、MCP Server 等核心概念
-
在 Cherry Studio 中簡單嘗試了 MCP
-
在 Cline 中基於 mongo-mcp-server 實現了結構化數據的精準檢索
還沒學習的同學建議先閱讀完上一期再回來看這一期,今天我們的學習大綱如下:
下面我們先回顧一下 MCP 的基礎知識:
一、MCP 基礎回顧
MCP(Model Context Protocol
,模型上下文協議)是由 Anthropic
公司(也就是開發 Claude 模型的公司)推出的一個開放標準協議,就像是一個 “通用插頭” 或者 “USB 接口”,制定了統一的規範,不管是連接數據庫、第三方 API,還是本地文件等各種外部資源,目的就是爲了解決 AI 模型與外部數據源、工具交互的難題。
MCP
大概的工作方式:MCP Host
,比如 Claude Desktop、Cursor
這些工具,在內部實現了 MCP Client
,然後MCP Client
通過標準的 MCP 協議和 MCP Server
進行交互,由各種三方開發者提供的 MCP Server
負責實現各種和三方資源交互的邏輯,比如訪問數據庫、瀏覽器、本地文件,最終再通過 標準的 MCP 協議返回給 MCP Client
,最終在 MCP Host
上展示。
開發者按照 MCP 協議進行開發,無需爲每個模型與不同資源的對接重複編寫適配代碼,可以大大節省開發工作量,另外已經開發出的 MCP Server,因爲協議是通用的,能夠直接開放出來給大家使用,這也大幅減少了開發者的重複勞動。
首先一步,我們先從 MCP Server
的配置講起,弄懂 MCP Server
的配置爲什麼是這樣設計的。
二、MCP Server 的配置爲何長這樣?
我們使用 Cherry Studio
中的 MCP
服務配置來舉例,最前面的名稱和描述比較好理解,都是用於展示的。
2.1 通信協議
下面我們發現有兩個類型,STDIO
和 SSE
:
MCP
協議中的 STDIO
和 SSE
其實就是是兩種不同的(MCP Server
與 MCP Client
)通信模式:
-
STDIO(標準輸入輸出):像「面對面對話」:客戶端和服務器通過本地進程的標準輸入和標準輸出直接通信。例如:本地開發時,你的代碼腳本通過命令行啓動,直接與 MCP 服務器交換數據,無需網絡連接。
-
SSE(服務器推送事件):像「電話熱線」:客戶端通過 HTTP 協議連接到遠程服務器,服務器可以主動推送數據(如實時消息)。例如:AI 助手通過網頁請求調用遠程天氣 API,服務器持續推送最新的天氣信息。
簡單理解,STDIO 調用方式是將一個 MCP Server
下載到你的本地,直接調用這個工具,而 SSE 則是通過 HTTP 服務調用託管在遠程服務器上的 MCP Server
。
這就是一個 SSE MCP Server 的配置示例,非常簡單,我們直接使用網絡協議和工具進行通信:
{
"mcpServers": {
"browser-use-mcp-server": {
"url": "http://localhost:8000/sse"
}
}
}
而我們之前用到的 FileSystem、Mongodb
都屬於典型的 STDIO
調用:
{
"mcpServers": {
"mongodb": {
"command": "npx",
"args": [
"-y",
"@modelcontextprotocol/server-filesystem",
"~/Downloads"
]
}
}
}
STDIO
爲程序的輸入輸出提供了一種統一的標準方式,無論是什麼類型的程序,只要遵循 STDIO
協議,就可以方便地進行數據的輸入和輸出,並且可以很容易地與其他程序或系統組件進行交互。
在命令行中,使用 STDIO 通信是非常常見的,比如 Linux
系統中的 cat
命令就是一個極爲簡單且能體現 STDIO 通信的例子。cat
命令通常用於連接文件並打印到標準輸出。當你不指定任何文件時,它會從標準輸入讀取內容,並將其輸出到標準輸出。
打開終端,輸入 cat
命令,然後按下回車鍵。此時,cat
命令開始等待你從標準輸入(通常是鍵盤)輸入內容。你可以輸入任意文本,每輸入一行並按下回車鍵,cat
會立即將這行內容輸出到標準輸出。
在這個例子中,你輸入了 你好,這裏是 code祕密花園!
和 我是花園老師
,cat
命令將這些內容從標準輸入讀取後,馬上輸出到標準輸出。
我們還能借助一些操作符,比如重定向操作符 >
將標準輸出重定向到文件。下面的例子會把你輸入的內容保存到 test.txt
文件裏:
上述命令執行後,test.txt
文件裏會包含你輸入的兩行內容:
在這個過程中,cat
命令從標準輸入讀取內容,接着將輸出重定向到文件,這就是一個最簡單的 STDIO
通信案例。
2.2 命令和參數
下面再來說說配置裏的命令,對應 JSON 配置中的 command
參數,在沒有填寫時,這裏默認推薦的是 uvx 和 npx:
在之前 mongodb
的例子中, command
使用的就是 npx
,之前我們提到了,只要安裝了 Node.js
環境,就可以使用,那爲什麼這裏不寫 node,而要用 npx
命令呢?
npx
是 Node.js
生態系統中的一個命令行工具,它本質上是 npm
的一個擴展。npx 的名字可以理解爲 “運行一個包”(npm execute package
)的縮寫。
npm
是Node.js
的包管理工具,我們編寫了一些通用的工具,就可以將其發佈爲一個 npm 包,這樣所有具備 Node.js 的環境都可以下載並且運行這個包
簡單來說,npx
的主要功能就是幫助我們快速運行一些通過 npm
安裝的工具,而不需要我們手動去下載安裝這些工具到全局環境。
{
"mcpServers": {
"mongodb": {
"command": "npx",
"args": [
"-y",
"@modelcontextprotocol/server-filesystem",
"~/Downloads"
]
}
}
}
在這個配置中,args
裏第一個參數 "-y"
其實就等同於 --yes,其作用是在執行命令時自動同意所有提示信息,可以避免交互式的確認步驟。
而第二個參數其實就是這個 npm
包的名字,比如 @modelcontextprotocol/server-filesystem
這個包:
args
裏的第三個參數其實就是傳遞給這個包的一個必備參數:允許讀取的本機路徑。
這樣理解其實就能說得通了,這幾個信息,再加上大模型分析用戶輸入後得出的參數,通過 STDIO
協議傳遞給這個包,其實就可以構成一條在終端直接可以運行的一條命令:
npx -y @modelcontextprotocol/server-filesystem ~/Downloads <<< '{"method":"tools/call","params":{"name":"list_directory","arguments":{"path":"~/Downloads"}},"jsonrpc":"2.0","id":1}'
我們測試執行一下:
我們把輸入內容格式化一下:
<<<
(Here 字符串操作符)的主要用途是將一個字符串作爲標準輸入(STDIN)傳遞給前面的命令。
在這個命令中,我們通過 <<< 操作符
,把後面的字符串傳遞給前面的命令(也就是 @modelcontextprotocol/server-filesystem
這個包)當作輸入數據。
後面的字符串中包含了需要調用的函數(list_directory
),以及傳遞給這個函數的參數(~/Downloads
),
前面的部分都是我們在 MCP Server
中配置的內容,屬於固定的部分。而大模型做的,就是根據用戶當前的輸入,以及當前支持的 MCP 工具列表,判斷出要不要調用這個工具,如果要調用,生成結構化的工具參數,最後 MCP Client 通過 STDIO
協議將這個結構化的參數再傳遞給 MCP Server
。
在上面的命令中,還有一個選項是 uvx
,和 npx
類似,它也可以直接讓你臨時執行某個工具包,而無需全局安裝。不過 uvx
:是 uv
工具鏈的一部分,主要聚焦於 Python
生態系統,它會創建一個臨時的隔離 Python
環境來運行 Python
工具包。
2.3 傳輸格式
我們再來看看剛剛的命令執行的結果:
也是一段格式化好的內容,裏面返回了當前目錄下的所有文件,而模型在接收到這段格式化的輸出後,會將其變成口語化的內容再返回到 MCP 客戶端。
這個輸入、輸出的參數格式,遵循的是 JSON-RPC 2.0
傳輸格式,它有一套自己的規則,規定了請求和響應的格式、如何處理錯誤等,官方文檔的中的描述:
2.4 Windows 下的配置
在實際使用中,大家可能會發現,下面的配置在 Windows 下可能不會生效
{
"mcpServers": {
"mongodb": {
"command": "npx",
"args": [
"-y",
"@modelcontextprotocol/server-filesystem",
"~/Downloads"
]
}
}
}
需要改成下面的方式:
{
"mcpServers": {
"mongodb": {
"command": "cmd",
"args": [
"/c",
"npx",
"-y",
"@modelcontextprotocol/server-filesystem",
"~/Downloads"
]
}
}
}
這是因爲在不同操作系統下 默認命令解釋器(Shell)的工作機制不同:
macOS
的終端(如 bash/zsh
)是 Unix shell
,支持直接執行可執行文件(如 npx
)。
Windows 的默認命令解釋器是 cmd.exe
,而非直接執行程序。即使 npx
已安裝(如通過全局 npm install -g npx
),也需要通過 cmd.exe
來調用,因爲:
-
Windows 不直接識別 Unix 風格的命令路徑(如
npx
本質是 Node.js 腳本,需通過node
執行)。 -
cmd.exe
需要明確的指令格式(如/c
參數用於執行命令後退出)。
args
中的 /c
是 cmd.exe
的參數,表示 “執行後續命令後關閉窗口”。完整的執行流程是:
cmd.exe /c npx -y @modelcontextprotocol/server-filesystem "~/Downloads"
這裏
cmd.exe
先解析/c
,再將npx ...
作爲子命令執行,確保 Windows 能正確調用 Node.js 腳本(npx
)。
通過分析 MCP Server
的配置,我們瞭解了 MCP Clinet
和 MCP Server
的通信協議、執行的命令和參數,以及兩者數據傳輸的標準格式,這樣 MCP Clinet
和 MCP Server
的交互方式我們基本上就弄清楚了。
那執行 MCP Server 我們一定要通過 npx
或 uvx
運行一個包嗎?
當然不是,實際上任何能夠在命令行執行代碼的方式,都是可以的,比如我們可以直接把一個 MCP Server 的倉庫拉取到本地,然後通過 node 或 python 命令直接執行裏面的代碼文件,這樣就可以實現完全斷網運行(前提是 MCP Server 中不會調用遠程 API)。
{
"mcpServers": {
"mongodb": {
"command": "node",
"args": [
"/path/mcp-server.js"
]
}
}
}
下面,我們在本地實現一個最簡單的例子,來教大家如何構建一個 MCP Server。
三、MCP Server 是如何開發和調試的?
3.1 基於 AI 輔助編寫 MCP Server
在官方文檔(https://modelcontextprotocol.io/quickstart/server)中,我們可以找到 MCP Server
的開發方式,以及官方提供的各個語言的 SDK 的示例代碼:
寫的挺清晰的,如果你懂代碼,仔細看一遍很快就能上手,但在 AI 時代,從零自己去寫肯定不可能了,官方其實也建議通過 AI
來幫我們實現 MCP
,所以在文檔中單獨還提供了一個 Building MCP with LLMs
的章節。
大概思路如下:
在開始之前,收集必要的文檔,以幫助 AI 瞭解 MCP:
-
訪問 https://modelcontextprotocol.io/llms-full.txt 並複製完整的文檔文本。
-
導航到 MCP 的 TypeScript 軟件開發工具包(SDK)或 Python 軟件開發工具包的代碼倉庫。
-
複製(README)和其他相關文檔。
-
將這些文檔粘貼到你與克勞德的對話中。
提供了相關文檔之後,向 AI 清晰地描述你想要構建的服務器類型:
-
你的服務器將公開哪些資源。
-
它將提供哪些工具。
-
它應該給出的任何提示。
-
它需要與哪些外部系統進行交互。
比如這是一個例子:構建一個 MCP 服務器,該服務器:
-
連接到我公司的 PostgreSQL 數據庫。
-
將表模式作爲資源公開。
-
提供用於運行只讀 SQL 查詢的工具。
-
包含針對常見數據分析任務的提示。
在實際測試中我發現,上面提到的 https://modelcontextprotocol.io/llms-full.txt 這個文檔就是整個文檔站的內容,裏面包含了很多構建 MCP Server 不需要的內容,反而會給模型造成干擾,大家可以直接參考我的提示詞:
## 需求
基於提供的 MCP 相關資料,幫我構建一個 MCP Server,需求如下:
- 提供一個獲取當前時間的工具
- 接收時區作爲參數(可選)
- 編寫清晰的註釋和說明
- 要求功能簡潔、只包含關鍵功能
- 使用 TypeScript 編寫
請參考下面四個資料:
## [參考資料 1] MCP 基礎介紹
- 粘貼 https://modelcontextprotocol.io/introduction 裏的內容。
## [參考資料 2] MCP 核心架構
- 粘貼 https://modelcontextprotocol.io/docs/concepts/architecture 裏的內容。
## [參考資料 3] MCP Server 開發指引
- 粘貼 https://modelcontextprotocol.io/quickstart/server 裏的內容。
## [參考資料 4] MCP Typescript SDK 文檔
- 粘貼 https://github.com/modelcontextprotocol/typescript-sdk/blob/main/README.md 裏的內容。
下面是一個 AI 幫我生成好的 MCP Server 的關鍵代碼:
import { McpServer } from"@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from"@modelcontextprotocol/sdk/server/stdio.js";
import { z } from"zod";
const server = new McpServer({
name: "TimeServer", // 服務器名稱
version: "1.0.0", // 服務器版本
});
server.tool(
"getCurrentTime", // 工具名稱,
"根據時區(可選)獲取當前時間", // 工具描述
{
timezone: z
.string()
.optional()
.describe(
"時區,例如 'Asia/Shanghai', 'America/New_York' 等(如不提供,則使用系統默認時區)"
),
},
async ({ timezone }) => {
// 具體工具實現,這裏省略
}
);
/**
* 啓動服務器,連接到標準輸入/輸出傳輸
*/
asyncfunction startServer() {
try {
console.log("正在啓動 MCP 時間服務器...");
// 創建標準輸入/輸出傳輸
const transport = new StdioServerTransport();
// 連接服務器到傳輸
await server.connect(transport);
console.log("MCP 時間服務器已啓動,等待請求...");
} catch (error) {
console.error("啓動服務器時出錯:", error);
process.exit(1);
}
}
startServer();
其實代碼非常簡單,我們可以拆分爲三部分來理解:
第一步:使用官方提供的 @modelcontextprotocol/sdk/server/mcp.js
包,創建一個 McpServer
實例:
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
/**
* 創建 MCP 服務器實例
*/
const server = new McpServer({
name: "TimeServer", // 服務器名稱
version: "1.0.0", // 服務器版本
});
第二步:使用 server.tool
定義提供的工具方法,包括工具方法的名稱、工具方法的描述、工具方法的參數、工具方法的具體實現邏輯。
另外使用了 "zod"
這個包定義了方法參數的類型以及描述。
import { z } from"zod";
server.tool(
"getCurrentTime", // 工具名稱,
"根據時區(可選)獲取當前時間", // 工具描述
{
timezone: z
.string()
.optional()
.describe(
"時區,例如 'Asia/Shanghai', 'America/New_York' 等(如不提供,則使用系統默認時區)"
),
},
async ({ timezone }) => {
// 具體工具實現,這裏省略
}
);
第三步:啓動 Server,並且使用 SDK 中導出的 StdioServerTransport
來定義工具使用 STDIO
通信協議,等待外部的標準輸入,並且把工具的執行結果轉化爲標準輸出反饋到外部。
import { StdioServerTransport } from"@modelcontextprotocol/sdk/server/stdio.js";
/**
* 啓動服務器,連接到標準輸入/輸出傳輸
*/
asyncfunction startServer() {
try {
console.log("正在啓動 MCP 時間服務器...");
// 創建標準輸入/輸出傳輸
const transport = new StdioServerTransport();
// 連接服務器到傳輸
await server.connect(transport);
console.log("MCP 時間服務器已啓動,等待請求...");
} catch (error) {
console.error("啓動服務器時出錯:", error);
process.exit(1);
}
}
startServer();
就是這麼簡單,我們只需要按照這個模板來添加更多的工具實現就可以了,另外 Resources、Prompt 的編寫方式其實都是類似的,大家只需要按照這套提示詞模板,定義好自己的需求,AI(Claude 的準確性最高)基本上都能比較完整的實現。
3.2 使用 inspector 調試 MCP Server
開發完成後,我們可以直接使用官方提供的 MCP Server
調試工具(@modelcontextprotocol/inspector
)來進行調試。
我們也可以直接通過 npx 運行 @modelcontextprotocol/inspector
:
npx @modelcontextprotocol/inspector <command> <arg1> <arg2>
比如我們可以調試我們剛剛開發好的工具,這裏直接使用 node 運行我們本地構建好的代碼:
npx @modelcontextprotocol/inspector node dist/index.js
啓動成功後,它會在我們的本地監聽 6274 端口,我們點擊 Connect
:
連接成功後,我們點擊 List Tools
:
然後可以看到當前 MCP Server
定義的所有工具,我們這裏定義了一個獲取當前時間的工具,我們點擊這個工具,可以對它進行調試:
我們可以在下方所有交互產生的請求和響應的具體數據:
基於這樣的思路,我們可以使用 @modelcontextprotocol/inspector
調試任意的 MCP Server
, 在你想要使用,但是還不知道怎麼使用一個 MCP Server
時,都可以使用它進行調試:
npx @modelcontextprotocol/inspector npx -y @modelcontextprotocol/server-filesystem ~/Downloads
3.3 在 Cline 中測試 MCP Server
下面我們在 Cline
中配置測試一下我們剛剛開發好的 MCP Server
,這裏我們直接使用 node 執行:
測試結果:
四、MCP Client 是如何和 Server 通信的?
看到這,可能很多同學還會有疑問,MCP Clinet 是怎麼知道 MCP Server 都提供了哪些工具列表的?模型又怎麼從這些工具中選擇出合適的工具?在後續的問答中兩者又是如何配合的呢?
下面我們用抓包工具來分別對 Cherry Studio
、Cline
兩個工具進行抓包,使用我們剛剛開發好 MCP Server,分析整個交互過程。
4.1 配置抓包工具
我們使用 Charles
這個工具來抓包。
在 Cherry Studio
中,僅有一部分模型支持 MCP ,這裏我們選擇阿里雲百鍊的 qwen-max,實測在 Cherry Studio
中工具調用是非常穩定的:
然後我們在 Charles
中過濾一下阿里雲百鍊的請求,就可以過濾出後續的請求包了:
在 Cline
中,所有模型都支持 MCP,這裏我們選擇的是 Openrouter
下免費的 DeepseekV3
。這裏需要注意的是, Charles
並不能直接抓取到 VsCode 的請求,我們需要在 VsCode
下配置代理:
點擊 VsCode
左上角的 “文件” 菜單,選擇 “首選項 > 設置”,搜索 http.proxy
,然後在其中填寫 http://127.0.0.1:8888
,則可以將 vsCode
後續的請求都代理到 Charles
下:
然後在 Charles
中過濾一下 Openrouter 的請求,就可以過濾後續的請求包了:
還有一點需要注意,這些軟件一般是都採用的是流式輸出,所以在響應報文裏不會展示完整的 JSON 內容,
我們可以藉助 AI 幫我們還原原始的響應報文 “
4.2 Cherry Studio 抓包分析
當我們在 Cherry Studio
中開啓我們剛剛開發的 Time MCP Server
,然後詢問 “紐約時間” :
我們發現軟件實際調用了兩次 LLM API
:
下面我們直接來看我已經處理好的請求報文(省略了一些無關內容,只保留關鍵信息):
- 第一次請求: 客戶端 將從
MCP Server
獲取到的所有的工具列表,通過 tools 參數傳遞給了模型,相當於告訴模型,我現在有這些工具,你可以根據用戶的輸入(messages.content) 判斷是否使用工具,以及使用哪個工具:
- 第一次響應:模型根據用戶輸入的內容,以及當前支持的工具列表,判斷需要使用
getCurrentTime
工具(這裏使用 id 進行映射),並且拼接好了工具調用所需要的標準參數格式:{"timezone": "America/New_York"}
:
- 第二次請求:客戶端將上次模型返回的工具調用參數,以及在本地執行工具調用後的結果都合併到 messages 裏,傳遞給模型,讓模型基於這些信息生成最終回答:
- 第二次響應:模型根據輸入的信息產出最終回答給用戶的內容:
4.3 Cline 抓包分析
我們在 Cline 中配置好 Time MCP Server ,並且詢問同樣的問題:
然後我們發現 Charles 中同樣抓到兩條請求:
同樣的,我們把請求報文處理好,並保存到本地查看:
- 第一次請求:我們發現請求中並沒有像
Cherry Studio 一樣,包含 tools
字段,而是包含了一段超長的系統提示詞(統計了一下,居然長達 41695 個字符,可見 Cline 真的挺費 Token 的。。。):
然後,我對這段系統提示詞進行了處理了分析,提取出了關鍵的部分,我們來具體分析一下:
提示詞第一部分:身份設定:
提示詞第二部分:工具使用相關:
-
使用原則:說明工具需用戶批准後執行,每條消息只能用一個工具,依據上次工具使用結果逐步完成任務。
-
格式規範:給出工具使用的類似 XML 標籤格式示例,如
<read_file><path>src/main.js</path></read_file>
。
- 具體工具介紹:以
use_mcp_tool
爲例,詳細說明其描述、參數(必填的server_name、tool_name、argument
s)和用法,並給出使用示例。
- 使用指南:包括在
<Think>
標籤中評估信息、選擇合適工具、迭代使用工具、按格式使用工具、依據用戶回覆結果決策、等待用戶確認等內容。
提示詞第三部分:MCP Server 相關:
-
協議作用:介紹模型上下文協議(MCP)允許系統與本地運行的 MCP 服務器通信,擴展能力。
-
已連接服務器操作:說明連接服務器後可通過
use_mcp_tool
使用工具、access_mcp_resource
訪問資源。 -
可用工具:說明工具的描述、具體參數信息:
提示詞第四部分:一些約束信息:
提示詞第五部分:目標工作流程:
-
任務分析與目標設定:分析任務設定可實現目標並排序。
-
目標處理與工具使用:依次處理目標,每次用一個工具,調用工具前分析並確定參數。
-
任務完成與結果展示:完成任務用 attempt_completion 工具展示結果,可提供 CLI 命令,根據反饋改進,避免無意義對話。
這就是 Cline 系統提示詞的關鍵部分,當前這裏省略了一些和 MCP 不相關的信息,比如 Cline 本身提供的一些讀取文件、編寫代碼的工具等等。
- 第一次響應:這裏和
Cherry Studio
也不一樣,並沒有使用tools_call
字段,而是直接使用assistant
字段返回需要調用的工具信息:
我們格式化一下,發現和系統提示詞裏要求的工具調用格式是相同的:
- 第二次請求:客戶端將上次模型返回的工具調用參數,以及在本地執行工具調用後的結果都合併到 messages 裏,傳遞給模型,讓模型基於這些信息生成最終回答:
- 第二次響應:模型根據輸入的信息產出最終回答給用戶的內容,
這裏依然按照系統提示詞中約定的標準格式返回:
4.4 MCP 的核心流程總結
看到這裏,大家應該比較明確了,Cherry Studio
實際上是通過將 MCP Server
中提供的工具、響應結果,轉換爲 Function Call
的標準格式來和模型進行交互。
Cline
將 MCP Server
中提供的工具、響應結果轉換未一套自己約定的輸入、輸出的標準數據格式,通過系統提示詞來聲明這種約定,再和模型進行交互。
這也解釋了,爲什麼在 Cherry Studio
中只有一部分模型支持 MCP
,前提是選擇的模型需要支持 Function Call 的調用,並且在客戶端進行了特殊適配;而 Cline
則使用的是系統提示詞,所以所有模型都支持。
-
初始化與工具列表獲取
用戶首先對 MCP 客戶端進行初始化操作,隨後 MCP 客戶端向 MCP 服務器發送請求以獲取可用的工具列表,MCP 服務器將工具列表返回給客戶端。 -
用戶輸入與提示詞構建
用戶在客戶端完成初始化後,向 MCP 客戶端輸入具體請求。客戶端將此前獲取的工具列表與用戶輸入內容相結合,共同組成用於詢問 LLM 的提示詞。 -
工具傳遞方式選擇
MCP 客戶端通過兩種方式之一將提示詞傳遞給 LLM:
-
方式 1:使用 Function Call(函數調用)直接攜帶工具列表信息;
-
方式 2:在系統提示詞(System Prompt)中包含工具列表。
- LLM 判斷與響應
LLM 接收到提示詞後,返回判斷結果:
-
無需工具:LLM 直接將處理結果通過 MCP 客戶端回覆給用戶;
-
需要工具:LLM 先向客戶端返回所需工具的參數格式要求。
-
工具命令生成與執行
若需要工具,MCP 客戶端根據 LLM 提供的參數格式,以及 MCP Server 配置的命令模板進行拼接,生成完整的可執行命令,並在本地環境(Local_Env)中執行該命令。 -
結果處理與輸出
本地環境執行命令後,將結果返回給 MCP 客戶端。客戶端將執行結果提交給 LLM,由 LLM 對技術化的執行結果進行處理,最終以人性化的語言形式輸出給用戶。
五、使用 mcp-client-nodejs 展示 MCP 交互流程
爲了方便大家更好的學習和 MCP ,我使用 Node.js 開發了一個基礎版的 MCP Clinet
(基於 Function Call 實現),項目地址:https://github.com/ConardLi/mcp-client-nodejs
MCP Clinet
的開發還是稍微有點門檻的,所以不在這期教程裏演示具體的代碼細節,主要用此工具來幫助大家更深入的理解 MCP Clinet
和 MCP Server
的整個交互流程,至於開發者們如果感興趣可以直接基於我的項目進行二次開發。
5.1 基於 LLM 構建 MCP Client
同樣的,這個客戶端的核心邏輯也是基於 AI 編寫的,大家可以直接使用我這個提示詞:
## 需求
我想開發一個 Node.js 版的 MCP Clinet ,下面有一些參考材料,包括 MCP 基礎介紹、MCP 核心架構介紹、MCP Clinet 開發教程,請你根據這些參考材料幫我開發一個 MCP Client,注意增加完善的中文註釋,以及在開發完成後編寫一個完善的中文 Readme 說明文檔。
## MCP 基礎介紹
粘貼 https://modelcontextprotocol.io/introduction 的內容。
## MCP 核心架構介紹
粘貼 https://modelcontextprotocol.io/docs/concepts/architecture 的內容。
## MCP Client 開發教程
粘貼 https://modelcontextprotocol.io/quickstart/client 的內容。
## 注意點
- 使用 openai 的 SDK 替換 @anthropic-ai/sdk ,這樣可以支持更多的模型,兼容性更好
- 你不要指定依賴的版本,如果遇到安裝依賴讓我自己來安裝,你只負責編寫代碼
5.2 mcp-client-nodejs 項目介紹
核心特性
-
支持連接任何符合 MCP 標準的服務器
-
支持兼容 OpenAI API 格式的 LLM 能力
-
自動發現和使用服務器提供的工具
-
完善的日誌記錄系統,包括 API 請求和工具調用
-
交互式命令行界面
-
支持工具調用和結果處理
安裝和配置
- 克隆倉庫
git clone https://github.com/yourusername/mcp-client.git
cd mcp-client
- 安裝依賴
npm install
- 配置環境變量
複製示例環境變量文件並設置你的 LLM API 密鑰:
cp .env.example .env
然後編輯 .env
文件,填入你的 LLM API 密鑰、模型提供商 API 地址、以及模型名稱:
OPENAI_API_KEY=your_api_key_here
MODEL_NAME=xxx
BASE_URL=xxx
- 編譯項目
npm run build
使用方法
要啓動 MCP 客戶端,你可以使用以下幾種方式:
- 直接指定服務器腳本路徑
node build/index.js <服務器腳本路徑>
其中 <服務器腳本路徑>
是指向 MCP 服務器腳本的路徑,可以是 JavaScript (.js) 或 Python (.py) 文件。
- 使用配置文件
node build/index.js <服務器標識符> <配置文件路徑>
其中 <服務器標識符>
是配置文件中定義的服務器名稱,<配置文件路徑>
是包含服務器定義的 JSON 文件的路徑。
{
"mcpServers": {
"time": {
"command": "node",
"args": [
"/Users/xxx/Desktop/github/mcp/dist/index.js"
],
"description": "自定義 Node.js MCP服務器"
},
"mongodb": {
"command": "npx",
"args": [
"mcp-mongo-server",
"mongodb://localhost:27017/studentManagement?authSource=admin"
]
}
},
"defaultServer": "mongodb",
"system": "自定義系統提示詞"
}
- 使用 npm 包(npx)
你可以直接通過 npx 運行這個包,無需本地克隆和構建:
# 直接連接腳本
$ npx mcp-client-nodejs /path/to/mcp-server.js
# 通過配置文件連接
$ npx mcp-client-nodejs mongodb ./mcp-servers.json
注意:需要在當前運行目錄的 .env 配置模型相關信息
5.3 分析 MCP 詳細交互流程
MCP Client 包含一個全面的日誌系統,詳細記錄所有關鍵操作和通信。日誌文件保存在 logs/
目錄中,以 JSON 格式存儲,方便查詢和分析。
-
LLM 的請求和響應 - 記錄與 LLM API 的所有通信
-
工具調用和結果 - 記錄所有工具調用參數和返回結果
-
錯誤信息 - 記錄系統運行期間的任何錯誤
日誌文件連統命名爲 [index] [log_type] YYYY-MM-DD HH:MM:SS.json
,包含序號、日誌類型和時間戳,方便按時間順序查看整個會話。
下面我們使用 mcp-mongo-server 來演示整個流程,首先在項目目錄新建 mcp-servers.json,並填寫下面的配置:
{
"mcpServers": {
"mongodb": {
"command": "npx",
"args": [
"mcp-mongo-server",
"mongodb://localhost:27017/studentManagement?authSource=admin"
]
}
},
"system": "使用中文回覆。\n\n當用戶提問中涉及學生、教師、成績、班級、課程等實體時,需要使用 MongoDB MCP 進行數據查詢和操作,表結構說明如下:xxx"
}
然後執行 node build/index.js mongodb ./mcp-servers.json
,客戶端成功啓動:
然後我們輸入一個問題:張老師教哪門課? ,因爲教師信息、課程信息分別存儲在兩個表:
所以這個任務 AI 也需要分兩步完成,第一步先去檢索教室表中姓張的老師,我們輸入繼續:
下面檢索課程表,得出最終信息:
然後我們發現,整個過程一共產生了 13 調日誌:
我們逐個來看一下:
[0] [GET Tools]
:在客戶端初始化時,拉取當前配置的 MCP Server 下提供的所有工具列表:
[1] [LLM Request]
:向大模型發送 用戶問題,並將所有工具列表(包含函數 + 參數的具體定義)通過 tools 字段傳遞過去:
[2] [LLM Response]
:模型根據用戶輸入 + 支持的參數列表判斷需要使用的工具,並通過 tool_calls 字段返回:
[3] [Tool Call]
:客戶端根據模型返回的函數 + 參數 + MCP 服務器的配置,拼接成一條可執行的命令,執行第一次工具調用:從 teachers 表中檢索姓張的老師的信息:
[4] [Tool Call Response]
:工具返回結果:姓張的老師的 ID:
[5] [LLM Request]
:客戶端將工具調用的結果反饋給模型:
[6] [LLM Response]
:模型判斷本次任務沒有完成,需要繼續執行工具調用(根據教師 ID 去課程表查詢課程信息):
[7] [LLM Request]
:因爲一次交互已經完成,但並未完成任務,所以客戶端主動詢問用戶是否繼續,用戶選擇繼續,所以再次發送請求:
-
[8] [LLM Response]
:和 6 返回的內容一樣(實際上這一步有點重複),如果用戶選擇繼續可以直接發起第二次工具調用 -
[9] [Tool Call]
:客戶端執行工具調用,查詢課程信息:
[10] [Tool Call Response]
:工具返回班級信息:
[11] [LLM Request]
:客戶端將之前工具調用的所有信息再次返回給模型:
[12] [LLM Response]
:模型最終給出人性化輸出:
5.4 最終流程總結
以上的過程,非常清晰的展示了整個 MCP 的交互流程,下面我們最後再總結一下:
一、初始化階段
- 客戶端啓動與工具列表獲取
-
用戶首先啓動 MCPClient,完成初始化操作。
-
MCPClient 向 MCPServer 發送 GET /tools/list 請求,獲取可用工具的元數據。
-
MCPServer 返回包含工具名稱、功能描述、參數要求等信息的 工具列表 JSON,供客戶端後續構建提示詞使用。
二、交互階段
1. 用戶輸入與提示詞構建
-
用戶通過 MCPClient 輸入自然語言請求(如 “查詢服務器狀態”“生成文件報告” 等)。
-
MCPClient 將用戶請求與初始化階段獲取的 工具列表 結合,生成包含任務目標和工具能力的提示詞(Prompt),傳遞給 LLMService(大語言模型服務層)。
2. 工具描述傳遞方式(二選一)
-
方式 1(Function Call):
LLMService 通過 LLM_API 調用大語言模型時,在請求中直接攜帶 工具 schema(結構化工具定義,如參數格式、調用格式),告知模型可用工具的調用方式。 -
方式 2(系統提示詞嵌入):
LLMService 將工具列表以自然語言描述形式嵌入 系統提示詞(System Prompt),讓模型在理解用戶需求時知曉可用工具的功能邊界。
3. 模型決策與響應解析
-
LLM_API 返回包含 tool_decision(工具調用決策)的響應:
-
若判定 無需工具(如簡單文本回復),響應直接包含最終答案;
-
若判定 需要工具(如需要執行本地命令、調用外部接口),響應中包含所需工具的參數要求(如工具名稱、入參格式)。
-
LLMService 解析決策結果,將信息傳遞給 MCPClient。
4. 工具調用分支(需要工具時)
-
獲取命令模板: MCPClient 根據模型指定的工具名稱,在初始化時保存的工具配置中取出對應的 命令模板(如 Shell 命令格式、API 調用參數模板)。
-
生成與執行命令: MCPClient 將用戶輸入參數與命令模板結合,通過 ToolService(工具執行服務)生成完整可執行命令,並提交給 本地系統 執行。
-
結果處理:本地系統 返回原始執行結果(如命令輸出文本、API 返回數據),ToolService 將其轉換爲 結構化結果(如 JSON 格式),反饋給 MCPClient。
-
二次調用模型生成最終回覆:MCPClient 將結構化結果與用戶原始問題一併提交給 LLMService,通過 LLM_API 調用模型,將技術化的執行結果轉化爲自然語言描述(如將 “服務器 CPU 使用率 80%” 轉化爲“當前服務器 CPU 負載較高,建議檢查進程”)。
5. 直接回復分支(無需工具時)
- 若模型判定無需工具,MCPClient 直接將模型響應顯示給用戶(如簡單的文本問答、信息總結)。
三、最終輸出
無論是否經過工具調用,MCPClient 最終將處理後的 自然語言結果 呈現給用戶,完成整個交互流程。
六、最後
關注《code 祕密花園》從此學習 AI 不迷路,code 祕密花園 AI 教程完整的學習資料彙總在這個飛書文檔:https://rncg5jvpme.feishu.cn/wiki/U9rYwRHQoil6vBkitY8cbh5tnL9
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/yP6D_mnxwFsL3SbC4qZnYg