前言
之前我们了解到 LLM 并不能单独靠自己和物理世界通讯,就像他并不知道今天是几月几号,也不知道明天北京会不会下雨。所以,有了 Function calling 和 Tool 的概念来打通 LLM 和物理世界的 “最后一公里” 隔阂。
现在有 这样一个🌰:已知在应用 A 已经适配了一个 toolA,那么应用 B、C、D也需要使用 toolA。但是可以直接复用吗?显然是不能的。因为不清楚几个应用各自的框架结构,比如使用的 LLM 模型、Agent框架等等。
同样,从商业的角度看。TRAE 公开的工具也不能直接在 Claude 使用。导致工具开发者需要为不同平台做多次适配,抑制了工具生态的发展。如此,MCP 便应运而生。
定义
MCP(Model Context Protoccol),模型上下文协议。是由 Anthropic 于2024年底提出并开源的一种开放协议。
- 核心目标:为 LLM 和物理世界(外部数据源、工具) 构建一套标准化的双向通信机制。
就像 Anthropic 自己说的,MPC 就好比 AI 应用程序之间的 “USB-C端口”。
意义
就像文章开头的🌰,在 MCP 时代 LLM 不直接与物理世界接触,也不是各自为政地编写对应 tool。而是所有的开发者在编写 tool 时必须遵循 MCP 协议,以 MCP 为中间层来连接 LLM 和物理世界。如此,toolA 会以 MCP 的形式提供给应用A、B、C、D,所有应用也不用特地做针对性的适配。
MCP 通过标准化,将工具集成的边际成本降至近乎为零:
-
对开发者:实现了“一次编写,随处运行”的工具开发体验。
-
对应用方:可以像安装插件一样,轻松扩展AI应用的能力边界。
-
对生态:催生出一个繁荣的、可互操作的工具市场,加速整个领域的创新。
组件
核心
MCP 基于经典的客户端-服务器(Client-Server)模型,主要由以下五个核心组件构成。
-
MCP主机(Host) :基于LLM的应用程序本身(如Claude Desktop、智能IDE),作为中枢管理用户交互和任务调度。提供执行AI任务,运行Client的环境
-
MCP客户端(Client) :嵌入在主机中的通信代理,与服务器建立一对一连接,负责协议层的消息收发,管理主机与多个server的通信。
-
MCP服务器(Server) :轻量级程序,将特定的数据源或工具(如本地文件、数据库、Slack API)的能力,通过MCP协议暴露出来。提供外部系统的访问权限、具备工具、提示、资源三个核心能力
-
本地数据源:主机本地的文件、数据库等。
-
远程服务:可通过网络访问的外部API或云服务。
Server
MCP服务器提供三个核心核心能力:
-
工具:对应 “执行” —— 改变外部状态或获取动态信息(如运行代码、查询数据库)。和我们之前 Function Calling的 tools 是一样的。
-
资源:对应 “感知” —— 获取静态或半静态的参考信息(如读取文件、查阅文档、图像、数据库等)。其实本质就是一种轻量级的 RAG。
-
提示:对应 “认知框架” —— 为特定任务优化的 PromptTemplate。预设思考的起点和模式(如设定角色、注入思维链模板)。
一个完整的任务往往是三个能力的组合。例如,LLM 可以:
-
先通过资源读取
bug_report.md。 -
然后使用特定PromptTemplate:
bug_triager来初始化分析框架。 -
最后在分析过程中,调用工具:
search_logs去查询相关错误日志。这种 “资源(背景知识)→ 提示(思考模式)→ 工具(执行动作)” 的链条,是构建强大AI工作流的基础范式。
和 Function Calling 关系
在 MCP 时代,LLM 和物理世界沟通的整个流程为:
理清这个流程后,是不是就不难发现二者之间的一些微妙关系:
-
Function Calling 是 MCP 的基础,MCP 是 LLM 和 Function Calling 发展到一定阶段的必然产物。
-
Function Calling 是模型层能力,而 MCP 是让这种能力得以高效、标准化落地的应用层协议。
-
Function Calling 是技术产物,解决 LLM 和物理世界的 “最后一公里” 隔阂问题;MCP 是生态产物,他并没有催生哪种新的基础技术,只是在 Function Calling 广泛应用之后来解决工具生态混乱而低效问题的一种规范协议。
Coding
工欲善其事
FastMCP
FastMCP是建立在 MCP 协议之上的一个高性能、生产就绪的Python框架。
- 核心:用极简的装饰器语法,帮你自动处理了协议序列化、类型验证、错误处理等底层细节。
我们简单地理解 FastMCP 就是可以快速地帮我们在本地启一个 server 服务。
StdioServerParameters
StdioServerParameters是一个用于配置单个服务器进程的底层参数对象。用于精确描述如何启动一个本地MCP服务器子进程。它不负责通信,只负责“描述”。这个参数对象通常需要传递给 mcp.client.stdio.stdio_client()来建立实际的通信流
- 核心参数:
command: 启动命令(如"python","node")args: 命令行参数列表(如["server.py"])env: 传递给子进程的环境变量字典
calculator_params = StdioServerParameters(
# 启动服务器的命令
command="python",
# 服务器脚本路径(需绝对路径或相对工作目录)
args=[os.path.join(os.path.dirname(os.path.dirname(__file__)), "server", "bmi_server.py")],
# 可选:设置服务器的工作目录(默认当前目录)
env=None
)
MultiServerMCPClient
MultiServerMCPClient 是一个用于管理多个服务器连接的高层客户端。他是LangChain MCP适配器(langchain_mcp_adapters)中的核心客户端类。专为生产级Agent设计,能同时管理多个不同类型(stdio/HTTP)的MCP服务器。
-
核心能力:
- 统一配置:通过一个字典配置多个服务器
- 工具自动发现:调用
get_tools()自动获取所有服务器工具并转换为LangChain Tool对象 - 传输抽象:支持
stdio、http、streamable_http等多种传输方式 - 会话管理:提供默认无状态和可选有状态会话
client = MultiServerMCPClient({
"math": {
"transport": "stdio",
"command": "python",
"args": ["math_server.py"]
},
"weather": {
"transport": "http",
"url": "http://localhost:8000/mcp"
}
})
Demo背景
我们写一个计算 BMI 的 tool:calculate_bmi(),让 LLM 可以通过 MCP 来调用calculate_bmi()。
Demo功能很简单,就像是一个“HelloMCP”,主要是通过这个🌰来理解 MCP 原理和流程,以及最简单的基础使用方法。
Server
我们使用 FastMCP 来构建服务。
# 创建一个MCP服务器
mcp = FastMCP("计算器演示")
# 启动 MCP 服务,使用标准输入输出方式
mcp.run(transport='stdio')
Tool
@mcp.tool():使用装饰器来定义工具(和 LAngchain 类似)。
# 使用装饰器定义一个工具(Tool)
@mcp.tool()
# 原有工具:计算BMI
def calculate_bmi(weight_kg: float, height_m: float) -> dict:
"""
计算人体BMI
"""
bmi = weight_kg / (height_m ** 2)
category = "偏瘦" if bmi < 18.5 else "正常" if bmi < 24 else "超重" if bmi < 28 else "肥胖"
return {"bmi": round(bmi, 2), "category": category}
Resurce
@mcp.resource("server://info"):MCP 资源装饰器"server://info": 资源唯一地址(URI)- URI可以随便自定义,🌰:
- 比如:
user://profileconfig://settingsdata://weather
- 比如:
- URI可以随便自定义,🌰:
# 使用装饰器定义一个资源(Resource):提供静态或动态数据
@mcp.resource("server://info")
def get_server_info() -> str:
"""
提供当前服务器的基本信息。
"""
return f"服务器状态正常。当前时间:{datetime.datetime.now()}"
Prompt
@mcp.prompt:MCP 的提示词模板装饰器
# Prompt:结合服务器时间和BMI工具的提示模板
@mcp.prompt
def generate_health_prompt(name: str) -> str:
"""
生成一个个性化的健康建议提示,结合当前服务器时间和BMI工具用法。
Args:
name: 用户姓名(用于个性化称呼)。
"""
# 调用资源获取当前时间(注意:Prompt函数中可以直接调用其他资源/工具)
server_info = get_server_info()
current_time = server_info.split(":")[1] # 提取时间部分
# 返回Markdown格式的提示
return f"""# 健康咨询提示
你好,{name}!当前服务器时间是{current_time},以下是为你准备的健康建议:
1. 请用`calculate_bmi`工具计算你的BMI(参数:体重kg、身高m);
2. 根据BMI结果,我会给你对应的健康建议(偏瘦/正常/超重/肥胖);
3. 提示:BMI正常范围18.5-23.9,超过28需注意饮食和运动。
示例:计算体重70kg、身高1.75m的BMI → `calculate_bmi(weight_kg=70, height_m=1.75)`
"""
Client
MCP Server启动配置
# 1. 配置MCP Server启动参数
bmi_params = StdioServerParameters(
# 启动服务器的命令
command="python",
# 服务器脚本路径(需绝对路径或相对工作目录)
args=["./bmi_server.py"],
# 可选:设置服务器的工作目录(默认当前目录)
env=None
)
stdio_client & ClientSession
# 2. 用stdio_client创建封装流(关键修复:不再手动创建子进程)
async with stdio_client(bmi_params) as (read_stream, write_stream):
# 3. 用封装流初始化ClientSession
async with ClientSession(read_stream, write_stream) as session:
await session.initialize() # 必须:完成协议握手
print("✅ 会话初始化完成,成功连接到MCP服务器!")
tool
# 4. 调用工具(正常执行)
bmi = await session.call_tool(
"calculate_bmi",
{"weight_kg": 70.0, "height_m": 1.75}
)
print(f"\n📊 BMI结果:{bmi}") # 输出:{"bmi": 22.86, "category": "正常"}
resource
# 5. 获取资源
info = await session.read_resource("server://info")
print(f"\n⏰ 服务器信息:{info}")
prompt
# 6. 调用Prompt
prompt = await session.get_prompt(
"health_prompt",
{"name": "张三"}
)
print(f"\n💡 健康提示:\n{prompt}")
Running
uv 管理子模块
我们早就已经开始使用 uv 管理依赖。那么实际工程中我们难免有多模块开发的需求,而且多个模块可能会独立打包。这个时候,我们总不能每个模块都依赖一个全量的 dependencies。显然这样效率太低了。
我也是今天才发现 uv 本身是支持子模块增量依赖的,而且他本身会继承根目录的公共依赖。以今天的 mcp_base 为例,来看 uv 如何管理子模块。
- 根目录 pyproject.toml 新增子模块申明
[tool.uv.workspace]
members = [ "mcp_base", ]
[project]
name = "helloagent"
version = "0.1.0"
description = "helloagent"
readme = "README.md"
requires-python = ">=3.11,<3.12"
dependencies = [
"aiohappyeyeballs==2.6.1",
...
]
# ✅ 核心:只做工作区,不做可安装包(关键!)
[tool.uv]
# 禁用 editable 安装,不触发 setuptools 构建
editable = false
# ✅ 启用 Workspace,声明所有子项目目录(members)
[tool.uv.workspace]
members = [
"mcp_base",
"mcp_stock_analysis_demo",
]
- 子模块目录新建 pyproject.toml,添加增量依赖
name = "mcp_base" # 一定要和根目录下的模块名一致
[project]
name = "mcp_base" # 一定要和根目录下的模块名一致
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.11,<3.12"
dependencies = [
"mcp>=0.1.0",
"asyncio>=3.4.3",
"requests>=2.31.0",
"beautifulsoup4>=4.12.0",
"duckduckgo_search>=3.9.0",
]
3. 全量安装
uv sync
- 增量安装
uv sync --package mcp_base
或者
cd mcp_base
uv sync
思考 🤔🤔🤔
🤔1 MCP-Client 和 LLM
- Q:MCP中的client角色llm也能充当,那么llm发请求和client发请求有什么区别?
- A:其实上边理解了 MCP 和 Function Calling关系之后就好解释了。
- LLM:意图生成阶段。强项是语义理解和任务规划。它不应该被底层通信协议、错误重试、连接池管理等工程细节所污染。
- 理解意图,并生成一个结构化的“调用指令”
- MCP:协议执行阶段。强项是稳定、可靠、高效地执行标准化操作。
- 接收LLM输出的“调用指令”,将其转换为一个完全合规的MCP请求,管理连接
- 这里如果完全让 LLM 做,LLM 需要耦合网络请求、MCP协议解析、以及序列化等功能。显然这在设计模式上违背了 “单一职责”/"关注点分离" 的原则
- LLM:意图生成阶段。强项是语义理解和任务规划。它不应该被底层通信协议、错误重试、连接池管理等工程细节所污染。
🤔2 Server-Prompts 和 Langchain-Hub
- Q:MCP的提示与Langchain中的 hub 提示词模板好像有异曲同工之处。
- A:确实目的一致,都是为了提升 Prompt 的可复用性、可管理性和协作性。但是在设计和原理上有所不同。
- 技术原理
- MCP:核心是 “服务发现与调用” 思维,MCP Server向Client动态暴露的能力。
- Langchain-Hub:核心是 “包管理” 思维,本质上是一个中心化的代码库与版本控制系统,就像git。Prompt 可上传可下载使用。
- 设计哲学
- MCP:动态发现
- Langchain-Hub:固化与复用。
- 应用场景
- MCP
- 提示词需要根据环境动态变化。
- 构建通用的、可插拔的AI助手平台。
- 将提示词能力作为服务对外提供。
- 提示词和相关工具强相关
- Langchain-Hub
- 提示词稳定,作为项目核心资产。
- 需要严格的版本控制和离线开发。
- 团队内部共享和迭代Prompt模板。
- MCP
- 技术原理