MCP 架構

MCP遵循客戶端 – 服務器架構,包含以下幾個核心部分:

為什么需要 MCP 呢?

舉個例子,例如我們目前還不能同時通過某個 AI 應用來做到聯網搜索、發送郵件、發布自己的博客等等,這些功能單個實現都不是很難,但是如果要全部集成到一個系統里面,就會變得遙不可及。可以想象一下日常開發中,有一個IDE ,我們可以通過 IDE 的 AI 來完成下面這些工作。

那有了 MCP 呢?其他服務都遵循 MCP 標準的話,就像萬能接口一樣,讓我們開發更高效了。

假設你正在使用一個 AI 編程助手來幫助你寫代碼。這個 AI 助手就是一個 MCP 主機。它需要訪問一些外部資源,比如代碼庫、文檔或者調試工具。MCP 服務器就像是一個中介,它連接了這些資源和 AI 助手。

使用 MCP 后,你直接對 AI 說:“幫我查一下最近數學考試的平均分,把不及格的同學名單整理到值日表里,并在微信群提醒他們補考。”AI 會自動完成:用 “萬能插頭” MCP 連接你的電腦,讀取 Excel 成績。用 MCP 連接微信,找到相關聊天記錄。用 MCP 修改在線文檔,更新值日表。整個過程不需要你手動操作,數據也不會離開你的設備,安全又高效。

所以,MCP 厲害的地方在于,不用重復造輪子。過去每個軟件(比如微信、Excel)都要單獨給 AI 做接口,現在 MCP 統一了標準,就像所有電器都用 USB-C 充電口,AI 一個接口就能連接所有工具。而且,數據不用上傳到云端,AI 直接在本地處理。比如你的成績單只存在自己電腦里,AI 通過 MCP 讀取分析,但數據不會外泄。

MCP 會讓 AI 更 “懂” 上下文,比如你讓 AI “總結上周班會的重點”,它能自動調取會議錄音、聊天記錄、筆記文檔,綜合這些信息給你答案,而不是憑空編造。所以,MCP 為 AI 應用提供了一個強大的工具,使其能夠更靈活、更安全地與外部世界交互。

MCP原理

VS Function Call

MCP的誕生標志著prompt engineering進入了一個新的發展階段,它通過提供更結構化的上下文信息,顯著提升了模型的能力。在設計prompt時,我們的目標是能夠將更加具體的信息(如本地文件、數據庫內容或網絡實時數據等)集成進來,從而使模型能夠更好地理解和解決實際問題。

回顧沒有 MCP 的時代,為了解決復雜問題,我們不得不手動從數據庫中篩選信息或使用工具來檢索相關信息,并將其逐一添加到 prompt 中。處理簡單問題時如需要大模型做歸納總結這種方法很奏效,但隨著問題復雜度的增加,這種方法變得越來越難以應對。

為了克服這些挑戰,許多大型語言模型(LLM)平臺(例如 OpenAI 和 Google)引入了 function call 功能。這一機制允許模型根據需要調用預定義函數以獲取數據或執行特定操作,大大提高了自動化程度。然而,function call 也有其局限性,包括對平臺的高度依賴以及不同 LLM 平臺間 API 實現的差異,這使得開發者在切換模型時必須重寫代碼,增加了開發成本。此外,還存在安全性和交互性等方面的挑戰。

實際上,數據和工具一直都在那里,關鍵在于如何更智能、更統一地將它們與模型連接起來。Anthropic 基于這樣的需求設計了 MCP,作為 AI 模型的“萬能適配器”,讓 LLM 能夠輕松訪問數據或調用工具。具體而言,MCP 的優勢體現在以下幾個方面:

模型如何智能選擇Agent/工具

MCP是核心是讓我們能方便地調用多個工具,那隨之而來的問題是LLM(模型)是在什么時候確定使用哪些工具的呢? Anthropic 為我們提供了詳細的解釋,當用戶提出一個問題時:

先理解第一步模型如何確定該使用哪些工具?我們可以參考MCP官方提供的client example為講解示例,并對相關代碼進行了簡化處理(移除了不影響邏輯理解的異常控制代碼部分)。通過分析這段代碼,可以看出模型是依靠prompt來識別當前可用的工具有哪些。具體做法是,我們將各個工具的使用描述以文本形式傳遞給模型,從而使模型能夠了解有哪些工具可供選擇,并基于實時情況做出最佳選擇。參考代碼中的注釋部分:

