大模型系列22-MCP
- 玩轉 MCP 協議:用 Cline + DeepSeek 接入天氣服務
- 什么是 MCP?
- 環境準備:VScode + Cline + DeepSeek
- **配置 DeepSeek 模型:**
- **配置 MCP 工具**
- **uvx是什么?**
- **安裝 uv(會自動有 uvx 命令)**
- **驗證uvx的安裝**:`uvx pycowsay 'hello world'`
- **通過uvx 手動安裝 fetch 工具**
- uv 命令找不到
- **使用 fetch 工具**
- MCP 示例:查詢紐約天氣
- 步驟一:配置 MCP Server
- 步驟二:安裝 uv 和運行測試
- 步驟三:寫一個 MCP 工具服務 weather.py
- 步驟四:手動驗證 weather.py
- 直接通過 Cline 來發起調用
- 全人肉交互式操作
- 步驟五:Cline 和 MCP 之間的工具代理
- 中間日志代理工具
- mcp server 測試客戶端
- 步驟六:模型調用工具示例流程
- 總結:MCP 的本質與價值
玩轉 MCP 協議:用 Cline + DeepSeek 接入天氣服務
本文記錄了使用 MCP(Model Context Protocol)協議,通過 Cline 插件接入 DeepSeek 模型,并構建一個天氣查詢工具的完整過程。包括從安裝環境、插件配置,到實現 MCP Server 的交互流程,適合對 AI 工具鏈、智能體、RAG 和插件生態感興趣的開發者參考。
本文只是一個自學的文章,其內容參考自bilibili:馬克的技術工作坊,三篇 鏈接,這個講的很好,建議大家都可以去看看他的三個系列
在開始之前,我們先借用視頻中的一張圖,來看一下 MCP 處于的位置,以及他是如何和我們的模型進行溝通的。這里的用戶是我們,MCP Server 是手寫的一個 python Weather 工具,它可以接收經緯度,返回天氣情況。模型是 DeepSeek 模型,我事先充值了 10元,以實現 API 調用。Cline 是一個 vscode 插件,它是一個 Agent(也有人將它稱為 MCP Host,我個人將它成為Agent,因為它從功能上就像是一個 Agent 智能體),用于連接 MCP Server 和 模型的橋梁。
啟動階段
- 啟動 MCP Server(如 weather.py)。
- Cline 啟動后向 MCP Server 打招呼,并請求工具列表。
- MCP Server 返回可用工具(如 get_forecast, get_alerts)。
用戶提問
- 用戶輸入自然語言問題,向Cline發問,如:“紐約明天的天氣怎么樣?”
- Cline將問題以及可用的工具發送給模型
模型接收用戶問題
- 分析出這是一個需要天氣信息的任務。
- 判斷出可以調用 get_forecast 工具,并準備參數(如經緯度),將其發給Cline
Cline 調用工具
- Cline 發送工具調用請求(如 get_forecast)到 MCP Server。
- MCP Server 執行本地 Python 工具函數,處理請求。
MCP 返回結果
- MCP Server 將結果返回給 Cline。
- Cline 將結果傳給模型,模型進行結果理解和語言組織。
模型生成回復
- 模型將調用結果包裝成自然語言輸出。
用戶收到回答
- 用戶最終看到如:“紐約明天的天氣是多云,最高 25°C,最低 18°C” 這樣的自然語言回復。
什么是 MCP?
MCP(Model Context Protocol)是一個用于定義模型與外部工具如何交互的協議。它可以告訴大模型有哪些工具可用、工具的參數和返回格式,然后觸發外部 Agent(如Cline)調用工具,實現讓大模型更好的使用工具的能力。說是大模型使用工具,其實是Agent發送給模型自己的工具列表,模型判斷出要使用什么工具,然后模型告訴Agent去調用,Agent執行工具調用之后,然后將調用結果返回給模型,由模型決策后續的行為(或者是直接總結工具的結果返回給用戶,或者是進一步調用工具等)。
簡單理解:MCP 就是“大模型如何用工具”的語言。
環境準備:VScode + Cline + DeepSeek
啟動 VSCode 之后,我們搜索 Cline 插件并安裝。安裝 Cline 插件后,我們可以將本地工具注冊為 MCP 服務,通過 DeepSeek 等大模型驅動調用。這包括配置模型以及配置 MCP 工具兩個步驟。
配置 DeepSeek 模型:
- 先訪問DeepSeek官網,進行API充值。充值還挺方便的,前天充值的10元,經過這幾天測試驗證MCP的調用,剛才看了下還有 9.57 元。
- 然后訪問API KEYS,創建一個 API KEY,用于 MCP 的驗證。我們在 Cline 插件中,點擊 Setttings,即可進行如下圖的 API Provider 等配置,填入 API KEY,就可以通過 Cline 使用 DeepSeek 啦
發送一句 “Hi”,測試模型是否接入成功:模型回答了一些內容。
配置 MCP 工具
我們有很多現成的 MCP 工具可以用,如 mcp.so
,mcpmarket.com
,smithery.ai
我們先嘗試一個網頁抓取的 MCP 應用:https://mcp.so/server/fetch/modelcontextprotocol
。它叫 fetch
,是用于抓取網頁的。
那如何在 Cline 中安裝該fetch
工具?我們需要進行如下圖的操作流程,點擊 Cline
,打開它的 MCP Servers
,然后點擊 Configure MCP Servers
,它會自動打開 cline_mcp_settting.json
文件。如果你是第一次開的時候,它的內容為空。
我們修改 cline_mcp_settting.json
復制以下內容,該 fetch
工具就會被自動配置到 Cline 中。我們使用 uvx 來啟動這個 fetch
工具。
"mcpServers": {"fetch": {"command": "uvx","args": ["mcp-server-fetch"]}
}
這里如果你看到 fetch
工具旁邊有報錯,先不用慌,它的報錯有以下幾個原因:
(1)沒有安裝 uvx
(2)下載 fetch
MCP 工具失敗
(3)安裝了 uvx
但是找不到 uvx
的命令
后面會給出詳細的解決方案。
uvx是什么?
uvx 是 Astral 的 uv 包管理工具 中的一個子命令,用于在隔離環境中運行 Python 腳本或工具,類似于 npx 之于 npm。它就是直接搞一個臨時隔離環境,在隔離環境中執行命令,不會影響現有的 python 環境。
工具 | 作用 |
---|---|
uv | pip + venv 的超快替代品(用于安裝、構建、運行 Python 項目) |
uvx | 在臨時隔離環境中運行一個 PyPI 包提供的命令行工具(無需手動安裝) |
安裝 uv(會自動有 uvx 命令)
curl -Ls https://astral.sh/uv/install.sh | bash 實在是太慢了,替換成 pip 清華源安裝方式
pip install uv -i https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple
驗證uvx的安裝:uvx pycowsay 'hello world'
通過uvx 手動安裝 fetch 工具
uvx mcp-server-fetch --index-url https://pypi.tuna.tsinghua.edu.cn/simple
Cline 在第一次啟動 fetch 的時候,它會自動下載這個 fetch 工具,但是我當時的嘗試來看,它最終是自動下載不成功(fetch 那個地方會報告 Error),于是我轉為手動下載,執行如下的下載命令即可:
uvx mcp-server-fetch --index-url https://pypi.tuna.tsinghua.edu.cn/simple
uv 命令找不到
如果在 VScode 報找不到 uv
的錯誤(如下圖所示,spawn uv ENOENT
表明 uv
命令找不到,這里圖中是 weather
,不過 fetch
工具也一樣),這應該是 vscode 沒有讀取到正確的 uv 環境變量路徑。我的操作方案是通過 WSL 命令行運行 code .
命令啟動 VSCode,這可以確保環境變量都被正確加載。當執行完畢后,再點擊 Retry Connection 的時候,就不會報告 uv ENONT 的錯誤了。
處理好之后,如果 fetch 還沒有正常,它會有個 Reconnect按鈕,點擊 fetch 處有個 Reconnect 重連,一般就可以成功啦。fetch 工具后面有兩個按鈕(如果都為綠色,表明成功):第一個是 啟用/禁用,第二個是工具是否正常。
使用 fetch 工具
我們向 Cline 提問進行抓取網頁
請抓取下面這個網頁的內容,并將其轉換為 markdown 后放到項目目錄里面的 guides.md 文件中:
https://docs.astral.sh/uv/guides/install-python/
如下圖所示,我們這個問題送給 DeepSeek模型 之后,它知道我們要使用 fetch 工具,并返回給 Cline 操作步驟,Cline 收到 fetch工具獲取網頁內容 這個命令后,會調用 fetch 工具獲取網頁內容,并將內容發送給 模型,模型會解析網頁內容,然后返回給 Cline 寫入guides.md文件 的命令,Cline 會執行該寫入命令,最終的內容如 guides.md 所示。
這里面我們忽略了一些細節
- 模型怎么知道我們有 fetch 這個工具?模型它自己不知道,其實是我們將上面的抓取問題給 Cline 之后,Cline 會將問題以及 Cline 當前安裝的 MCP 工具命令和參數類型信息全部發送給模型,模型才知道我們有 fetch 這個工具。
- 是模型調用的 fetch 工具嗎?不是模型來直接調用工具,是模型收到了問題和工具列表,它分析一通,給出 Cline 的回復是可以調用 fetch 這個命令,Cline 收到模型給出的工具調用指令,然后 Cline 發起 fetch 工具的調用,并將工具的結果返回給模型。
MCP 示例:查詢紐約天氣
假設我們向模型提問:
明天紐約的天氣怎么樣?
因為模型基于歷史數據訓練出來的,它是無法預知明天的天氣怎么樣的,這必須需要依賴 MCP 工具來完成。我們在隨后的內容中講述如何來創建一個簡單的 MCP 工具來獲取天氣信息。假設我們有了一個 MCP weather 的工具,
按照 MCP 的設計,模型會解析出“需要天氣數據”的需求,并調用本地的 weather
MCP Server 來完成查詢。
步驟一:配置 MCP Server
我們需要先構建一個 MCP 協議的本地服務,它通過標準輸入輸出 (stdio
) 和 cline 通信。
{"mcpServers": {"weather": {"timeout": 60,"command": "uv","args": ["--directory","/mnt/c/workspace/llm/mcp/weather","run","weather.py"],"transportType": "stdio"}}
}
字段說明如下:
字段 | 含義 |
---|---|
command | 使用 uv 啟動 Python 腳本 |
args | 啟動時附帶的參數數組: --directory 指定工作目錄 "run" 是執行命令 "weather.py" 是要運行的腳本。 |
transportType | 使用 stdio 方式通信,意味著Cline與工具通過 I/O 管道進行通信 |
timeout | 超時時間 60 秒 |
上述配置相當于 Cline 執行了這樣的一個命令:
uv --directory /mnt/c/workspace/llm/mcp/weather run weather.py
我們通過修改 cline_mcp_settting.json
文件,給 Cline 增加了一個獲取天氣的 MCP 工具。不過這里我們還沒有這樣的 weather.py
文件,因此 Cline 的工具列表中 weather 工具并沒有正常工作,我們將在隨后的章節中增加相應的代碼,以完成一個獲取天氣的工具應用。
步驟二:安裝 uv 和運行測試
前面已經介紹過 uv 的細節,如果你已經安裝過,請忽略。
使用 pip 安裝 uv
:
pip install uv -i https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple
驗證安裝:
uvx pycowsay 'hello world'
步驟三:寫一個 MCP 工具服務 weather.py
以下是核心實現,使用 fastmcp
框架 + httpx
請求 OpenWeather 或 NWS 數據。
from fastmcp import FastMCP
import httpx
from typing import Anymcp = FastMCP("weather")
NWS_API_BASE = "https://api.weather.gov"async def make_nws_request(url: str) -> dict[str, Any] | None:...@mcp.tool()
async def get_forecast(latitude: float, longitude: float) -> str:...@mcp.tool()
async def get_alerts(state: str) -> str:...if __name__ == "__main__":mcp.run(transport='stdio')
工具被 @mcp.tool()
裝飾后,我們就可以通過 MCP 協議和該工具進行交互了。mcp.tool裝飾器將代碼轉換為 tool
@mcp.tool()
async def get_forecast(latitude: float, longitude: float) -> str:"""Get weather forecast for a location.Args:latitude: Latitude of the locationlongitude: Longitude of the location"""
轉換為這樣的一個 tool
{"name": "get_forecast","description": "Get weather forecast for a location.\n\n Args:\n latitude: Latitude of the location\n longitude: Longitude of the location\n ","inputSchema": {"properties": {"latitude": {"title": "Latitude","type": "number"},"longitude": {"title": "Longitude","type": "number"}},"required": ["latitude","longitude"],"type": "object"}}
MCP 協議規定了每個mcp server有哪些函數以及每個函數的調用方法,它并沒有規定和模型的交互方式
weather.py 全部代碼如下:
from typing import Any
import httpx
from fastmcp import FastMCP# Initialize FastMCP server
mcp = FastMCP("weather")# Constants
NWS_API_BASE = "https://api.weather.gov"
USER_AGENT = "weather-app/1.0"async def make_nws_request(url: str) -> dict[str, Any] | None:"""Make a request to the NWS API with proper error handling."""headers = {"User-Agent": USER_AGENT,"Accept": "application/geo+json"}async with httpx.AsyncClient() as client:try:response = await client.get(url, headers=headers, timeout=30.0)response.raise_for_status()return response.json()except Exception:return Nonedef format_alert(feature: dict) -> str:"""Format an alert feature into a readable string."""props = feature["properties"]return f"""
Event: {props.get('event', 'Unknown')}
Area: {props.get('areaDesc', 'Unknown')}
Severity: {props.get('severity', 'Unknown')}
Description: {props.get('description', 'No description available')}
Instructions: {props.get('instruction', 'No specific instructions provided')}
"""@mcp.tool()
async def get_alerts(state: str) -> str:"""Get weather alerts for a US state.Args:state: Two-letter US state code (e.g. CA, NY)"""url = f"{NWS_API_BASE}/alerts/active/area/{state}"data = await make_nws_request(url)if not data or "features" not in data:return "Unable to fetch alerts or no alerts found."if not data["features"]:return "No active alerts for this state."alerts = [format_alert(feature) for feature in data["features"]]return "\n---\n".join(alerts)@mcp.tool()
async def get_forecast(latitude: float, longitude: float) -> str:"""Get weather forecast for a location.Args:latitude: Latitude of the locationlongitude: Longitude of the location"""# First get the forecast grid endpointpoints_url = f"{NWS_API_BASE}/points/{latitude},{longitude}"points_data = await make_nws_request(points_url)if not points_data:return "Unable to fetch forecast data for this location."# Get the forecast URL from the points responseforecast_url = points_data["properties"]["forecast"]forecast_data = await make_nws_request(forecast_url)if not forecast_data:return "Unable to fetch detailed forecast."# Format the periods into a readable forecastperiods = forecast_data["properties"]["periods"]forecasts = []for period in periods[:5]: # Only show next 5 periodsforecast = f"""
{period['name']}:
Temperature: {period['temperature']}°{period['temperatureUnit']}
Wind: {period['windSpeed']} {period['windDirection']}
Forecast: {period['detailedForecast']}
"""forecasts.append(forecast)return "\n---\n".join(forecasts)if __name__ == "__main__":# Initialize and run the servermcp.run(transport='stdio')
weather.py 代碼放到對應的目錄/mnt/c/workspace/llm/mcp/weather
下,先手動命令行執行:uv --directory /mnt/c/workspace/llm/mcp/weather run weather.py
,確認可以執行成功。
在 cline 中找到 Installed MCP 工具后,對 weather 點擊 Reconnect 即可看到 weather 正確連接了。
步驟四:手動驗證 weather.py
當我們開發了一個 weather.py 的工具,我們有兩種驗證它的方案,一種是直接通過 Cline 來發起調用;另外一種是全人肉交互式操作。通過 Cline 發起調用可以感受到模型調用工具的樂趣;而通過全人肉的方式相當于能夠扒開 Cline 的內部實現,更清晰的理解工具調用的整體流程圖。
直接通過 Cline 來發起調用
從該圖可以看出,模型識別到了問題以及 weather 工具,且回復使用工具 weather來獲取天氣信息,并給出坐標。
weather 工具收到坐標后,從NWS_API_BASE = “https://api.weather.gov” 網站獲取到天氣信息后,將內容(下圖的一堆英文天氣信息)返回給 Cline,Cline將英文天氣內容發給模型,模型經過一番總結后,給出了最終的回復:
紐約明天(5月26日)天氣預報:
- 白天:大部分時間晴朗,最高氣溫約71°F(22°C),西北風2-7 mph
- 夜間:部分多云,最低氣溫約57°F(14°C),西南風約3 mph
全人肉交互式操作
手動啟動 weather.py:uv --directory /mnt/c/workspace/llm/mcp/weather run weather.py
使用 MCP 協議和該工具進行交互,由于它是通過標準輸入輸出來交互的,因此我們可以直接將下面的內容逐條粘貼到上述命令窗口中,和它進行交互:
// 初始化請求
{"method": "initialize", "params": {"protocolVersion": "2024-11-05", "capabilities": {}, "clientInfo": {"name": "cline", "version": "1.0"}}, "jsonrpc": "2.0", "id": 0}// 通知:已初始化
{"method": "notifications/initialized", "jsonrpc": "2.0"}// 工具列表請求
{"method": "tools/list", "jsonrpc": "2.0", "id": 1}// 發起獲取天氣的請求
{"method":"tools/call","params":{"name":"get_forecast","arguments":{"latitude":40.7128,"longitude":-74.006}},"jsonrpc":"2.0","id":4}
上述命令會得到以下的輸出,合并起來就有三大步:
(1):和MCP工具進行握手
(2):獲取其支持的工具列表:get_alerts, get_forecast
(3):調用 get_forecast 工具
步驟五:Cline 和 MCP 之間的工具代理
你可能會有疑問,如何才能知道用什么格式的內容和工具進行交互呢?它的輸入的格式為什么是這個樣子呢?我們可以通過在 Cline 和 MCP 工具中間增加一個中間代理,來截獲 Cline 到 weather 的消息協議,進而就可以手動來發起工具調用了。
中間日志代理工具
使用 GPT 寫了一個代碼,用于在 Cline 和 MCP工具之間加一個代理。 mcp_logger.py
是一個 中間日志代理工具,用于在運行 MCP Server(如 weather.py
)時,記錄Cline與工具之間的輸入輸出交互內容,主要功能如下:
-
攔截標準輸入(stdin)傳給 MCP Server 的數據(例如 Cline 發來的 JSON-RPC 請求),并記錄為
"輸入 >>> ..."
; -
攔截 MCP Server 標準輸出(stdout)返回給 Cline 的響應結果,并記錄為
"輸出 >>> ..."
; -
所有的交互式日志都會追加寫入
mcp_io_log.txt
文件,帶有時間戳; -
保持原有輸入輸出鏈路不中斷:數據一邊轉發,一邊被記錄。
中間日志代理工具用法示例:
python mcp_logger.py uv --directory /mnt/c/workspace/llm/mcp/weather run weather.py
這樣一來,無需改動原有 MCP 工具 weather.py
服務代碼,就能實時查看Cline
與MCP
的通信過程,方便調試和分析。這個工具 mcp_logger.py
的代碼如下:
import sys
import asyncio
import datetime
import inspectLOG_FILE = "/mnt/c/workspace/llm/mcp/weather/mcp_io_log.txt"def log_to_file(prefix: str, message: str):timestamp = datetime.datetime.now().isoformat()with open(LOG_FILE, "a", encoding="utf-8") as f:f.write(f"[{timestamp}] {prefix} >>> {message}\n")async def forward_stream(src_reader, dst_writer, tag, decode_bytes=False):while True:line = await src_reader.readline()if not line:breakif isinstance(line, bytes) and decode_bytes:decoded = line.decode("utf-8", errors="replace").rstrip()log_to_file(tag, decoded)await maybe_await(dst_writer, decoded + "\n")else:log_to_file(tag, line.decode("utf-8", errors="replace").rstrip()if isinstance(line, bytes) else line.rstrip())await maybe_await(dst_writer, line)async def maybe_await(fn, arg):"""Call fn(arg), await if it's async."""result = fn(arg)if inspect.isawaitable(result):await resultasync def main():if len(sys.argv) < 2:print("Usage: python mcp_logger.py <real-server-command...>", file=sys.stderr)sys.exit(1)child_cmd = sys.argv[1:]proc = await asyncio.create_subprocess_exec(*child_cmd,stdin=asyncio.subprocess.PIPE,stdout=asyncio.subprocess.PIPE)loop = asyncio.get_running_loop()stdin_reader = asyncio.StreamReader()await loop.connect_read_pipe(lambda: asyncio.StreamReaderProtocol(stdin_reader), sys.stdin)async def write_to_proc_stdin(data):if isinstance(data, str):data = data.encode("utf-8")proc.stdin.write(data)await proc.stdin.drain()def write_to_stdout(data):sys.stdout.write(data)sys.stdout.flush()await asyncio.gather(forward_stream(stdin_reader, write_to_proc_stdin,"輸入", decode_bytes=False),forward_stream(proc.stdout, write_to_stdout,"輸出", decode_bytes=True))if __name__ == "__main__":asyncio.run(main())
現在我們在 Cline 中不再直接配置 weather.py
這個工具了,而是用mcp_logger.py
將它包裝成一個新的 weather
工具。如下,我們將 mcp_logger.py
配置到 cline_mcp_settings.json
文件中,它將 weather.py
作為參數。
"weather": {"disabled": false,"timeout": 60,"command": "python","args": ["/mnt/c/workspace/llm/mcp/weather/mcp_logger.py","uv","--directory","/mnt/c/workspace/llm/mcp/weather","run","weather.py"],"transportType": "stdio"}
最終的啟動命令為:
python mcp_logger.py uv --directory /mnt/c/workspace/llm/mcp/weather run weather.py
配置完畢 Cline 后,我們再次向 Cline 提問“紐約明天的天氣怎么樣”,mcp_logger.py
會將和工具交互的內容記錄到/mnt/c/workspace/llm/mcp/weather/mcp_io_log.txt
文件。這里面的流水賬就是和工具交互的幾大步驟,更多細節可以參看 mcp server 測試客戶端
章節。
[2025-05-24T18:53:27.437433] 輸入 >>> {"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"Cline","version":"3.17.4"}},"jsonrpc":"2.0","id":0}
[2025-05-24T18:53:28.024624] 輸出 >>> {"jsonrpc":"2.0","id":0,"result":{"protocolVersion":"2025-03-26","capabilities":{"experimental":{},"prompts":{"listChanged":false},"resources":{"subscribe":false,"listChanged":false},"tools":{"listChanged":true}},"serverInfo":{"name":"weather","version":"1.9.1"}}}
[2025-05-24T18:53:28.026294] 輸入 >>> {"method":"notifications/initialized","jsonrpc":"2.0"}
[2025-05-24T18:53:28.027355] 輸入 >>> {"method":"tools/list","jsonrpc":"2.0","id":1}
[2025-05-24T18:53:28.031537] 輸出 >>> {"jsonrpc":"2.0","id":1,"result":{"tools":[{"name":"get_alerts","description":"Get weather alerts for a US state.\n\n Args:\n state: Two-letter US state code (e.g. CA, NY)\n ","inputSchema":{"properties":{"state":{"title":"State","type":"string"}},"required":["state"],"type":"object"}},{"name":"get_forecast","description":"Get weather forecast for a location.\n\n Args:\n latitude: Latitude of the location\n longitude: Longitude of the location\n ","inputSchema":{"properties":{"latitude":{"title":"Latitude","type":"number"},"longitude":{"title":"Longitude","type":"number"}},"required":["latitude","longitude"],"type":"object"}}]}}
[2025-05-24T18:53:28.034546] 輸入 >>> {"method":"resources/list","jsonrpc":"2.0","id":2}
[2025-05-24T18:53:28.037251] 輸出 >>> {"jsonrpc":"2.0","id":2,"result":{"resources":[]}}
[2025-05-24T18:53:28.038989] 輸入 >>> {"method":"resources/templates/list","jsonrpc":"2.0","id":3}
[2025-05-24T18:53:28.041609] 輸出 >>> {"jsonrpc":"2.0","id":3,"result":{"resourceTemplates":[]}}
[2025-05-24T18:55:20.618362] 輸入 >>> {"method":"tools/call","params":{"name":"get_forecast","arguments":{"latitude":40.7128,"longitude":-74.006}},"jsonrpc":"2.0","id":4}
[2025-05-24T18:55:22.201629] 輸出 >>> {"jsonrpc":"2.0","id":4,"result":{"content":[{"type":"text","text":"\nToday:\nTemperature: 63°F\nWind: 12 to 16 mph W\nForecast: Isolated rain showers after noon. Partly sunny. High near 63, with temperatures falling to around 61 in the afternoon. West wind 12 to 16 mph. Chance of precipitation is 20%.\n\n---\n\nTonight:\nTemperature: 53°F\nWind: 10 to 15 mph W\nForecast: Isolated rain showers before 10pm. Partly cloudy, with a low around 53. West wind 10 to 15 mph. Chance of precipitation is 20%.\n\n---\n\nSunday:\nTemperature: 67°F\nWind: 10 to 14 mph W\nForecast: Mostly sunny, with a high near 67. West wind 10 to 14 mph.\n\n---\n\nSunday Night:\nTemperature: 55°F\nWind: 7 to 12 mph NW\nForecast: Partly cloudy, with a low around 55. Northwest wind 7 to 12 mph.\n\n---\n\nMemorial Day:\nTemperature: 69°F\nWind: 7 mph N\nForecast: Mostly sunny, with a high near 69. North wind around 7 mph.\n"}],"isError":false}}
mcp server 測試客戶端
使用 GPT 寫了一個用于測試 mcp server 的客戶端,它用于和 MCP weather 工具進行交互,將上述的工具握手和獲取天氣的流程自動執行一遍,將它命名為 test_weather.py
,代碼如下:
import subprocess
import json
import timedef send_json_and_read(proc, obj):"""發送一行 JSON 并讀取一行響應"""line = json.dumps(obj)print("👉 發送:", line)proc.stdin.write(line + "\n")proc.stdin.flush()while True:response = proc.stdout.readline()if response.strip():print("? 響應:", response.strip())return json.loads(response.strip())# 啟動 MCP server 子進程
proc = subprocess.Popen(["python", "weather.py"],stdin=subprocess.PIPE,stdout=subprocess.PIPE,stderr=subprocess.PIPE,text=True,
)# 1. initialize
send_json_and_read(proc, {"method": "initialize","params": {"protocolVersion": "2024-11-05","capabilities": {},"clientInfo": {"name": "cline","version": "1.0"}},"jsonrpc": "2.0","id": 0
})# 2. notifications/initialized (通知類,不需要響應)
print("👉 發送通知: notifications/initialized")
proc.stdin.write(json.dumps({"method": "notifications/initialized","jsonrpc": "2.0"
}) + "\n")
proc.stdin.flush()# 3. tools/list
send_json_and_read(proc, {"method": "tools/list","jsonrpc": "2.0","id": 1
})# 4. resources/list
send_json_and_read(proc, {"method": "resources/list","jsonrpc": "2.0","id": 2
})# 5. resources/templates/list
send_json_and_read(proc, {"method": "resources/templates/list","jsonrpc": "2.0","id": 3
})# 6. 調用 get_forecast(注意你原來寫的是 tools/call,其實 FastMCP 使用的是 tools/invoke)
# {"method":"tools/call","params":{"name":"get_forecast","arguments":{"latitude":40.7128,"longitude":-74.006}},"jsonrpc":"2.0","id":4}
send_json_and_read(proc, {"method": "tools/call","params": {"name": "get_forecast","arguments": {"latitude": 40.7128,"longitude": -74.0060}},"jsonrpc": "2.0","id": 4
})proc.terminate()
它的輸出內容如下:
這張圖展示了一個完整的 MCP 工具交互過程:客戶端通過 JSON-RPC 協議依次向 weather.py
發送 initialize
握手請求,獲取工具列表(tools/list
)、資源信息(resources/list
與 resources/templates/list
),然后調用 get_forecast
工具查詢紐約市天氣,并成功接收到格式化的預報結果,表明 MCP 天氣服務端已完整實現并正確響應所有標準請求。
- 客戶端發送
initialize
請求,協商協議版本并獲取服務端信息,服務端返回名稱為"weather"
、版本為"1.9.1"
的響應。 - 客戶端發送
notifications/initialized
通知,表示初始化完成,準備開始交互。 - 客戶端通過
tools/list
請求獲取工具列表,服務端返回包含get_alerts
和get_forecast
兩個工具,并提供每個工具的說明和參數結構。 - 客戶端請求
resources/list
和resources/templates/list
,用于發現可用資源和模板,服務端分別返回空數組,表示當前未注冊資源或模板。 - 客戶端使用
tools/call
方法調用get_forecast
工具,并傳入紐約市的經緯度參數(latitude: 40.7128,longitude: -74.006
)。 - 服務端成功響應工具調用,返回包含天氣預報文本的結構化結果,字段包括
content
(文本數組)和isError: false
,表明請求處理成功。
步驟六:模型調用工具示例流程
前面的內容給出了 Cline 如何和 工具進行溝通的。那下一個問題就是 Cline 是如何和模型交互的呢?不同的MCP Agent 使用不同的格式和模型進行溝通,Cline 是使用xml格式來交互的。
和前面的 中間日志代理工具 類似,我們可以在 Cline 和 模型 中間加一個 本地代理服務器,這個本地服務器就用來打日志,將 Cline 和 DeepSeek 交互的輸入和輸出打印出來。
中轉服務器 llm_logger.py
代碼
from fastapi import FastAPI, Request
from fastapi.responses import StreamingResponse
import httpx
import json
import uuid
import datetime
import osapp = FastAPI()
LOG_FILE = "/mnt/c/workspace/llm/mcp/weather/llm_io_log.txt"# 啟動時清空日志文件
if os.path.exists(LOG_FILE):with open(LOG_FILE, "w", encoding="utf-8") as f:f.write("")print(f"清空日志文件:{LOG_FILE}")def log_to_file(log_id, direction, content):"""將一條日志寫入文件"""timestamp = datetime.datetime.now().isoformat()with open(f"{LOG_FILE}", "a", encoding="utf-8") as f:f.write(direction + "\n" + content)f.write("\n")print(direction + "\n" + content)@app.post("/chat/completions")
async def proxy_request(request: Request):log_id = uuid.uuid4().hex # 每個會話唯一標識body_bytes = await request.body()body_str = body_bytes.decode('utf-8')log_to_file(log_id, "模型輸入:", body_str) # 記錄請求體body = await request.json()async def event_stream():collected_text = "" # 收集最終自然語言響應async with httpx.AsyncClient(timeout=None) as client:async with client.stream("POST","https://api.deepseek.com/chat/completions",json=body,headers={"Content-Type": "application/json","Accept": "text/event-stream","Authorization": request.headers.get("Authorization"),},) as response:async for line in response.aiter_lines():if line.startswith("data:"):try:data_json = json.loads(line[len("data:"):].strip())delta = data_json.get("choices", [{}])[0].get("delta", {})content_piece = delta.get("content")if content_piece:collected_text += content_pieceexcept json.JSONDecodeError:passyield f"{line}\n"# 所有內容收集完畢,寫入日志log_to_file(log_id, "模型輸出:", collected_text)return StreamingResponse(event_stream(), media_type="text/event-stream")if __name__ == "__main__":import uvicornuvicorn.run(app, host="0.0.0.0", port=8001)
啟動中轉服務器
使用下面的命令來啟動中轉服務器:uvicorn llm_logger:app --port 8001
配置中轉服務器
配置本地的中轉服務器的地址,以及 DeepSeek的 API KEY 和模型ID
llm_logger.py
實現了一個OpenAI API 兼容的代理服務器,用于中轉與 DeepSeek 等模型服務之間的對話請求,它接收 /chat/completions
請求 并使用 httpx.AsyncClient
將請求轉發給到 DeepSeek 接口https://api.deepseek.com/chat/completions
,同時保留原始 Authorization
頭,用以傳遞 API KEY。它可作為調試 MCP 流程中「模型調用工具前后的原始問答」記錄工具; 與 Cline 搭配使用,實現“模型調用 + 工具調用 + 日志追蹤”完整鏈路。
可直接運行該腳本,通過 uvicorn
啟動 FastAPI 服務監聽 8001 端口:
python your_file.py
# 或手動 uvicorn 啟動
uvicorn your_file:app --host 0.0.0.0 --port 8001
詢問天氣情況:
Cline 發起天氣詢問請求,同時攜帶的還有 “role”: “system” 的內容,它的里面有 weather 工具的信息,講述它的功能以及參數類型等等。
## weather (`python /mnt/c/workspace/llm/mcp/weather/mcp_logger.py uv --directory /mnt/c/workspace/llm/mcp/weather run weather.py`)\n\n### Available Tools\n- get_alerts: Get weather alerts for a US state.\n\n Args:\n state: Two-letter US state code (e.g. CA, NY)\n \n Input Schema:\n {\n \"type\": \"object\",\n \"properties\": {\n \"state\": {\n \"title\": \"State\",\n \"type\": \"string\"\n }\n },\n \"required\": [\n \"state\"\n ]\n }\n\n- get_forecast: Get weather forecast for a location.\n\n Args:\n latitude: Latitude of the location\n longitude: Longitude of the location\n \n Input Schema:\n {\n \"type\": \"object\",\n \"properties\": {\n \"latitude\": {\n \"title\": \"Latitude\",\n \"type\": \"number\"\n },\n \"longitude\": {\n \"title\": \"Longitude\",\n \"type\": \"number\"\n }\n },\n \"required\": [\n \"latitude\",\n \"longitude\"\n ]\n }\n\n====\n\n
DeepSeek 模型收到這個問題之后,從工具列表中找到了 weather,建議 Cline use_mcp_tool發起 weather 服務中 get_forecast 的調用,同時還給出了工具調用參數紐約的經緯度坐標
模型輸出:
<thinking>
1. 用戶詢問的是紐約明天的天氣
2. 系統信息顯示有一個已連接的MCP天氣服務器(weather)
3. 該服務器提供了獲取天氣預報的工具(get_forecast)
4. 需要先獲取紐約的經緯度坐標才能查詢天氣預報
5. 可以使用MCP天氣服務器的get_forecast工具來完成任務
</thinking><use_mcp_tool>
<server_name>weather</server_name>
<tool_name>get_forecast</tool_name>
<arguments>
{"latitude": 40.7128,"longitude": -74.0060
}
</arguments>
</use_mcp_tool>
Cline 收到回復之后,Cline發起 tool/call
工具調用并得到 紐約 的天氣信息
[2025-05-26T00:08:00.053419] 輸入 >>> {"method":"tools/call","params":{"name":"get_forecast","arguments":{"latitude":40.7128,"longitude":-74.006}},"jsonrpc":"2.0","id":7}
[2025-05-26T00:08:01.741230] 輸出 >>> {"jsonrpc":"2.0","id":7,"result":{"content":[{"type":"text","text":"\nThis Afternoon:\nTemperature: 68°F\nWind: 14 mph NW\nForecast: Isolated rain showers after 5pm. Partly sunny, with a high near 68. Northwest wind around 14 mph. Chance of precipitation is 20%.\n\n---\n\nTonight:\nTemperature: 55°F\nWind: 7 to 12 mph NW\nForecast: Isolated rain showers before 10pm. Partly cloudy, with a low around 55. Northwest wind 7 to 12 mph. Chance of precipitation is 20%.\n\n---\n\nMemorial Day:\nTemperature: 71°F\nWind: 7 mph N\nForecast: Mostly sunny, with a high near 71. North wind around 7 mph.\n\n---\n\nMonday Night:\nTemperature: 57°F\nWind: 6 mph SW\nForecast: Partly cloudy, with a low around 57. Southwest wind around 6 mph.\n\n---\n\nTuesday:\nTemperature: 70°F\nWind: 3 to 10 mph E\nForecast: Partly sunny. High near 70, with temperatures falling to around 68 in the afternoon. East wind 3 to 10 mph.\n"}],"isError":false}}
Cline 將從工具收到的天氣信息發送給 DeepSeek 模型
DeepSeek模型將收到的信息轉換成易讀的形式,返回給Cline,這里attempt_completion表明是最終結果,不需要繼續交互了。
總結:MCP 的本質與價值
MCP 作為工具調用協議,其核心目標是:
-
發現函數(工具):tools/list 中規定了所有函數名、輸入結構、用途說明
-
標準化調用方式:統一了調用請求/響應格式
-
模型工具解耦:并不關心模型如何組織 prompt,由 Agent(如 cline)決定
通過 VScode + Cline + DeepSeek + MCP,可以快速構建出一個結構清晰、交互統一、支持自然語言指令的工具調用系統。無論是天氣查詢、網頁抓取,還是其他插件式擴展,MCP 都為構建 AI 應用打開了一種新方式。如果你對 Agent 模型感興趣,可以參考我之前的文章:大模型系列18-AI Agents。