全網最細,一文帶你弄懂 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、arguments)和用法,並給出使用示例。
- 使用指南:包括在
<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