... # 省略了無關的代碼
async def start(self):
# 初始化所有的 mcp server
for server in self.servers:
await server.initialize()

# 獲取所有的 tools 命名為 all_tools
all_tools = []
for server in self.servers:
tools = await server.list_tools()
all_tools.extend(tools)

# 將所有的 tools 的功能描述格式化成字符串供 LLM 使用
# tool.format_for_llm() 我放到了這段代碼最后,方便閱讀。
tools_description = "\n".join(
[tool.format_for_llm() for tool in all_tools]
)

# 詢問 LLM(Claude) 應該使用哪些工具。
system_message = (
"You are a helpful assistant with access to these tools:\n\n"
f"{tools_description}\n"
"Choose the appropriate tool based on the user's question. "
"If no tool is needed, reply directly.\n\n"
"IMPORTANT: When you need to use a tool, you must ONLY respond with "
"the exact JSON object format below, nothing else:\n"
"{\n"
' "tool": "tool-name",\n'
' "arguments": {\n'
' "argument-name": "value"\n'
" }\n"
"}\n\n"
"After receiving a tool's response:\n"
"1. Transform the raw data into a natural, conversational response\n"
"2. Keep responses concise but informative\n"
"3. Focus on the most relevant information\n"
"4. Use appropriate context from the user's question\n"
"5. Avoid simply repeating the raw data\n\n"
"Please use only the tools that are explicitly defined above."
)
messages = [{"role": "system", "content": system_message}]

while True:
# Final... 假設這里已經處理了用戶消息輸入.
messages.append({"role": "user", "content": user_input})

# 將 system_message 和用戶消息輸入一起發送給 LLM
llm_response = self.llm_client.get_response(messages)

... # 后面和確定使用哪些工具無關

class Tool:
"""Represents a tool with its properties and formatting."""

def __init__(
self, name: str, description: str, input_schema: dict[str, Any]
) -> None:
self.name: str = name
self.description: str = description
self.input_schema: dict[str, Any] = input_schema

# 把工具的名字 / 工具的用途(description)和工具所需要的參數(args_desc)轉化為文本
def format_for_llm(self) -> str:
"""Format tool information for LLM.

Returns:
A formatted string describing the tool.
"""
args_desc = []
if "properties" in self.input_schema:
for param_name, param_info in self.input_schema["properties"].items():
arg_desc = (
f"- {param_name}: {param_info.get('description', 'No description')}"
)
if param_name in self.input_schema.get("required", []):
arg_desc += " (required)"
args_desc.append(arg_desc)

return f"""
Tool: {self.name}
Description: {self.description}
Arguments:
{chr(10).join(args_desc)}
"""

總結:模型是通過 prompt engineering,即提供所有工具的結構化描述和 few-shot 的 example 來確定該使用哪些工具另一方面,Anthropic 肯定對 Claude 做了專門的訓練,畢竟是自家協議,Claude 更能理解工具的 prompt 以及輸出結構化的 tool call json 代碼。

工具執行與結果反饋機制

工具的執行就比較簡單和直接了。承接上一步,我們把 system prompt(指令與工具調用描述)和用戶消息一起發送給模型,然后接收模型的回復。當模型分析用戶請求后,它會決定是否需要調用工具:

如果回復中包含結構化 JSON 格式的工具調用請求,則客戶端會根據這個 json 代碼執行對應的工具。具體的實現邏輯都在 process_llm_response 中,代碼、邏輯非常簡單。

如果模型執行了 tool call,則工具執行的結果 result 會和 system prompt 和用戶消息一起重新發送給模型,請求模型生成最終回復。如果 tool call 的 json 代碼存在問題或者模型產生了幻覺怎么辦呢?通過閱讀代碼 發現,我們會 skip 掉無效的調用請求。執行相關的代碼與注釋如下:

... # 省略無關的代碼
async def start(self):
... # 上面已經介紹過了,模型如何選擇工具

while True:
# 假設這里已經處理了用戶消息輸入.
messages.append({"role": "user", "content": user_input})

# 獲取 LLM 的輸出
llm_response = self.llm_client.get_response(messages)

