MCP 终极指南完整笔记 —— 基础篇 + 进阶篇 + 番外篇
本文整理自 B 站 UP 主「马克的技术工作坊」的《MCP 终极指南》完整系列,并结合 UP 主开源的配套代码仓库 MarkTechStation/VideoCode 整理而成。
| 篇目 | 视频链接 | 时长 | 发布时间 |
|---|---|---|---|
| 基础篇 | BV1uronYREWR | 27 分钟 | 2025-04-15 |
| 进阶篇 | BV1Y854zmEg9 | 27 分钟 | 2025-04-19 |
| 番外篇 | BV1v9V5zSEHA | 44 分钟 | 2025-05-02 |
第一部分 · 基础篇:MCP 是什么、怎么用
1. MCP 简要介绍
MCP(Model Context Protocol,模型上下文协议)是 Anthropic 于 2024 年 11 月开源的标准协议,用于让大语言模型(LLM)与外部工具和数据源标准化通信。
- 痛点:M 个模型 × N 个工具 = M × N 套定制集成,重复造轮子
- 解法:MCP 成为"AI 应用的 USB-C 接口"——工具方实现一次 MCP Server,所有支持 MCP 的 Host 都能使用,问题降为 M + N

2. 核心概念
- MCP Host(宿主):运行 LLM、面向用户的应用,如 Cline、Claude Desktop、Cursor
- MCP Client(客户端):Host 内部的连接器,与 MCP Server 保持 1:1 有状态连接
- MCP Server(服务器):暴露能力的进程,提供三类原语:
- Tools:模型可主动调用的功能(查天气、执行 SQL、读写文件)
- Resources:只读上下文数据(文件内容、数据库记录)
- Prompts:可复用的提示词模板

3. 环境搭建与配置
- 安装 MCP Host:VS Code 扩展市场安装 Cline
- 配置 API Key:Cline 设置中选择 API Provider(Anthropic / OpenRouter / DeepSeek 等),填入 Key
- 配置 MCP Server:编辑
~/.cline/cline_mcp_settings.json:
{
"mcpServers": {
"weather": {
"command": "uvx",
"args": ["mcp-server-weather"],
"env": {}
}
}
}
4. 使用他人制作的 MCP Server
- Python 系:用
uvx临时下载运行(需先安装 uv) - Node 系:用
npx -y @modelcontextprotocol/server-xxx(需先装 Node.js)
5. MCP 交互流程
用户提问 → Host 把问题 + Tools 清单发给 LLM
→ LLM 返回 Tool Call(工具名 + 参数)
→ 用户确认 → Client 转发给 Server
→ Server 执行并返回结果 → 结果回传 LLM → 生成最终回答

