# 20 - MCP 模型上下文协议 --- **本章课程目标:** - 理解 **MCP(Model Context Protocol,模型上下文协议)** 是什么、解决什么痛点,以及它与 [Tool](17-Tools工具调用.md)、[RAG](19-RAG检索增强生成.md)、[Agent](21-Agent智能体.md) 的定位区别。 - 掌握 MCP 的**主机 / 客户端 / 服务器**架构、核心能力、常见传输方式,以及 `mcp.json`、FastMCP、LangChain MCP 适配器在项目中的作用。 - 跑通并理解本章全部案例:**极简教学版服务端、FastMCP 服务端、天气 MCP 服务、同进程客户端、基于 `mcp.json` + LangChain Agent 的 MCP 客户端**,为后续学习 [第 21 章 Agent 智能体](21-Agent智能体.md) 打基础。 **学习建议:** MCP 先别当成一堆协议名背,它的核心是把外部能力接入模型应用时变得更标准。第一遍只抓三组关系:Tools / Resources / Prompts 是暴露的能力,Host / Client / Server 是通信角色,stdio / Streamable HTTP 是传输方式。学完 [第 21 章 Agent 智能体](21-Agent智能体.md) 后再回来看 `Agent + MCP`,会更容易理解它为什么重要。 **官方文档与资源**:详见 [工具导航与参考资料索引 - 工具调用、MCP与智能体](工具导航与参考资料索引.md#工具调用、MCP与智能体)。 --- ## 1、为什么需要 MCP ### 1.1 真实项目里的接入痛点 很多同学第一次接触 MCP 时,会把它理解成“让大模型联网”或“让大模型能调用工具”。这只说对了一部分。 更核心的问题其实不是“模型能不能用工具”,而是“不同 AI 应用怎样用统一方式接入外部工具和上下文”。 在没有 MCP 时,真实项目里常见的痛点主要有三类: - **每个 AI 应用都要重复接一遍外部系统** 例如同样是接 GitHub、Slack、数据库、文件系统,Cursor 要写一套,Claude Desktop 要写一套,自研 Agent 平台又要写一套。 - **每个框架和宿主各有自己的接法** 即使大家都支持 Tool / Function Calling,真正落地时仍然要处理:服务如何发现、参数 schema 怎么描述、鉴权怎么传、进程怎么启动、结果怎么返回。 - **工具很难复用成“生态能力”** 没有统一协议时,一个工具即使写得很好,也往往只能服务于某个特定应用,迁移和复用成本很高。 ![无 MCP 时:各 AI 应用需分别对接 GitHub、Slack、数据库等,重复开发与适配成本高](images/20/20-1-1-1.jpeg) 所以,MCP 出现的背景,不是“以前没人会写工具”,而是: **大家都在写工具,但缺少一套跨应用、跨框架、跨宿主都能复用的统一连接标准。** ### 1.2 MCP 的核心问题 MCP 解决的问题可以概括成一句话:**让“外部工具、资源、提示词模板”等能力,能够按统一协议被不同 AI 应用发现和使用。** 举个更贴近项目的例子。假设你在 IDE 里有一个 AI 编程助手,希望它能: - 读取本地代码仓库 - 查 GitHub Issue - 调内部文档系统 - 查询云服务配置 - 调用数据库辅助排查问题 如果没有 MCP,你往往要为这个 AI 助手单独实现很多连接器。如果这些能力都已经按 MCP 标准暴露,那么这个 AI 助手只需要支持 MCP,就可以统一接入这些服务。 这时,MCP 的价值就很明显了:**一次暴露,多处复用**;**统一 schema,降低适配成本**;**更容易形成工具生态**。 ### 1.3 直观类比:AI 世界的统一插口 很多同学都记得钢铁侠的助手「贾维斯」:它不是只会聊天,而是能连接战甲、实验室、摄像头、数据库、控制系统等各种外部能力。 现实中的 AI 应用也是一样。一个真正有用的 AI 助手,往往不仅要“说”,还要能:查本地文件、查数据库、搜 GitHub Issue、调天气接口、发消息到 Slack / 微信 / 邮件、调用内部业务系统。 ![若缺乏统一协议,每接一类系统都要单独实现连接与鉴权,维护成本陡增](images/20/20-1-3-1.jpeg) 如果每接一个系统都单独写一套连接逻辑,成本会非常高。MCP 的思路,就是给 AI 应用提供一个更像“**统一插口**”的东西。 把它理解成一层统一插口会更直观:它像 **AI 世界的 USB-C**,也像 **大模型版的 OpenFeign / gRPC 协议层**,本质上承担的是 **AI 应用和外部能力之间的通用适配层**。 把这层直觉和上一节连起来理解会更顺:**MCP 统一的不是模型本身,而是 AI 应用发现、理解、调用和复用外部能力的方式。** --- ## 2、MCP 简介 ### 2.1 定义 MCP(Model Context Protocol,模型上下文协议)是一套**开放的标准协议**,用于规范 **AI 应用 / Agent / IDE / 聊天客户端** 如何与 **外部工具、资源和上下文提供方** 交互。 官方的核心表述可以概括成: - MCP 标准化了应用程序向 LLM 提供上下文的方式 - 它让 AI 应用可以用统一方式连接不同的数据源和工具 入门阶段可先把握这一点: **MCP 的关键词是“标准协议、统一接入、跨宿主复用”。** ### 2.2 和 Tool、RAG、Agent 有什么区别 | 概念 | 解决什么问题 | 典型关注点 | | --------------------------- | ------------------------------ | ---------------------------------------- | | **Tool / Function Calling** | 模型如何调用一个具体工具 | 工具 schema、参数、调用结果 | | **RAG** | 模型如何拿到外部知识上下文 | 文档加载、切块、检索、上下文拼接 | | **MCP** | 外部能力如何被标准化暴露与接入 | Host / Client / Server、协议、传输、发现 | | **Agent** | 谁来规划、决策、调用这些能力 | 推理、编排、记忆、执行闭环 | 一句话速记: > **术语约定:** 本章里表格写作 **Tool / Function Calling**,是为了兼容不同文档语境;和 [第 17 章](17-Tools工具调用.md) 一样,你可以把它们先看成同一层“模型如何表达工具调用意图”的机制。 - **Tool** 解决“能不能调用” - **RAG** 解决“能不能拿到知识” - **MCP** 解决“怎么统一接入” - **Agent** 解决“谁来决定何时调用” --- ## 3、MCP 能做什么 ### 3.1 统一接入与抽象 MCP 最直观的价值,就是把原本分散的外部能力,用统一方式暴露给 AI 应用。 ![分散接入:各工具、服务、客户端各自约定协议与 schema,重复适配多](images/20/20-3-1-1.jpeg) > **说明**:「分」——各应用、各数据源各自对接,重复开发、难以复用。 ![MCP 统一层:标准化暴露后,由 Host 侧按同一套方式发现与调用能力](images/20/20-3-1-2.jpeg) > **说明**:「合」——通过 MCP 等统一协议,一次开发、多端复用。 所以,MCP 不是在替代 Tool,而是在 Tool 之上再向上抽象了一层“协议层”。 ### 3.2 MCP 服务器通常能暴露什么 根据官方文档,MCP 服务器最核心的三类能力是: | 类型 | 作用 | 控制方式 | 对应案例 | | ------------- | --------------------------------------------------- | --------------------------- | ---------------------------------------------- | | **Tools** | 可执行动作,例如查天气、查数据库、发请求 | **模型可触发** | `McpServer.py`、`McpServerWeatherByFastMCP.py` | | **Resources** | 可读取内容,例如文件、配置、数据库 schema、API 响应 | **应用 / 宿主决定如何使用** | `McpServerByFastMCP.py` 的 `@mcp.resource()` | | **Prompts** | 可复用的提示词模板 / 工作流模板 | **用户显式选择更常见** | `McpServerByFastMCP.py` 的 `@mcp.prompt()` | 官方对三者的控制方式有明确区分: - **Tools 是 model-controlled** - **Resources 是 application-driven** - **Prompts 更偏 user-controlled** 也就是说: - Tool 更适合让模型自动决定何时调用 - Resource 更适合由宿主决定如何纳入上下文 - Prompt 更适合由用户显式触发某种模板化工作流 这也是为什么不能把 MCP 简单理解成“就是工具协议”。**工具是 MCP 的一部分,但不是全部。** ### 3.3 容易忽略的另外几类能力 除了 Tools / Resources / Prompts,官方协议里还有一些更进阶的客户端 / 会话能力,例如: - **Sampling**:服务器通过客户端向**宿主侧的 LLM** 请求一次生成(把“算力在哪一侧”也纳入协议协作) - **Elicitation**:服务器通过客户端向用户请求补充信息 - **Logging**:服务器向客户端发送结构化日志 - **Progress / Notifications**:长任务过程中的进度和通知 这些内容在当前仓库案例里没有作为主线展开,但对理解 MCP 很重要,因为它说明: **MCP 不只是“列出工具然后调用工具”,它还定义了更完整的人机协作和上下文交换机制。** 不过对本章来说,先重点掌握: - Tool 如何暴露 - 服务器和客户端怎么连 - LangChain / Agent 如何拿到 MCP 工具 就已经足够了。 ### 3.4 在实际项目的常见用途 在真实项目里,MCP 最常见的落地方向通常有三种: 1. **给现成 AI 应用接能力** 例如让 Cursor、Claude Desktop、VS Code、ChatGPT 类客户端接入文件系统、代码库、浏览器、内部系统。 2. **给自研 Agent 平台做统一工具接入层** 这样 Agent 平台就不用为 GitHub、数据库、知识库、Slack、云平台各写一套不同协议。 3. **让企业内部能力变成可复用的 AI 接口层** 例如把“查工单”“查订单”“查配置”“发通知”这些能力封装成 MCP 服务,供多个 AI 应用共享。 **安全与信任边界(落地必知):** MCP Server 往往能以较高权限访问本机文件、内网 API 或密钥;生产环境应控制**来源可信**(仅安装审计过的服务)、**最小权限**与**网络隔离**,并记录调用审计。这与“能接什么”同样重要。 --- ## 4、怎么用 MCP ### 4.1 直接使用现成的 MCP 服务 如果你的目标不是“学习怎么实现协议”,而是“先把能力接进来”,最直接的方式通常是: - 找到现成的 MCP Server - 在宿主应用里配置连接方式 - 让客户端自动发现其 Tools / Resources / Prompts 当前官方已经有 Registry 方向的能力和生态,公共 MCP 服务器的发现也越来越规范化。这意味着: **很多能力无需一开始就自己写服务端,先学会怎么接、怎么配、怎么调试也很有必要。** ![通过 Registry 或目录浏览、发现已发布的 MCP 服务与能力(示意)](images/20/20-4-1-1.jpeg) ### 4.2 本地自建 MCP 服务端 如果你要接的是:本地文件系统、内部数据库、企业私有 API、自己的业务系统。 那通常就要自己写 MCP Server。这也是本章案例的重点:我们会通过本地天气服务、FastMCP 示例和 `mcp.json` 客户端配置,理解一个 MCP 服务是如何被暴露、被发现、再被 Agent 使用的。 #### 4.2.1 什么是 FastMCP 这里很适合顺手把 **FastMCP** 这个词讲清楚,因为后面的案例文件名里会频繁出现它。 **FastMCP 是 MCP 官方 Python 生态里用来快速编写 MCP Server 的高层封装。**它帮你把很多底层样板工作收起来,让你可以更接近“写 Python 函数”的方式去暴露 MCP 能力。 先把它和 MCP 的关系看成下面这组对应: - **MCP** 是协议标准,解决“AI 应用怎么统一接入外部能力” - **FastMCP** 是 Python 里的服务端开发工具,解决“我怎么更方便地把能力按 MCP 标准暴露出去” 也就是说:**MCP 是规则**,**FastMCP 是实现这些规则的一种工具**。这就像:HTTP 是协议,FastAPI / Flask 是帮助你实现 HTTP 服务的框架。 放到这一章里也是一样:MCP 决定 Host、Client、Server 之间怎么协作,FastMCP 帮你更轻松地写出一个 MCP Server。 #### 4.2.2 本章为什么讲 FastMCP 在真实项目里,如果你准备自己写一个 MCP 服务端,通常有两条常见路线: 1. **自己直接按 SDK / 协议细节去实现** 2. **借助 FastMCP 这种更高层的封装来实现** 第二条路线通常更友好,因为它能把注意力放回到“我要暴露什么能力”上,而不是一开始就陷进大量底层细节。 这也是为什么本章案例会分成两类: - `McpServer.py` 这是教学版极简实现,帮助你理解“服务端注册工具”这个最小概念。 - `McpServerByFastMCP.py`、`McpServerWeatherByFastMCP.py` 这是更贴近真实 Python MCP 开发体验的写法,帮助你理解 Tool / Resource / Prompt 怎么按官方 SDK 风格暴露出去。 因此这一章里,**FastMCP 不是主角,MCP 协议本身才是主角;但 FastMCP 很适合作为 Python 侧的入门实现工具。** ### 4.3 在 LangChain / Agent 里使用 MCP 这一点也和当前项目主线非常贴近。 LangChain 官方已经提供了对 MCP 的适配支持。常见路线是: 1. 用 `MultiServerMCPClient` 连接一台或多台 MCP 服务器 2. 通过 `get_tools()` 取回 MCP 工具 3. 把这些工具交给 `create_tool_calling_agent` 或 `create_agent` 4. 让 Agent 在对话中实际调用它们(Agent 的创建与执行细节见 [第 21 章 Agent 智能体](21-Agent智能体.md)) 这也说明了 MCP 和 LangChain 的关系: - **MCP** 负责“标准化接入” - **LangChain / Agent** 负责“把接进来的能力真正用起来” --- ## 5、MCP 架构知识 ### 5.1 主机、客户端、服务器定义 MCP 采用典型的 **Host - Client - Server** 架构。 ![MCP 架构:Host(用户应用)内的 Client 与远端或子进程中的 Server 通信,Server 再访问本地/远程资源](images/20/20-5-1-1.jpeg) | 角色 | 含义 | | ---------------------------- | ---------------------------------------------------------------- | | **MCP Host(MCP 主机)** | 用户真正交互的应用,例如 IDE、桌面客户端、聊天应用、自研 AI 平台 | | **MCP Client(MCP 客户端)** | Host 内部负责和某个 MCP Server 建立协议连接的组件 | | **MCP Server(MCP 服务器)** | 对外暴露 Tools / Resources / Prompts 等能力的服务 | | **本地 / 远程资源** | 服务器可访问的文件、数据库、API、内部系统等 | 官方文档里需要分清一个点: - **Host 是你在用的应用** - **Client 是 Host 内部的协议连接组件** 这也是为什么“一个 Host 可以连多台 Server”,但“一个具体 Client 通常对应一条到某台 Server 的直接连接”。 ![多 Server 拓扑:同一 Host 可挂多个 Client,各 Client 分别维护到一台 Server 的会话](images/20/20-5-1-2.jpeg) ### 5.2 MCP 协议层面大致怎么工作 MCP 不只是“发 HTTP 请求”这么简单,它在协议层有自己的一套约定。 学习本章时,先理解下面这条主线即可: 1. **Host / Client 发起连接** 2. **Client 和 Server 做初始化** 3. **双方声明各自支持的 capabilities** 4. **客户端发现服务器提供的 tools / resources / prompts** 5. **按需调用或读取** 6. **结果返回给 Host,再由模型 / UI 使用** MCP 的底层消息格式基于 **JSON-RPC 2.0**(请求 / 响应 / 通知)。这也是为什么你会在很多资料里看到: - request / response / notification - capabilities negotiation - initialize MCP 更像是:**“AI 应用与外部能力之间的协议层 + 能力发现层 + 调用层”**而不只是某个单纯的 SDK。 #### 5.2.1 用 5 个动作理解一次完整 MCP 调用 如果把 MCP 放回一轮真实问答里,可以把它拆成下面 5 个动作: 1. **握手与能力发现(Handshake & Discovery)** Host 启动后,会根据配置连接 MCP Server,并完成初始化。此时客户端会知道:这台服务器提供了哪些 Tools、Resources、Prompts,以及它支持哪些 capabilities。 2. **用户提问与上下文注入(Context Injection)** 用户提出问题后,Host 会把“用户问题 + 已发现的工具说明 / 资源信息 / 提示词信息”一并提供给模型或应用逻辑。 3. **模型或应用做决策(Reasoning / Decision)** 模型决定是否需要调用某个 Tool,或者应用决定是否读取某个 Resource、选用某个 Prompt。 4. **路由与执行(Routing & Execution)** Host / Client 按协议把请求发给 MCP Server。Server 在自己的进程或远端服务中执行真正的逻辑,例如查天气、读文件、查数据库。 5. **结果回传与继续生成(Result Feedback)** Server 把结果返回给 Client,Client 再把结果交回 Host,由 Host 继续让模型生成最终回答,或者直接展示给用户。 可以把这个过程和第 17 章 Tool 调用对比着理解: - **Tool 调用** 更像“模型知道怎么调一个函数” - **MCP** 更像“这个函数来自哪里、怎么发现、怎么连接、怎么按协议调” 这也是为什么在 Agent 场景里,MCP 常常出现在 Tool 之前一层。 ### 5.3 两类常见传输:STDIO 与 HTTP 系列 这一节先把新旧叫法分清楚。 根据 **MCP 官方传输规范**(文档以 [Transports](https://modelcontextprotocol.io/specification/2025-11-25/basic/transports) 等页面为准,版本会迭代),当前主线标准传输是: 1. **stdio** 2. **Streamable HTTP** 官方还明确说明: - **Streamable HTTP** 取代了 **2024-11-05 版本中的 HTTP+SSE transport** - 新的 HTTP 传输里,服务器**仍然可以使用 SSE 作为流式返回机制** 也就是说,今天更准确的理解应该是: - **STDIO**:本地子进程通信 - **Streamable HTTP**:独立服务进程,通过 HTTP 通信,必要时可配合 SSE 流式返回 而你在很多旧资料、旧案例、适配器配置里看到的 `sse`,通常属于: - 旧的 HTTP+SSE transport 叫法 - 或兼容写法 - 或具体库层面对历史接口的保留 ![传输方式对照:stdio(子进程管道)与 Streamable HTTP(独立 HTTP 服务,可含 SSE 流式)及历史 SSE 兼容语境](images/20/20-5-3-1.jpeg) 结合仓库现有案例,可以这样看: | 传输方式 | 更适合什么场景 | 对应案例 | | --------------------------- | ---------------------------------- | ------------------------------------------ | | **STDIO** | 本地、轻量、由客户端拉起服务端进程 | `McpServerByFastMCP.py` | | **SSE / HTTP 兼容教学写法** | 理解历史资料、理解远程服务形态 | `McpServerWeatherByFastMCP.py`、`mcp.json` | | **当前规范重点** | 优先理解为 `Streamable HTTP` | 新项目应以官方规范为准 | 读仓库案例时记住两点: - **本章案例保留 `sse` 写法,是为了兼容仓库现有代码** - **当前规范重点应理解为 `stdio + Streamable HTTP`** #### 5.3.1 传输之外,还要注意安全边界 MCP 真正落地时,传输方式只是第一层,安全边界同样重要。结合官方安全实践,先记住下面几条: - **写操作要有人确认**:删除、外呼、支付、批量修改这类 Tool,不要默认让模型静默执行。 - **HTTP 服务要做鉴权和来源校验**:至少要考虑 token、会话身份、Origin / 来源校验,而不是“能连上就算接入成功”。 - **本地服务尽量收口暴露范围**:能只监听本机就不要默认对公网开放,避免把调试用 MCP Server 直接变成外网入口。 - **日志与业务数据要分级处理**:进度、错误、调试日志很有用,但不要把敏感配置、私有数据原样暴露给模型或不可信客户端。 ### 5.4 FastMCP 的基本写法与常用 API 仓库里的 MCP 服务端案例,主要围绕 FastMCP / 官方 Python SDK 这条路线展开。看案例前,先把这几个 API 的职责记住: #### 5.4.1 创建服务实例 ```python from mcp.server.fastmcp import FastMCP mcp = FastMCP("Demo") ``` 这一步是在创建一个 MCP Server 实例。 #### 5.4.2 注册 Tool ```python @mcp.tool() def add(a: int, b: int) -> int: return a + b ``` 这表示把一个普通 Python 函数暴露成 MCP Tool。 #### 5.4.3 注册 Resource ```python @mcp.resource("greeting://default") def get_greeting() -> str: return "Hello from static resource!" ``` 这表示服务器对外暴露一个可读资源,客户端可以按 URI 读取。 #### 5.4.4 注册 Prompt ```python @mcp.prompt() def greet_user(name: str, style: str = "friendly") -> str: return f"为{name}生成问候语" ``` 这表示服务器对外暴露一个可复用提示词模板。 #### 5.4.5 启动服务 ```python mcp.run(transport="stdio") ``` 或者: ```python mcp.run(transport="streamable-http") ``` 在当前仓库案例里,还保留了: ```python mcp.run(transport="sse", host="127.0.0.1", port=8000) ``` 这类写法用于保留仓库现有案例。阅读时把它放在“**历史兼容 / 教学保留**”的语境里即可。 #### 5.4.6 从底层 SDK 视角看客户端 `McpClientAgent.py` 用的是更高层的 `MultiServerMCPClient`,它把很多底层细节都封装掉了。 但从学习角度,知道底层客户端大概在做什么,会更有助于你理解 MCP。 以官方 SDK 思路来看,一个 MCP 客户端的典型动作通常是: 1. 建立传输连接 2. 创建 `ClientSession` 3. 调用 `initialize()` 4. `list_tools()` / `list_resources()` / `list_prompts()` 5. `call_tool()` / `read_resource()` / `get_prompt()` 如果是 **STDIO**,大致会是这样的顺序: ```python import asyncio from mcp import ClientSession, StdioServerParameters from mcp.client.stdio import stdio_client async def main(): server_params = StdioServerParameters( command="python", args=["mcp_server_stdio.py"], ) async with stdio_client(server_params) as (read, write): async with ClientSession(read, write) as session: await session.initialize() tools = await session.list_tools() result = await session.call_tool("add", {"a": 1, "b": 2}) print(tools) print(result) asyncio.run(main()) ``` 如果是 **Streamable HTTP**,核心差异主要在“连接方式”变成了远程 URL: ```python import asyncio from mcp import ClientSession from mcp.client.streamable_http import streamable_http_client async def main(): async with streamable_http_client("http://127.0.0.1:8000/mcp") as (read, write, _): async with ClientSession(read, write) as session: await session.initialize() tools = await session.list_tools() print(tools) asyncio.run(main()) ``` 你不需要一上来就背这些底层 API,但知道这层动作很有价值,因为它能帮助你理解:为什么要先 `initialize()`;为什么客户端能“列出”工具、资源和提示词;为什么 `MultiServerMCPClient` 本质上是在帮你封装这些底层过程。 ### 5.5 完整调用过程理解 把协议层想象成一条完整链路,会更容易理解: 1. 用户在 Host 中发起请求 2. Host 内部某个 MCP Client 与对应 Server 建立会话 3. Client 发现 Server 暴露的能力 4. 模型或应用决定是否调用某个 Tool / 读取某个 Resource / 获取某个 Prompt 5. Server 执行或返回结果 6. Host 把结果展示给用户,或继续交给模型推理 如果这个流程再落到 LangChain Agent 里,就会变成: 1. `MultiServerMCPClient` 连接 MCP 服务 2. `get_tools()` 获取工具 3. Agent 拿到工具列表 4. 用户提问 5. Agent 选择工具 6. 工具返回结果 7. 模型基于结果继续生成最终回答 --- ## 6、案例实战:本地 MCP 天气服务与客户端 ### 6.1 本章案例在项目中的位置 核心文件如下: | 文件 | 作用 | 你应该怎样理解它 | | ------------------------------ | ---------------------- | ------------------------------------------------------- | | `McpServer.py` | 极简教学版服务端 | **概念演示版**,帮助理解“工具如何注册和暴露” | | `McpServerByFastMCP.py` | FastMCP 正式写法示例 | 展示 `tool / resource / prompt + stdio` | | `McpServerWeatherByFastMCP.py` | 天气服务端 | 展示天气 Tool 和 HTTP/SSE 兼容写法 | | `McpClient.py` | 简化版客户端 | **同进程教学版**,不是严格意义上的协议网络客户端 | | `mcp.json` | 客户端连接配置 | 声明要连接哪些 MCP 服务、怎么连接 | | `McpClientAgent.py` | LangChain + MCP 客户端 | 更贴近真实项目,读取 `mcp.json` 后把 MCP 工具交给 Agent | ### 6.2 服务端案例区分理解 #### 6.2.1 极简教学版:McpServer.py 【案例源码】`案例与源码-2-LangChain框架/11-mcp/McpServer.py` [McpServer.py](案例与源码-2-LangChain框架/11-mcp/McpServer.py ":include :type=code") 这个文件最重要的价值,不是“严格协议完整实现”,而是**帮助你先看懂 MCP 服务端到底在干什么**。 它做的核心事情只有两件: 1. 维护一个 `_tools` 容器 2. 用 `@mcp.tool()` 把 `get_weather` 注册进去 所以它更像一个“**MCP 思想演示版**”。 你应该把它理解成: - 帮你先理解“服务器暴露工具”这件事 - 帮你理解客户端为什么能发现工具 - 帮你理解 MCP 和普通 `@tool` 的关系 但也要明确:**它不是一个严格意义上完整、标准、可独立对外服务的 MCP 服务器实现。** #### 6.2.2 标准写法入门版:McpServerByFastMCP.py 【案例源码】`案例与源码-2-LangChain框架/11-mcp/McpServerByFastMCP.py` [McpServerByFastMCP.py](案例与源码-2-LangChain框架/11-mcp/McpServerByFastMCP.py ":include :type=code") 这个文件更接近“官方 Python SDK / FastMCP 的正常使用方式”,它同时演示了: - `@mcp.tool()` - `@mcp.resource()` - `@mcp.prompt()` - `mcp.run(transport="stdio")` 它很适合用来回答一个关键问题:**MCP 服务器不只是暴露工具,它还可以暴露资源和提示词模板。** 这也是本章里最适合拿来建立“Tools / Resources / Prompts 三分法”直觉的案例。 #### 6.2.3 天气服务端:McpServerWeatherByFastMCP.py 【案例源码】`案例与源码-2-LangChain框架/11-mcp/McpServerWeatherByFastMCP.py` [McpServerWeatherByFastMCP.py](案例与源码-2-LangChain框架/11-mcp/McpServerWeatherByFastMCP.py ":include :type=code") 这个文件主要展示两件事: 1. 如何把天气查询封装成 MCP Tool 2. 如何把服务端作为“独立服务”运行起来 这个文件仍使用仓库中的 `transport="sse"` 写法。阅读时把它当作“独立服务形态”的教学案例即可;新项目的传输方式,仍优先对照前面 5.3 节里的 `stdio / Streamable HTTP`。 ### 6.3 mcp.json 简介 【配置文件】`案例与源码-2-LangChain框架/11-mcp/mcp.json` [mcp.json](案例与源码-2-LangChain框架/11-mcp/mcp.json ":include :type=code") 这一节我特别想帮你纠正一个常见误区: **`mcp.json` 不是 MCP 协议本身,也不是“唯一契约”。** 它更准确的定位是:**某些 MCP Host / Client / 适配器常用的客户端连接配置文件。** 也就是说,它解决的是: - 要连接哪几台服务器 - 每台服务器用什么 transport - URL / command / args 是什么 而不是: - MCP 协议本体如何定义 - Tool schema 的完整定义如何写 这里的 `mcp.json` 主要读三类信息: - `weather` - 走 `sse` - 指向本地天气服务 - `fetch` - 走 `stdio` - 通过命令启动一个本地 MCP Server 这也很好地体现了 MCP 的一个现实特点:**同一个客户端完全可以同时连接多台 MCP Server,而且每台服务器可以用不同传输方式。** ### 6.4 客户端案例怎么区分理解 #### 6.4.1 同进程教学版客户端:McpClient.py 【案例源码】`案例与源码-2-LangChain框架/11-mcp/McpClient.py` [McpClient.py](案例与源码-2-LangChain框架/11-mcp/McpClient.py ":include :type=code") 这个文件容易让人误会,这里需要讲清楚:它的重点是演示: - 服务端如何暴露工具 - 客户端如何发现工具 - 工具如何被调用 但它的实现方式是: - `from McpServer import mcp` - 直接读 `mcp._tools` - 同进程内直接调用函数 所以它更像:**MCP 思路演示版客户端**,而不是:**真正按协议通过网络或子进程传输连接到独立服务端的客户端**。也正因为如此,它很适合入门,但不适合被误当成“正式 MCP 网络调用案例”。 #### 6.4.2 更贴近真实项目的客户端:McpClientAgent.py 【案例源码】`案例与源码-2-LangChain框架/11-mcp/McpClientAgent.py` [McpClientAgent.py](案例与源码-2-LangChain框架/11-mcp/McpClientAgent.py ":include :type=code") 这个案例更贴近真实项目,因为它做了下面这条完整链路: 1. 读取 `mcp.json` 2. 用 `MultiServerMCPClient` 连接服务 3. 通过 `get_tools()` 拿到 MCP 工具 4. 把工具交给 LangChain Agent 5. 让 Agent 在对话中实际使用这些工具 这个文件最值得学习的地方有两个: **第一,它说明 MCP 和 Agent 是怎么接起来的。** MCP 不负责帮你规划,也不负责帮你推理;它负责把工具接进来。真正决定“什么时候调用天气工具”的,是 Agent。 **第二,它说明 LangChain 对 MCP 的支持已经不只停留在工具发现。** 根据 LangChain 官方文档,除了 `get_tools()`,现在还可以: - `get_resources()` - `get_prompt()` - 使用 stateful session - 处理 logging / elicitation / structured content 不过本章主线,还是以 **MCP 工具接入 Agent** 为重点,这也是最适合入门先掌握的第一步。 这里再补一个会话相关的细节: 不少适配器会把“获取工具并调用工具”封装成按需连接的路径。多数普通工具这样用就够了;如果 Server 依赖长期会话状态,就要按文档使用 `async with client.session(...)` 等写法维持会话,再在这个会话里读取资源、提示词或调用工具。 ### 6.5 测试建议与学习顺序 建议按下面顺序跑这一章案例: 1. **先看 `McpServer.py` + `McpClient.py`** 先建立“服务端暴露工具、客户端发现并调用”的直觉。 2. **再看 `McpServerByFastMCP.py`** 建立对 `tool / resource / prompt` 三类能力的整体认识。 3. **再看 `McpServerWeatherByFastMCP.py` + `mcp.json`** 理解独立服务和客户端配置。 4. **最后跑 `McpClientAgent.py`** 看 LangChain Agent 怎么把 MCP 工具真正用起来。 如果你想更贴近官方生态调试方式,还可以了解 **MCP Inspector**: - 官方工具文档:https://modelcontextprotocol.io/docs/tools/inspector 它适合做这些事情: - 查看服务器有哪些 Tools / Resources / Prompts - 手工测试工具参数 - 看服务端日志和通知 这对排查“到底是服务端没暴露出来,还是客户端没连上”非常有帮助。 --- **章节思考题:** 1. MCP 解决的问题,和“写一个本地工具函数”有什么不同? **参考思路:** 本地工具函数只服务当前代码;MCP 让工具、资源、提示词以统一协议暴露给不同 AI 应用复用。它的重点是标准化接入和跨应用复用,而不是多写一个函数。 2. Host、Client、Server 三个角色如果搞混,会造成什么理解偏差? **参考思路:** 会分不清谁运行应用、谁负责协议通信、谁暴露能力。Host 是应用宿主,Client 负责连接 MCP Server,Server 提供 Tools / Resources / Prompts。角色清楚后,配置和排障才有方向。 3. Tool、Resource、Prompt 三类能力应该怎么划分? **参考思路:** Tool 适合可执行动作,Resource 适合被读取的上下文资料,Prompt 适合可复用提示模板。不要把所有东西都做成 Tool,否则权限和语义都会变乱。 4. 什么时候用 stdio,什么时候更关注 Streamable HTTP? **参考思路:** 本地进程、桌面工具、开发调试常见 stdio;跨进程、服务化、远程访问更需要 HTTP 形态。传输方式服务于部署场景,不是单纯背协议名。 **本章小结:** - **MCP 的本质**:不是模型、不是工具本身,而是 AI 应用与外部能力之间的标准化连接协议。 - **MCP 的核心价值**:统一发现、统一描述、统一接入,让工具、资源、提示词模板更容易跨应用复用。 - **和其他概念的区别**:Tool 解决“调用能力”,RAG 解决“检索知识”,Agent 解决“规划和决策”,MCP 解决“标准化接入”。 - **当前规范重点**:标准传输优先理解为 `stdio` 与 `Streamable HTTP`;仓库中的 `sse` 写法保留为案例兼容与教学理解用途。 - **本章案例主线**:从极简版教学服务端,到 FastMCP 服务端,再到 `mcp.json` + LangChain Agent 的 MCP 客户端,已经构成了完整的入门路线。 - **安全边界不能省略**:MCP 让接入更统一,不代表接入就天然安全。真实项目里仍要考虑服务器来源可信、认证授权、最小权限、敏感能力隔离,以及不要把本不该暴露的内部资源直接开放给模型侧。 - 学完本章后,你至少应该能分清四件事:MCP 是协议层;Host / Client / Server 是通信角色;Tools / Resources / Prompts 是服务端暴露的能力;统一接入不等于自动解决安全问题。 **建议下一步:** 建议先把本章的本地服务端和客户端都亲手跑通,再回看 [第 17 章 Tools 工具调用](17-Tools工具调用.md) 和 [第 21 章 Agent 智能体](21-Agent智能体.md),你会更容易看清“本地 Tool、MCP Tool、Agent 决策层”三者在工程上的分工。