# 處理 LLM 的輸出(如果有 tool call 則執行對應的工具)
result = await self.process_llm_response(llm_response)

# 如果 result 與 llm_response 不同,說明執行了 tool call (有額外信息了)
# 則將 tool call 的結果重新發送給 LLM 進行處理。
if result != llm_response:
messages.append({"role": "assistant", "content": llm_response})
messages.append({"role": "system", "content": result})

final_response = self.llm_client.get_response(messages)
logging.info("\nFinal response: %s", final_response)
messages.append(
{"role": "assistant", "content": final_response}
)
# 否則代表沒有執行 tool call,則直接將 LLM 的輸出返回給用戶。
else:
messages.append({"role": "assistant", "content": llm_response})

根據上述原理分析,可以看出工具文檔至關重要。模型依賴于工具描述文本來理解和選擇適用的工具,這意味著精心編寫的工具名稱、文檔字符串(docstring)以及參數說明顯得尤為重要。鑒于MCP的選擇機制基于prompt實現,理論上任何模型只要能夠提供相應的工具描述就能與MCP兼容使用。

MCP Server 開發實踐

對絕大部分 AI 開發者來說,除了了解MCP的原理,我們更關心 Server 的實現。因此,我這里準備通過一個最簡單的示例來介紹如何實現一個 MCP Server。MCP servers 可以提供三種主要類型的功能:

本教程將主要關注工具(Tools)。

1、使用 LLM 構建 MCP 的最佳實踐