要点:LLM 全程不直接接触 MCP Server;Tools 列表会占用上下文窗口,按需安装;用户确认是安全底线。
第二部分 · 进阶篇:自己动手写一个 MCP Server
时间轴:00:00 视频介绍 → 01:12 手写 MCP Server → 11:00 底层协议分析原理 → 12:48 实战协议剖析 → 20:54 直接用协议与 Server 交互 → 23:08 MCP 的真实含义与定位
1. 用 FastMCP 手写一个天气 Server
UP 主用官方 Python SDK 中的 FastMCP 高层封装,接美国国家气象局(NWS)的免费 API,写了一个天气 MCP Server。核心代码(来自配套仓库 MCP终极指南-进阶篇/weather/weather.py):
from typing import Any
import httpx
from mcp.server.fastmcp import FastMCP
# 初始化 FastMCP server
mcp = FastMCP("weather", log_level="ERROR")
NWS_API_BASE = "https://api.weather.gov"
USER_AGENT = "weather-app/1.0"
async def make_nws_request(url: str) -> dict[str, Any] | None:
"""请求 NWS API,带错误处理"""
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 None
@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(f) for f 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."""
points_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."
forecast_url = points_data["properties"]["forecast"]
forecast_data = await make_nws_request(forecast_url)
# ...格式化并返回未来 5 个时段的预报
...
if __name__ == "__main__":
mcp.run(transport="stdio") # 以 stdio 传输方式启动
关键要点:
@mcp.tool()装饰器:把一个普通 Python 函数变成 MCP Tool——函数签名即参数 Schema,docstring 即工具描述(会被发给 LLM 帮它决定何时调用)- 一个函数一个 Tool:
get_alerts查警报、get_forecast查预报,粒度清晰 mcp.run(transport="stdio"):本地 MCP Server 的标准启动方式——作为 Host 的子进程运行,通过标准输入/输出通信- 项目工程化:用
uv init创建项目、pyproject.toml管理依赖(mcp[cli]、httpx),uv run weather.py直接运行
配置到 Cline 时,把这个 Server 注册进去:
{
"mcpServers": {
"weather": {
"command": "uv",
"args": ["--directory", "/path/to/weather", "run", "weather.py"]
}
}
}
2. MCP 底层协议分析的原理与方法
MCP Server 用 stdio 传输时,Host 与 Server 之间的所有通信都走标准输入/输出。这就给抓包提供了一个绝妙的思路:
写一个"中间人"代理——Host 以为在跟真 Server 说话,实际数据先经过代理,代理原样转发的同时把每一行记到日志:
Cline (Host) ──stdio──▶ mcp_logger.py(中间人,只记录不修改) ──stdio──▶ weather.py (Server)
mcp_logger.py 的核心逻辑(来自配套仓库):
# 用 subprocess 启动真正的 MCP Server,用管道接管它的 stdin/stdout
process = subprocess.Popen(
target_command,
stdin=subprocess.PIPE, stdout=subprocess.PIPE,
stderr=subprocess.PIPE, bufsize=0
)
# 两个线程分别转发并记录两个方向的数据
def forward_and_log_stdin(proxy_stdin, target_stdin, log_file):
while True:
line_bytes = proxy_stdin.readline() # 读 Host 发来的数据
if not line_bytes: break
log_file.write(f"输入: {line_str}") # 记录
target_stdin.write(line_bytes) # 原样转发给 Server
def forward_and_log_stdout(target_stdout, proxy_stdout, log_file):
while True:
line_bytes = target_stdout.readline() # 读 Server 的返回
if not line_bytes: break
log_file.write(f"输出: {line_str}") # 记录
proxy_stdout.write(line_bytes) # 原样转发给 Host
Cline 配置里把 command 换成这个 logger,真正的命令放进 args:
{
"mcpServers": {
"weather": {
"command": "uv",
"args": ["run", "mcp_logger.py", "--", "uv", "run", "weather.py"]
}
}
}
这样一次完整的交互后,mcp_io.log 里就躺着 Host 与 Server 的全部对话记录。

3. 实战解析:协议的完整剖析过程
分析日志可以发现,Host 与 Server 的通信是 JSON-RPC 2.0 格式,每行一条 JSON 消息。完整流程分四步:

第 1 步 · 初始化握手(initialize)
Host → Server:
{
"jsonrpc": "2.0", "id": 1, "method": "initialize",
"params": {
"protocolVersion": "2024-11-05",
"capabilities": { "roots": { "listChanged": true } },
"clientInfo": { "name": "Cline", "version": "..." }
}
}
Server 回复自己支持的协议版本和能力:
{
"jsonrpc": "2.0", "id": 1, "result": {
"protocolVersion": "2024-11-05",
"capabilities": { "tools": {} },
"serverInfo": { "name": "weather", "version": "1.0.0" }
}
}
随后 Host 再发一条 notifications/initialized 通知,握手完成。
第 2 步 · 工具发现(tools/list)
Host → Server:{"jsonrpc": "2.0", "id": 2, "method": "tools/list"}
Server 返回所有 Tool 的清单——每个工具的名称、描述、参数 Schema。这正是 FastMCP 从函数签名和 docstring 自动生成的:
{
"jsonrpc": "2.0", "id": 2, "result": {
"tools": [
{
"name": "get_forecast",
"description": "Get weather forecast for a location.",
"inputSchema": {
"type": "object",
"properties": {
"latitude": { "type": "number" },
"longitude": { "type": "number" }
},
"required": ["latitude", "longitude"]
}
}
]
}
}
第 3 步 · 工具调用(tools/call)
用户提问后,LLM 决定调用工具,Host → Server:
{
"jsonrpc": "2.0", "id": 3, "method": "tools/call",
"params": {
"name": "get_forecast",
"arguments": { "latitude": 37.7749, "longitude": -122.4194 }
}
}
Server 执行函数,返回结果:
{
"jsonrpc": "2.0", "id": 3, "result": {
"content": [
{ "type": "text", "text": "Today: Temperature: 18°C, Wind: ..." }
]
}
}
第 4 步 · 结果回传
Host 拿到工具结果,塞进对话历史发回给 LLM,LLM 生成最终自然语言回答。
4. 使用底层协议直接与 MCP Server 交互
既然协议只是"按行发送 JSON-RPC",那完全可以不借助任何 Host,自己在终端里手动扮演 Client:启动 Server 进程,逐行粘贴 initialize → tools/list → tools/call 的 JSON 消息,观察返回。这一步的演示印证了 MCP 的本质:它就是一套约定好的消息格式,没有任何魔法。
5. 超越表象:MCP 的真实含义与定位
剥开实现细节,MCP 的定位可以概括为:
- MCP 是协议,不是框架——它只规定"消息长什么样"(JSON-RPC + 一组约定方法名),不管你怎么实现
- 解决的是"发现"与"调用"的标准化——工具如何声明自己(tools/list)、如何被调用(tools/call)、结果如何回传,全部有统一格式
- 类比 USB-C:手机(Host)、数据线(Client)、外设(Server)各司其职,任何符合标准的设备都能即插即用
- 为什么用 stdio:本地场景下最简单可靠——Server 是 Host 的子进程,管道通信无需网络端口、无认证负担
第三部分 · 番外篇:抓包分析 Cline 与模型的交互协议(Agent 实现原理)
本篇回答一个更深层的问题:MCP Server 之上,Host(Cline)与 LLM 之间是怎么通信的?Agent 到底是怎么实现的?
1. 抓包方法
Cline 与 LLM 之间的通信走 HTTPS,UP 主的方法是搭一个本地代理服务器,把 Cline 的 API Base URL 指向本地代理,代理转发请求到真实 API 并记录全程(配套仓库 MCP终极指南-番外篇/llm_logger.py):
python -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
python llm_logger.py # 启动本地记录代理
然后在 Cline 里把 API 地址改指向 http://localhost:xxxx,就能看到 Cline 发给模型的每一个请求、模型的每一次返回。
2. 发现一:Cline 的请求里藏着一个巨大的系统提示词
抓包看到的第一件事:Cline 发给模型的请求体中,system 字段是一篇超长的系统提示词(数千 token),内容大致包括:
- 你是 Cline,一个资深软件工程师,运行在 VS Code 里
- 你可以通过工具一步一步完成任务:read_file、write_to_file、search_files、execute_command、ask_followup_question、attempt_completion……
- 每个工具的调用格式(XML 风格标签)和参数说明
- 行为准则:一次只用一个工具、必须等工具结果返回再继续、不要假设结果……
关键洞察:所谓"Cline 会用工具",不是模型原生会,而是系统提示词教出来的。
3. 发现二:LLM 并不真正"调用"函数
这是全片最核心的认知纠偏。很多人以为 LLM 原生支持 function calling,真相是:
- Cline 把用户问题 + 系统提示词(含工具说明)+ 对话历史打包发给模型
- 模型返回的仍然只是文本——一段"我想使用 read_file 工具,参数是 Hello.cs"的格式化文本(XML 标签包裹)
- Cline 应用层用字符串解析识别出工具名和参数,在本地执行(读文件)
- 把工具执行结果作为新的对话内容再次发给模型
- 循环往复,直到模型输出的不再是工具调用,而是
attempt_completion(任务完成的最终回答)
也就是说:
模型输出文本 → 应用层解析文本 → 执行 → 结果拼回对话 → 再发给模型……这就是 Agent 循环的全部真相。
(现代 API 的 "tool use" 能力本质上是把这个模式标准化了——模型经过专门训练,能稳定输出结构化的工具调用格式,但"解析-执行-回传"的循环依然由应用层完成。)
4. 发现三:这就是 ReAct 模式
Cline 系统提示词遵循的模式,学术界叫 ReAct(Reason + Act,2022 年提出)。它的提示词模板长这样(来自配套仓库 ReAct系统提示词.md):
你需要解决一个任务。为此,你需要将任务分解为多个步骤。对于每个步骤,
首先使用 `Thought:` 思考要做什么,然后使用可用工具之一决定一个 `Action:`。
接着,你将根据你的行动从环境/工具中收到一个 `Observation:`。
持续这个思考和行动的过程,直到你有足够的信息来提供 `FinalAnswer:`。
示例:
Question: 埃菲尔铁塔有多高?
Thought: 我需要找到埃菲尔铁塔的高度。可以使用搜索工具。
Action: get_height("埃菲尔铁塔")
Observation: 埃菲尔铁塔的高度约为330米(包含天线)。
Thought: 搜索结果显示了高度。我已经得到答案了。
FinalAnswer: 埃菲尔铁塔的高度约为330米。
请严格遵守:
- 输出Action后立即停止生成
- 等待返回真实的Observation
- 擅自生成Observation将导致错误
一次完整的 ReAct 循环:
Thought(推理)→ Action(行动)→ [应用层执行工具] → Observation(观察)→ Thought → … → FinalAnswer

MCP 在这个图景里的位置:ReAct 循环中的 "Action 执行" 环节,如果调的是外部工具,就经由 MCP Client → MCP Server 完成。两层协议各管一段:
Cline ⇄ LLM :自然语言 + 系统提示词 + 工具调用文本(ReAct 循环)
Cline ⇄ MCP Server :JSON-RPC 标准化协议(initialize / tools/list / tools/call)
5. Agent 实现原理总结
把三篇视频的知识串成一句话:
Agent = 一个 while 循环 + 一个解析器 + 一堆工具。
LLM 负责"想"(输出下一步行动),应用层负责"做"(解析、执行、回传),MCP 负责"连接"(标准化地发现和调用外部工具)。
真正需要智能的只有 LLM;循环、解析、调度、协议——其余一切都是工程。
全系列总结
| 层次 | 问题 | 答案 |
|---|---|---|
| 概念层 | MCP 是什么 | LLM 与外部工具通信的开放标准,"AI 的 USB-C" |
| 使用层 | 怎么用别人的 Server | uvx(Python)/ npx(Node)配到 Host 里 |
| 开发层 | 怎么写自己的 Server | FastMCP:装饰器 + 函数签名 = Tool |
| 协议层 | 底层是什么 | JSON-RPC 2.0 over stdio,initialize → tools/list → tools/call |
| 原理层 | Host 和 LLM 怎么交互 | 系统提示词教模型输出工具调用文本,应用层解析执行(ReAct 循环) |
| 本质层 | Agent 是什么 | while 循环 + 解析器 + 工具集;智能只在 LLM,其余是工程 |
配套代码仓库:https://github.com/MarkTechStation/VideoCode
(含 MCP终极指南-进阶篇/weather/ 完整可运行的天气 Server 与协议 logger、MCP终极指南-番外篇/ 的 Cline 中英文系统提示词与 ReAct 模板)
附录:评论区与弹幕精华补充
本附录整理自三集视频的评论区与 1200+ 条弹幕,都是观众在学习过程中提出的真问题、踩过的真坑和达成的关键共识,与正文互补阅读效果最佳。
一、高频概念辨析:Function Calling 与 MCP 到底什么关系?
这是基础篇弹幕区争论最激烈的问题(02:00 前后刷屏),达成的共识值得记下来:
- Function Calling(FC)是"能力":它让大模型拥有了"决定使用外部工具"的可能性——模型先有这个意识,才有后面的一切。
- MCP 是"协议/标准":它规范的是工具如何注册、如何被发现、如何被调用——解决的是"更好、更统一地用工具"的工程问题。
- 一句话:FC 让模型"会想用工具",MCP 让工具"有个统一的插口"。弹幕金句:"fc 是能力,mcp 是能力协议"。
另一个翻译陷阱:弹幕指出 "Server" 在程序员语境里是"服务器/服务端",不要被"服务者"这类直译带偏。
二、工具到底是谁调用的?(弹幕反复确认的点)
番外篇 17 分钟处观众密集提问"工具接口是大模型调用的还是 agent 主机调用的?",弹幕共识非常一致:
- 大模型从不真正调用任何东西——它只输出文本:"我要调用某工具,参数是 XXX"。
- 实际执行者是 Agent(MCP Host):解析模型输出 → 调用工具 → 把结果回传给模型。
- 弹幕金句:"大模型只是个大脑,它只会说话,干啥都只能通过说话的方式指使别人干。"
- 这也解释了正文番外篇的结论:function calling 的本质是提示词约定 + 字符串解析,不是模型内建的神秘机制。
三、Token 消耗:MCP 为什么越用越烧钱?(弹幕"token 爆炸"名场面)
番外篇后半段(21:00–34:00)弹幕哀嚎一片"token 爆炸""为了查个天气花了好几百块",背后是三个重要机制:
- 大模型是无状态的:每次请求都要把系统提示词 + 全部历史对话重新发一遍。"记忆"全靠客户端把上下文撑着,每次提问 prompt 都会再发一遍。
- MCP Server 越多,system prompt 越膨胀:每个 Server 的 tools 描述(名称、参数、说明)都会塞进上下文。有观众提问"加载越多 MCP 是不是越耗 token"——答案是肯定的。Cline 长期霸榜 OpenRouter token 消耗榜第一就是这个原因。
- 省钱机制——prefix cache(前缀缓存):重复的 prompt 前缀(系统提示词等固定内容)命中输入缓存后费用大幅降低,这也是为什么各家 API 都强调 prompt caching。
实用建议(来自弹幕):问的问题和当前上下文无关时,果断新开会话,避免无用历史跟着每次请求重发。
四、静态知识 vs 动态信息("纽约经纬度哪来的"之问)
多集弹幕反复出现"模型怎么知道纽约的经纬度"的疑问,答案很清晰:
- 经纬度属于静态知识,早就在模型的训练数据里了,模型直接"想"出来即可,不用调工具。
- 天气属于动态信息,训练数据里不可能有"明天"的天气,才需要 MCP 工具去查。
- 弹幕总结很精辟:"只有动态变量部分是需要 MCP 的方式去调用,静态的都可以依赖模型本身训练的知识。"
五、SSE vs WebSocket(番外篇 04:27–05:30 弹幕热议)
视频里 Cline 与 LLM 之间使用 SSE(Server-Sent Events)流式返回,弹幕补充了关键对比:
| SSE | WebSocket | |
|---|---|---|
| 方向 | 单向(服务端 → 客户端推送) | 双向 |
| 底层 | 基于 HTTP | 独立协议(TCP 升级) |
| 典型场景 | LLM 流式返回、行情推送 | 聊天室、实时游戏 |
- 补充细节:LLM API 实际是"HTTP 请求上行 + SSE 下行"的组合——只有模型到客户端是 SSE,客户端发起请求用的是普通 HTTP。
- 关于流式返回的顺序问题,弹幕也给出答案:SSE 基于 TCP,底层协议栈保证按序到达,应用层无需关心 sequence。
- 另有观众指出:流式返回里大量 JSON 字段(
<thinking>等结构)中只有content部分计入输出 token,其余是传输的数据结构。
六、实操避坑清单(弹幕里的血泪经验)
- Windows 激活虚拟环境:不用
source,命令是.venv\Scripts\activate(注意是反斜杠)。 - FastMCP 版本兼容坑:
log_level参数在部分版本会导致 Server 启动失败(有观众反馈报 619 错误);新版本 mcp 库的导入路径也变了(from mcp.server.mcpserver import MCPServer),运行报错先查库版本。 - uv 其实可以省掉
source这步:uv add会自动检查当前虚拟环境。 - Cline 早已进化:视频里的 XML 格式工具调用是当时的实现,现在 Cline 已升级为原生 function calling 模式——概念不变,格式与时俱进。
- 抓包时把 log 文件在编辑器里打开实时刷新,观察更方便(UP 主演示用的 Vim 正则替换格式化 JSON)。
七、观众总结思维导图
基础篇评论区有观众用 Freeform 绘制了一张「Agent = LLM + Tools」全景总结图,与本文观点高度一致,附此供参考:

图中要点:LLM 分析问题并决定用什么工具(结构化格式输出调用请求,如 xml/json);MCP Client(Claude Code、Cursor、Cline、Minima 等)将 LLM 生成的请求发给 MCP Server 或执行本地函数,并把结果回传;底层对应 ReAct 的 Thought → Act → Observation 循环。

浙公网安备 33010602011771号