当前位置: 首页 > article >正文

大模型系列22-MCP

大模型系列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 啦
    配置 DeepSeek API

发送一句 “Hi”,测试模型是否接入成功:模型回答了一些内容。
DeepSeek 接入测试

配置 MCP 工具

我们有很多现成的 MCP 工具可以用,如 mcp.somcpmarket.comsmithery.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 环境。

工具作用
uvpip + 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 所示。

这里面我们忽略了一些细节

  1. 模型怎么知道我们有 fetch 这个工具?模型它自己不知道,其实是我们将上面的抓取问题给 Cline 之后,Cline 会将问题以及 Cline 当前安装的 MCP 工具命令和参数类型信息全部发送给模型,模型才知道我们有 fetch 这个工具。
  2. 是模型调用的 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与工具之间的输入输出交互内容,主要功能如下:

  1. 拦截标准输入(stdin)传给 MCP Server 的数据(例如 Cline 发来的 JSON-RPC 请求),并记录为 "输入 >>> ...";

  2. 拦截 MCP Server 标准输出(stdout)返回给 Cline 的响应结果,并记录为 "输出 >>> ...";

  3. 所有的交互式日志都会追加写入 mcp_io_log.txt 文件,带有时间戳;

  4. 保持原有输入输出链路不中断:数据一边转发,一边被记录。

中间日志代理工具用法示例:

python mcp_logger.py uv --directory /mnt/c/workspace/llm/mcp/weather run weather.py

这样一来,无需改动原有 MCP 工具 weather.py 服务代码,就能实时查看ClineMCP的通信过程,方便调试和分析。这个工具 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/listresources/templates/list),然后调用 get_forecast 工具查询纽约市天气,并成功接收到格式化的预报结果,表明 MCP 天气服务端已完整实现并正确响应所有标准请求。

  • 客户端发送 initialize 请求,协商协议版本并获取服务端信息,服务端返回名称为 "weather"、版本为 "1.9.1" 的响应。
  • 客户端发送 notifications/initialized 通知,表示初始化完成,准备开始交互。
  • 客户端通过 tools/list 请求获取工具列表,服务端返回包含 get_alertsget_forecast 两个工具,并提供每个工具的说明和参数结构。
  • 客户端请求 resources/listresources/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。

http://www.lryc.cn/news/2387718.html

相关文章:

  • 【监控】Prometheus+Grafana 构建可视化监控
  • vscode里几种程序调试配置
  • RAGFlow源码安装操作过程
  • Unity使用XCharts动态配置数据——折线图(LineChart)
  • 【HITCSAPP 哈工大计算机系统期末大作业】 程序人生-Hello’s P2P
  • DAY9 热力图和箱线图的绘制
  • 如何查看 GitLab 内置的 PostgreSQL 版本?
  • VR 技术与病毒分离鉴定:一场奇妙的邂逅​
  • 解释一下NGINX的反向代理和正向代理的区别?
  • 数学笔记一:标量、向量和矩阵基本概念辨析
  • vue3获取两个日期之间的所有时间
  • Python 实现简易版的文件管理(结合网络编程)
  • 元组可以比较大小吗?一次返回多个值?编程语言的元组?声明变量一定需要指定类型吗?
  • PXC集群
  • 线程安全问题的成因
  • 零基础远程连接课题组Linux服务器,安装anaconda,配置python环境(换源),在服务器上运行python代码【3/3 适合小白,步骤详细!!!】
  • 字节跳动BAGEL-7B-MoT模型开源:多模态AI技术的新范式与行业涟漪
  • Ubuntu静态IP配置信息查看命令
  • unity实现wasd键控制汽车漫游
  • Python优雅执行SSH命令:10种方法+虚拟环境深度实践
  • Linux TCP与Socket与IO多路复用(Epoll)
  • LINUX安装运行jeelowcode后端项目(命令行)
  • 嵌入式高级工程师面试全解:从 malloc 到 kernel panic 的系统知识梳理
  • 机器学习第二十七讲:Kaggle → 参加机器学习界的奥林匹克
  • C++(初阶)(二十)——封装实现set和map
  • 【MySQL】06.内置函数
  • 企业微信内部网页开发流程笔记
  • 智慧在线判题OJ系统项目总体,包含功能开发思路,内部中间件,已经部分知识点
  • 【MySQL】2-MySQL索引P2-执行计划
  • 云电脑显卡性能终极对决:ToDesk云电脑/顺网云/海马云,谁才是4K游戏之王?