在開始之前,Anthropic 為我們提供了一個基于LLM 的 MCP Server 的最佳開發實踐(https://modelcontextprotocol.io/tutorials/building-mcp-with-llms),Guide里面特意提到了,該實踐是基于Claude,也可以基于其它LLM。總結如下:

1.引入 domain knowledge (說人話就是,告訴他一些 MCP Server 開發的范例和資料)

2.描述你的需求

給出一個 example prompt:

... (這里是已經引入的 domain knowledge)
打造一個 MCP 服務器,它能夠:
- 連接到我公司的 PostgreSQL 數據庫- 將表格結構作為資源開放出來- 提供運行只讀 SQL 查詢的工具- 包含常見數據分析任務的引導

剩下的部分也很重要,但是偏重于方法論,實踐性較弱,這里就不展開了,推薦直接看鏈接:https://modelcontextprotocol.io/tutorials/building-mcp-with-llms。

2、手動實踐

本節內容主要參考了官方文檔:Quick Start: For Server Developers(https://modelcontextprotocol.io/quickstart/server)。這里準備了一個簡單的示例,使用 Python 實現一個 MCP Server,用來統計當前桌面上的 txt 文件數量和獲取對應文件的名字(你可以理解為一點用都沒有,但是它足夠簡單,主要是為了難以配置環境的讀者提供一個足夠短的實踐記錄)。以下實踐均運行在我的 MacOS 系統上。

Step1. 前置工作

Step2. 環境配置

# 安裝 uvcurl -LsSf https://astral.sh/uv/install.sh | sh
# 創建項目目錄uv init txt_countercd txt_counter
# 設置 Python 3.10+ 環境echo "3.11" > .python-version
# 創建虛擬環境并激活uv venvsource .venv/bin/activate
# Install dependenciesuv add "mcp[cli]" httpx
# Create our server filetouch txt_counter.py

Question: 什么是 uv 呢和 conda 比有什么區別?

Answer: 一個用 Rust 編寫的超快速 (100x) Python 包管理器和環境管理工具,由 Astral 開發。定位為 pip 和 venv 的替代品,專注于速度、簡單性和現代 Python 工作流。

Step3. 構造一個 prompt

"""... (這里是已經引入的 domain knowledge)"""
打造一個 MCP 服務器,它能夠:- 功能: - 統計當前桌面上的 txt 文件數量 - 獲取對應文件的名字
要求:- 不需要給出 prompt 和 resource 相關代碼。- 你可以假設我的桌面路徑為 /Users/{username}/Desktop

Domain Knowledge 復制于 MCP Python SDK的README文件(https://raw.githubusercontent.com/modelcontextprotocol/python-sdk/refs/heads/main/README.md)

Step4. 實現 MCP Server

以下代碼由 Claude 3.7 直接生成。當然這里主要是因為需求足夠簡單,當需要實現一個復雜的 MCP Server 時,可能需要多步的引導和 Debug 才能得到最終的代碼。

import?osfrom?pathlib?
import?Pathfrom?mcp.server.fastmcp?
import?FastMCP
# 創建 MCP Servermcp = FastMCP("桌面 TXT 文件統計器")

@mcp.tool()
def?count_desktop_txt_files() ->?int:? ??
"""Count the number of .txt files on the desktop."""
? ??# Get the desktop path? ?
username = os.getenv("USER")?or?os.getenv("USERNAME")
? ? desktop_path = Path(f"/Users/{username}/Desktop")
? ??

# Count .txt files? ?
txt_files =?list(desktop_path.glob("*.txt"))
? ??return?len(txt_files)

@mcp.tool()
def?list_desktop_txt_files() ->?str:
? ??"""Get a list of all .txt filenames on the desktop."""
? ??# Get the desktop path? ?
username = os.getenv("USER")?or?os.getenv("USERNAME")
? ? desktop_path = Path(f"/Users/{username}/Desktop")
? ??
# Get all .txt files
? ? txt_files =?list(desktop_path.glob("*.txt"))
? ??# Return the filenames
? ??if?not?txt_files:
? ? ? ??return?"No .txt files found on desktop."
? ??
# Format the list of filenames
? file_list =?"\n".join([f"-?{file.name}"?for?file?in?txt_files])
? ??return?f"Found?{len(txt_files)}?.txt files on desktop:\n{file_list}"

if?__name__ ==?"__main__":
? ??# Initialize and run the server? ?
mcp.run()

任務非常簡單,只需要調用非常基本的os就可以完成。

Step5. 測試 MCP Server

$ mcp dev txt_counter.pyStarting MCP inspector...Proxy server listening on port 3000
MCP Inspector is up and running at http://localhost:5173

之后進入到給出的鏈接中,你大概能按下圖進行操作:

Step6. 接入 Claude

最后一步就是把我們寫好的 MCP 接入到 Claude Desktop 中。流程如下:

# 打開 claude_desktop_config.json (MacOS / Linux)
# 如果你用的是 cursor 或者 vim 請更換對應的命令
code ~/Library/Application\ Support/Claude/claude_desktop_config.json

在配置文件中添加以下內容,記得替換相關路徑為實際路徑。

{
"mcpServers": {
"txt_counter": {
"command": "/opt/homebrew/bin/uv",
"args": [
"--directory",
"/Users/yangfan/mcp/txt_counter",
"run",
"txt_counter.py"
]
}
}
}

uv最好是絕對路徑,推薦使用 which uv 獲取。

配置好后重啟 Claude Desktop,如果沒問題就能看到對應的 MCP Server 了。

Step7. 實際使用

接下來,我們通過一個簡單的 prompt 進行實際測試:

能推測我當前桌面上 txt 文件名的含義嗎?

它可能會請求你的使用權限,如圖一所示,你可以點擊 Allow for This Chat

看起來我們 MCP Server 已經正常工作了!

3、MCP Server Debug

Debug 是一個非常復雜的話題,這里直接推薦官方的教程:

總結

MCP (Model Context Protocol) 代表了 AI 與外部工具和數據交互的標準建立。通過本文,我們可以了解到:

MCP 還處于發展初期,但其潛力巨大。更重要的是生態,基于統一標準下構筑的生態也會正向的促進整個領域的發展。我們可以看看MCP工具平臺:https://mcp.so/

文章轉載自: 一文講透MCP的原理及實踐

上一篇:

DeepSeek+dify 工作流應用,自然語言查詢數據庫信息并展示

下一篇:

實測:阿里云百煉上線「全周期 MCP 服務」,AI 工具一站式托管
#你可能也喜歡這些API文章!

我們有何不同?

API服務商零注冊

多API并行試用

數據驅動選型,提升決策效率

查看全部API→
??

熱門場景實測,選對API

#AI文本生成大模型API

對比大模型API的內容創意新穎性、情感共鳴力、商業轉化潛力

25個渠道
一鍵對比試用API 限時免費

#AI深度推理大模型API

對比大模型API的邏輯推理準確性、分析深度、可視化建議合理性

10個渠道
一鍵對比試用API 限時免費