Agent来了0x0c:MCP 初识

2 阅读11分钟

前言

之前我们了解到 LLM 并不能单独靠自己和物理世界通讯,就像他并不知道今天是几月几号,也不知道明天北京会不会下雨。所以,有了 Function callingTool 的概念来打通 LLM 和物理世界的 “最后一公里” 隔阂。

现在有 这样一个🌰:已知在应用 A 已经适配了一个 toolA,那么应用 B、C、D也需要使用 toolA。但是可以直接复用吗?显然是不能的。因为不清楚几个应用各自的框架结构,比如使用的 LLM 模型、Agent框架等等。

同样,从商业的角度看。TRAE 公开的工具也不能直接在 Claude 使用。导致工具开发者需要为不同平台做多次适配,抑制了工具生态的发展。如此,MCP 便应运而生。

定义

MCP(Model Context Protoccol),模型上下文协议。是由 Anthropic 于2024年底提出并开源的一种开放协议

  • 核心目标:为 LLM 和物理世界(外部数据源、工具) 构建一套标准化的双向通信机制。

就像 Anthropic 自己说的,MPC 就好比 AI 应用程序之间的 “USB-C端口”。

image.png

意义

就像文章开头的🌰,在 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

  • 然后使用特定PromptTemplatebug_triager来初始化分析框架。

  • 最后在分析过程中,调用工具search_logs 去查询相关错误日志。

    这种 “资源(背景知识)→ 提示(思考模式)→ 工具(执行动作)” 的链条,是构建强大AI工作流的基础范式。

和 Function Calling 关系

在 MCP 时代,LLM 和物理世界沟通的整个流程为:

image.png

理清这个流程后,是不是就不难发现二者之间的一些微妙关系:

  • 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对象
    • 传输抽象:支持 stdiohttpstreamable_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://profile
          • config://settings
          • data://weather
# 使用装饰器定义一个资源(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

image.png

uv 管理子模块

我们早就已经开始使用 uv 管理依赖。那么实际工程中我们难免有多模块开发的需求,而且多个模块可能会独立打包。这个时候,我们总不能每个模块都依赖一个全量的 dependencies。显然这样效率太低了。

我也是今天才发现 uv 本身是支持子模块增量依赖的,而且他本身会继承根目录的公共依赖。以今天的 mcp_base 为例,来看 uv 如何管理子模块。

  1. 根目录 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",
]

  1. 子模块目录新建 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
  1. 增量安装
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协议解析、以及序列化等功能。显然这在设计模式上违背了 “单一职责”/"关注点分离" 的原则

🤔2 Server-Prompts 和 Langchain-Hub

  • Q:MCP的提示与Langchain中的 hub 提示词模板好像有异曲同工之处。
  • A:确实目的一致,都是为了提升 Prompt 的可复用性、可管理性和协作性。但是在设计和原理上有所不同。
    • 技术原理
      • MCP:核心是 “服务发现与调用” 思维,MCP Server向Client动态暴露的能力。
      • Langchain-Hub:核心是 “包管理” 思维,本质上是一个中心化的代码库与版本控制系统,就像git。Prompt 可上传可下载使用。
    • 设计哲学
      • MCP:动态发现
      • Langchain-Hub:固化与复用
    • 应用场景
      • MCP
        1. 提示词需要根据环境动态变化。
        2. 构建通用的、可插拔的AI助手平台。
        3. 将提示词能力作为服务对外提供。
        4. 提示词和相关工具强相关
      • Langchain-Hub
        1. 提示词稳定,作为项目核心资产。
        2. 需要严格的版本控制和离线开发。
        3. 团队内部共享和迭代Prompt模板。

源码

github