大模型系列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 啦
发送一句 “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。
相关文章:

大模型系列22-MCP
大模型系列22-MCP 玩转 MCP 协议:用 Cline DeepSeek 接入天气服务什么是 MCP?环境准备:VScode Cline DeepSeek**配置 DeepSeek 模型:****配置 MCP 工具****uvx是什么?****安装 uv(会自动有 uvx 命令&…...

【监控】Prometheus+Grafana 构建可视化监控
在云原生和微服务架构盛行的今天,监控系统已成为保障业务稳定性的核心基础设施。作为监控领域的标杆工具,Prometheus和Grafana凭借其高效的数据采集、灵活的可视化能力,成为运维和开发团队的“标配”。 一、Prometheus Prometheus诞生于2012…...
vscode里几种程序调试配置
标题调试python嵌入的c代码,例如 import torch from torch.utils.cpp_extension import loadtest_load load(nametest_load, sources[test.cpp],extra_cflags[-O0, -g],#extra_cflags[-O1],verboseTrue, ) a torch.tensor([1, 2, 3]) b torch.tensor([4, 5, 6]) result te…...

RAGFlow源码安装操作过程
RAGFlow是一款基于深度文档理解构建的开源 RAG(Retrieval-Augmented Generation)引擎,可作为Dify的外部知识库使用[1]。本文主要介绍RAGFlow前端和后端等源码安装操作过程。 一.后端安装 特别注意:python ">3.12,<3…...

Unity使用XCharts动态配置数据——折线图(LineChart)
XCharts官网地址:https://xcharts-team.github.io/ 本地上传资源:https://download.csdn.net/download/m0_64375864/90919669 效果图: 动态配置数据: public class Test3 : MonoBehaviour {public LineChart lineChart;public …...

【HITCSAPP 哈工大计算机系统期末大作业】 程序人生-Hello’s P2P
计算机系统 大作业 题 目 程序人生-Hello’s P2P 专 业 计算机与电子通信类 学 号 2023112915 班 级 23L0505 学 生 杨昕彦 指 导 教 师 刘宏伟 计算机科学…...

DAY9 热力图和箱线图的绘制
浙大疏锦行 学会了绘制两个图: 热力图:表示每个特征之间的影响,颜色越深数值越大表示这两个特征的关系越紧密 箱线图:表示每个特征的数据分布情况 箱体(Box): 箱体的上下边界分别表示第一四分位…...
如何查看 GitLab 内置的 PostgreSQL 版本?
GitLab 依赖 PostgreSQL,PostgreSQL 的升级会随着 GitLab 的版本升级而进行,本文分享查看 GitLab 内置 PostgreSQL 版本的方法。 GitLab 版本和 PostgreSQL 版本需要一一对应,默认情况下使用 Omnibus 方式安装的 GitLab 实例会自动升级 Postg…...
VR 技术与病毒分离鉴定:一场奇妙的邂逅
过去,病毒分离鉴定主要依靠传统实验技术,虽为病毒学发展奠定基础,但在现代病毒研究中有诸多局限。 沉浸式操作,告别风险担忧 VR 技术给病毒分离鉴定带来的最大变革是大幅提升实验安全性。借助 VR 设备,实验者身处高…...

解释一下NGINX的反向代理和正向代理的区别?
大家好,我是锋哥。今天分享关于【解释一下NGINX的反向代理和正向代理的区别?】面试题。希望对大家有帮助; 解释一下NGINX的反向代理和正向代理的区别? NGINX的反向代理和正向代理的区别主要体现在它们的功能和使用场景上。下面我会详细解释它们的定义…...

数学笔记一:标量、向量和矩阵基本概念辨析
一、标量 标量(Scalar) 是一种仅用数值大小(即 “量值”)就能完全描述的物理量或数学对象,它不具有方向属性。 例如在实数领域的正数、负数。 在物理学领域的多少斤、多少公斤、水温多少度、气温多少度都是标量。 …...

vue3获取两个日期之间的所有时间
1.获取两个日期之间所有年月日 如图所示: 代码如下: <template><div class"datePicker"><el-date-pickerv-model"value1"type"daterange"range-separator"至"start-placeholder"开始时间…...

Python 实现简易版的文件管理(结合网络编程)
目录 一、Python 代码实现1. 服务器端2. 客户端 二、结果展示1. 查看当前路径下的内容 ls2. 切换当前路径 cd3. 查看当前路径 pwd4. 显示根目录下的树状结构 tree5. 在当前路径下创建目录 mkdir6. 删除当前路径下的文件或目录 rm7. 复制文件 mv8. 移动文件 cp9. 用户从当前路径…...
元组可以比较大小吗?一次返回多个值?编程语言的元组?声明变量一定需要指定类型吗?
目录 元组可以比较大小吗? 一次返回多个值? 编程语言的元组 支持元组的语言 元组的基本特性 元组的初始化和使用 声明变量一定需要指定类型吗? var类型 元组可以比较大小吗? 不同编程语言对元组的定位稍有差异,是否可以比较大小随语言而定。 Swift支持…...

PXC集群
PXC集群 一、环境介绍二、PXC安装1、关闭默认mysql模块2、安装yum源3、准备pxc安装环境4、安装pxc5、启动mysql,并更改root密码 三、搭建PXC集群1、编辑/etc/my.cnf 配置文件(1)pxc1节点配置文件(2)pxc2节点配置文件&a…...

线程安全问题的成因
前言 大家晚上好呀~~ 今天学习了线程不安全问题的成因。线程安全问题是十分重要的知识点,我想把我所学的与大家分享一波,希望可以帮助到有需要的人,同时加深自己对于线程安全问题的理解。 分析过程如下 结语 今天心情还不错~ 要坚持持续…...

零基础远程连接课题组Linux服务器,安装anaconda,配置python环境(换源),在服务器上运行python代码【3/3 适合小白,步骤详细!!!】
远程连接服务器 请查阅之前的博客——零基础远程连接课题组Linux服务器,安装anaconda,配置python环境(换源),在服务器上运行python代码【1/3 适合小白,步骤详细!!!】&am…...
字节跳动BAGEL-7B-MoT模型开源:多模态AI技术的新范式与行业涟漪
在人工智能领域,技术开源与商业化落地的平衡始终是核心议题。2025年5月26日,字节跳动发布开源多模态AI模型BAGEL-7B-MoT,凭借其混合架构设计与跨模态处理能力,在图像生成、视觉理解等任务中展现出与GPT-4o等闭源模型抗衡的实力。这…...
Ubuntu静态IP配置信息查看命令
Ubuntu静态IP配置信息查看命令 1. 查看当前IP地址信息 (Address & Netmask) 方法1: 使用ip命令 (推荐) ip addr show # 或简写 ip a方法2: 使用ifconfig命令 ifconfig # 查看特定网卡 ifconfig eth0方法3: 只查看IP地址 hostname -I2. 查看网关信息 (Gateway) 查看默…...

unity实现wasd键控制汽车漫游
1.给汽车模型添加Box Collider和Rigidbody 2.创建脚本CarController并加载到汽车模型上 using UnityEngine; using UnityEngine.UI;public class CarController : MonoBehaviour...

Python优雅执行SSH命令:10种方法+虚拟环境深度实践
引言:为什么选择Python操作SSH? SSH作为网络安全的基石,广泛应用于远程管理、文件传输和自动化任务。Python凭借其丰富的生态(如paramiko、fabric)和简洁语法,成为编写SSH脚本的首选语言。本文将系统梳理通…...
Linux TCP与Socket与IO多路复用(Epoll)
目录 一、背景 二、交互流程 2.1 数据流动 2.2 对象之间的关系 三、TCP 3.1 为什么需要三次握手 3.2 三次握手流程 3.3 三次握手后的产物 3.4 TCB 四、Socket 4.1 Java Socket和C Socket 4.2 Socket的本质 4.3 Socket和TCB的关系 4.4 通过文件描述符调用Socket的…...
LINUX安装运行jeelowcode后端项目(命令行)
环境准备 运行环境:JDK1.8开发工具: Idea、Maven默认已启动中间件:(推荐使用宝塔)Mysql8.0、Redis、Minio第一步:下载JeelowCode项目并导入IDEA中 第二步:导入数据库文件到mysql中,…...

嵌入式高级工程师面试全解:从 malloc 到 kernel panic 的系统知识梳理
在嵌入式和操作系统方向的技术面试中,常常会涉及一系列关于内存管理、虚拟化、系统权限、调试工具、外设通信等方面的问题。本文将基于一次真实的高级嵌入式工程师岗位面试问题,整理并详解所有相关技术点,作为一份结构清晰、知识全面的学习资…...
机器学习第二十七讲:Kaggle → 参加机器学习界的奥林匹克
机器学习第二十七讲:Kaggle → 参加机器学习界的奥林匹克 资料取自《零基础学机器学习》。 查看总目录:学习大纲 关于DeepSeek本地部署指南可以看下我之前写的文章:DeepSeek R1本地与线上满血版部署:超详细手把手指南 Kaggle详解…...

C++(初阶)(二十)——封装实现set和map
二十,封装实现set和map 二十,封装实现set和map1,参数类型2,比较方式3,迭代器3.1,普通迭代器3.2,const迭代器3.3,set_map的迭代器实现 4,插入和查找5,特别的&a…...

【MySQL】06.内置函数
1. 聚合函数 -- 统计表中的人数 -- 使用 * 做统计,不受 NULL 影响 mysql> select count(*) 人数 from exam_result; -------- | 人数 | -------- | 5 | -------- 1 row in set (0.01 sec)-- 使用表达式做统计 mysql> select count(name) 人数 from ex…...

企业微信内部网页开发流程笔记
背景 基于ai实现企微侧边栏和工作台快速问答小助,需要h5开发,因为流程不清楚摸索半天,所以记录一下 一、网页授权登录 1. 配置步骤 1.1 设置可信域名 登录企业微信管理后台 进入"应用管理" > 选择开发的具体应用 > “网…...

智慧在线判题OJ系统项目总体,包含功能开发思路,内部中间件,已经部分知识点
目录 回顾一下xml文件怎么写 哪个地方使用了哪个技术 MyBatis-Plus-oj的表结构设计, 管理员登录功能 Swagger Apifox编辑 BCrypt 日志框架引入(slf4jlogback) nacos Swagger无法被所有微服务获取到修改的原因 身份认证三种方式: JWT(Json Web Json,一…...

【MySQL】2-MySQL索引P2-执行计划
欢迎来到啾啾的博客🐱。 记录学习点滴。分享工作思考和实用技巧,偶尔也分享一些杂谈💬。 有很多很多不足的地方,欢迎评论交流,感谢您的阅读和评论😄。 目录 EXPLAINexplain output 执行计划输出解释重点typ…...