AI 智能体的 MCP 操作

第 3 章 · 约 36 分钟阅读

阅读进度自动保存在此浏览器

本章内容

  • 理解 MCP 的基础知识及其在 Agent 开发中的作用
  • 快速上手 MCP Server
  • 在 Agent 中使用 MCP Server
  • 构建供 Agent 使用的 MCP Server

在第 2 章中,我们介绍了 Agent 的核心组成部分。本章将重点介绍 MCP ——赋予 Agent 行动能力的连接器。MCP 常被称为 Agent 和大语言模型的 USB-C 接口,因为它为 Agent 使用工具提供了一套统一的标准协议。更重要的是,随着我们构建越来越复杂的 Research Agent工作流,MCP 为 Agent 打开了一个庞大的工具生态,使其能够轻松调用各种外部能力。

MCP 在 AI 领域的发展速度极快,几乎所有希望 Agent 完成的任务,现在都可以找到对应的 MCP Server。不仅如此,我们还可以将自己开发的 Agent 封装成可供他人调用的 MCP Server。这样,复杂的 Agent 工作流也能作为工具,被其他 Agent 工作流复用,从而像组件一样自由组合,构建更加专业、可复用的 Agent 系统。

接下来的内容中,我们将首先介绍 MCP 的基础架构,然后学习如何构建 MCP Server,并让 Agent 使用这些 Server,从而理解 MCP 是如何全面提升 Agent 能力的。

3.1 理解 Agent 开发中的 MCP 基础

在深入介绍 Agent 的具体实现之前,我们需要先理解:

  • MCP 是什么?
  • MCP 为什么会出现?
  • MCP 是如何从根本上改变 AI 系统开发方式的?

MCP(Model Context Protocol) 是由 Anthropic 提出的一个开放标准,它基于 JSON-RPC 2.0 协议,旨在让 AI 系统能够以统一、安全、高效的方式连接各种外部服务。

3.1.1 MCP 解决的标准化问题

在 MCP 出现之前,开发 AI Agent 时通常会遇到许多关键问题,这些问题使 Agent 的开发既复杂又脆弱。图 3.1 展示了开发 Agent 和 LLM 应用时最常见的几个挑战。

本书插图

图 3.1 开发 Agent 时面临的主要问题包括:

  • 工具集成碎片化
  • 数据访问方式不统一
  • 多 Agent 编排复杂
  • 安全性与权限控制困难

正如图中所示,在为 LLM 构建 Agent 时,开发者通常需要面对多个难题。

  • 1. 工具集成碎片化:最大的痛点之一,就是不同 LLM 厂商拥有不同的 Tool 定义方式。无论是简单的单 Agent 系统,还是复杂的多 Agent 工作流,开发者都必须针对不同模型分别实现工具调用逻辑。

  • 2. 数据访问方式不统一:第二个问题是连接外部数据源缺乏统一接口。例如,一个 Agent 可能需要同时访问:

    • 文件系统
    • 数据库
    • Web API

    每一种数据源都需要开发不同的适配接口。也就是说,每增加一种数据源,就意味着增加一套新的集成方案。这些定制化接口不仅维护成本高,而且几乎无法复用。

  • 3. 不同模型之间工具无法互通:由于不同 AI 模型和 Agent Framework 对 Tool Calling 的定义各不相同,因此:一个为 OpenAI Function Calling 编写的工具,并不能直接在 Anthropic Claude 中使用。通常需要进行大量修改,包括:

    • 重写 Tool 的 JSON Schema;
    • 修改参数描述及类型定义;
    • 调整工具返回结果的数据格式;
    • 修改 Agent 解析 Tool Call 的代码。

    这些修改本身并不复杂,但每个模型厂商都有自己的一套规范。因此,开发者必须:

    • 阅读各家的 API 文档;
    • 针对不同平台分别调试;
    • 分别维护多套代码。

    假设团队需要同时支持两个模型供应商,那么所有工具都需要维护两份实现。如果需要支持四家供应商,工作量甚至会超过线性增长。因为每新增一个工具、修改一个 Schema 或调整一次行为,都必须同步更新所有平台上的实现。

  • 4. 多 Agent 编排困难:另一个重要问题是:多个 Agent 之间缺乏统一的通信方式。如果希望多个 Agent 协同工作,就必须编写大量自定义集成代码。过去并没有统一标准让一个 Agent 对外暴露自己的能力,因此构建复杂的多 Agent 系统十分困难。

  • 5. 安全与权限控制困难:最后,还有安全方面的问题。由于缺乏统一协议,开发者很难:

    • 实现统一的身份认证;
    • 管理访问权限;
    • 对工具调用进行监控;
    • 记录审计日志。

    不同工具和数据源通常都有各自独立的安全机制,使整个系统难以统一管理。

MCP 如何解决这些问题?

MCP 通过提供一套统一协议,标准化了 Agent 与外部系统之间的交互方式。有了 MCP,开发 Agent 不再需要为每个工具、每个平台单独编写适配代码,而更像是在搭积木:

  • 工具可以标准化接入;
  • Agent 可以统一调用;
  • 多 Agent 可以方便协作;
  • 安全机制也能够统一管理。

因此,Agent 开发从过去的大量重复集成工作,逐渐演变为基于标准组件进行组合与复用,极大提升了开发效率和系统可维护性。

3.1.2 MCP 架构:客户端、服务器与服务

还记得 USB-C 成为统一接口之前的时代吗?那时候,手机、相机、平板、耳机等设备几乎都有各自专有的接口,连接设备时常常需要准备各种不同的数据线和转接头。而随着 USB-C 的普及,我们现在只需要一根数据线,就可以连接所有支持 USB-C 的设备,再也不用为各种接口烦恼。 MCP 对 AI Agent 来说,就扮演着类似 USB-C 的角色。如果没有 MCP,你的 Agent 每连接一个工具或服务,都需要编写一套专门的集成代码。

图 3.2 展示了 Agent 连接多个服务和资源时所面临的问题。

本书插图

图 3.2 Agent/LLM 开发者连接多个服务和资源时面临的典型问题:由于缺乏统一的连接标准,不同服务需要不同的连接方式和适配器。

图中展示了一个需要访问多个服务和资源的 Agent。在 MCP 出现之前,开发者需要自己:

  • 编写各种工具;
  • 对接不同的 API;
  • 集成各种 SDK;
  • 适配不同通信协议;
  • 格式化各种返回结果。

换句话说,如果想构建一个功能完善的 Agent,大部分开发工作其实都花在了工具集成上,而不是 Agent 本身。

MCP 的出现彻底改变了这一局面。它提供了一套统一的通信协议,并将复杂的工具实现从 Agent 的代码中抽离出来。现在,开发者只需要连接到对应服务的 MCP Server,剩下的集成工作都由 MCP 协议负责完成。当然,MCP 并没有消除所有问题。在生产环境中,你仍然需要考虑:

  • 身份认证
  • 安全控制
  • 错误处理
  • Server 版本管理
  • Server 可用性

MCP 真正解决的是过去工具集成中最繁琐的一部分:

  • 不同厂商之间不同的 Tool Schema;
  • 不同 API 的调用格式;
  • 不同协议之间的数据转换。

正因为消除了这些重复工作,Agent 的开发方式发生了根本性的变化。

图 3.3 展示了从大量内部工具实现,转变为通过 MCP 统一访问外部资源的过程。

本书插图

图 3.3 MCP 作为服务层,对 Agent 所需访问的各种外部服务进行了统一封装和抽象。

继续沿用单智能体的示例,在使用 MCP 时,我们只需编写几行代码即可连接到服务器,然后将其注册到智能体中。如今,大多数现代 AI 智能体框架都已经支持与 MCP 的简单且无缝的集成。与此同时,一个不断壮大的开发者社区也正在实现各种 MCP Server,使任何 AI 智能体或大语言模型都能够连接到不同的服务。

为了进一步简化理解,我们可以将 MCP 生态系统视为由三个组成部分构成:客户端、服务器以及服务器所连接的底层服务(例如数据库、API、文件系统或 SaaS 产品)。

需要注意的是,这里的“服务”是我们为了说明方便而使用的术语,并不是 MCP 官方定义的概念。MCP 本身定义了服务器可以向客户端暴露的三种能力原语:

  • 工具(Tools)—— 智能体可以执行的操作。
  • 资源(Resources)—— 智能体可以读取的数据。
  • 提示(Prompts)—— 服务器为常见交互提供的模板。

在本章中,我们将只关注工具,因为绝大多数智能体的工作都是围绕工具展开的。

图 3.4 展示了 MCP 生态系统的三组件视图,并给出了可以集成的客户端和服务示例。

本书插图

图 3.4 MCP 架构由三个核心组成部分构成:Client、Server 和 Service。常见 Client 包括 Agent、LLM 应用、Claude Desktop 和 VS Code;底层 Service 则可能是文件系统、数据库、Web API 或其他 AI Agent。

该图展示了 MCP(模型上下文协议)架构的几个基本组成部分:

  • MCP 客户端 —— MCP 并不仅仅适用于智能体,它同样被许多 AI 应用和大语言模型所支持。客户端负责连接服务器,并利用资源发现机制查找可用的工具及其他资源。

  • MCP 服务器 —— 服务器负责处理客户端请求,并提供对工具、资源和服务的访问。它使用标准化的 JSON-RPC 2.0 协议处理请求并返回响应。

  • 服务 —— 表示实际的应用或资源,例如文件操作、数据库查询、Web 搜索,或者其他 AI 智能体。通过 MCP 连接智能体的方式与调用工具本质上是相同的。这种模式在很多场景下效果良好,但如果需要更强的能力,可能需要考虑使用 A2A(Agent-to-Agent)等协议。

MCP 客户端与服务器之间通过 JSON-RPC 2.0 进行通信,这确保了无论访问的是哪种服务,请求和响应都能以统一且结构化的方式进行交换。

3.1.3 核心组件:工具、资源与提示

每个 MCP Server 都通过三类组件向外提供能力,它们分别服务于 Agent 工作流中的不同用途:Tools、Resources 和 Prompts。图 3.5 展示了这三种组件及其使用方式。

本书插图

图 3.5 MCP Server 的主要组成包括 Prompts(专门的系统提示)、Resources(用于访问文件、配置、数据库等数据),以及 Tools(扩展 Agent 能力、执行具体操作的工具)。

MCP Server 会提供可发现的接口,例如 list_tools,使 LLM 或 Agent 能够识别服务器中提供了哪些能力。

下面是 MCP Server 可暴露的三类组件:

  • Tools——工具是 Agent 或 LLM 可以主动调用的可执行能力。它们通常是 Agent 完成工作的主要方式,也是 MCP Server 中最常被使用的能力。需要注意的是,并不是所有 MCP Client 都支持三种能力(Tools、Resources、Prompts)。有些客户端仅支持 Tools,因此一些 MCP Server 作者会把原本属于 Prompt 或 Resource 的功能包装成 Tool,使客户端仍然能够访问这些能力。不过,这只是一种兼容性方案,而不是最佳实践。Tools、Resources 和 Prompts 在设计目标上是不同的,它们各自承担不同职责。

  • Resources——资源表示 Agent 可以读取的外部数据,例如数据库、文件、配置文件以及其他可供 LLM 使用的数据源。

  • Prompts——提示是服务器预定义好的 Prompt 模板,用于规范 Agent 如何执行复杂工作流,或者如何调用服务器提供的复杂工具与资源。

三种能力还有一个重要区别,那就是由谁决定何时使用它们:

  • Tools 由模型控制。Agent 会根据用户的问题,自主决定是否调用某个工具以及何时调用。
  • Resources 和 Prompts 通常由用户或应用程序控制。例如,用户主动选择一个 Prompt 模板,或者客户端把可用资源展示出来,让用户选择附加到当前对话中。

这也是 Claude Desktop、Cursor 等 MCP 客户端普遍采用的模式,因此 MCP 才会把这三种能力设计成彼此独立的类别,而不是全部统一成 Tool。可以简单理解为:

  • Tools 是 Agent 主动拿来用的。
  • Resources 和 Prompts 是用户主动交给 Agent 使用的。

注意:本书主要关注 MCP Server 中的 Tools。目前 OpenAI Agents SDK 还不支持 Resources 和 Prompts,不过这并不是太大的问题,因为很多情况下 Tool 可以承担它们的大部分作用。如果你使用的是支持 Resources 或 Prompts 的 Agent 框架,则建议按照它们原本的设计用途来使用这些能力。

下一节,我们将介绍 MCP Server 的部署方式,以及 Agent 如何连接这些服务器。

3.1.4 面向 Agent 的 MCP 部署模式

根据 Agent 系统架构的不同,MCP Server 可以采用不同的部署方式。图 3.6 展示了 MCP Server 与 Agent(或 LLM)之间的三种典型连接模式:

  • 本地部署
  • 远程部署
  • 混合部署

本书主要讨论 Agent 如何使用 MCP,但需要牢记:MCP 并不仅仅服务于 Agent,它同样适用于各种 LLM 应用。

本书插图

图 3.6 Agent 连接 MCP Server 的几种部署模式,包括本地子进程运行、远程 HTTP 服务,以及同时连接本地与远程 MCP Server 的混合架构。

从图中可以看到,MCP Server 可以通过多种方式部署并供 Agent 使用:

  • 在本地作为子进程运行,通过标准输入/输出通信;
  • 部署在独立服务器上,通过 HTTP 提供服务;
  • 同时连接多个本地与远程 Server,形成混合架构。

下面分别介绍几种部署模式。

  • 本地部署:当 MCP Server 与 Agent 运行在同一台机器上时,它们通常通过标准输入/输出进行通信。这种方式具有以下优点:

    • 通信延迟低;
    • 没有网络开销;
    • 进程管理简单;
    • 非常适合开发环境或单机部署。
  • 远程部署:当 MCP Server 与 Agent 部署在不同机器上时,它们通常通过 HTTP 和 Server-Sent Events(SSE)进行通信。这种方式支持:

    • 分布式 Agent 架构;
    • 多个 Agent 共用同一个 Tool Server;
    • 云原生部署;
    • 多租户与负载均衡。
  • 混合部署:真实生产环境中的 Agent 系统通常会同时采用本地和远程 MCP Server。例如:

    • 本地 Server 负责敏感操作(文件访问、本地数据库等);
    • 远程 Server 提供共享服务(Web 搜索、外部 API 等);
    • 不同 Agent 之间也可以通过远程 MCP Server 相互通信。

本地与远程混合使用

需要特别说明的是:

一个 MCP Server 要么是本地部署,要么是远程部署,不存在单个 Server 同时属于两种模式。

所谓"混合部署",真正的含义是:

一个 Agent 同时连接多个 MCP Server,而这些 Server 中既有本地的,也有远程的。

例如,一个 Agent 可能同时连接:

  • 本地 Filesystem MCP Server;
  • 本地 Git MCP Server;
  • 远程 GitHub MCP Server;
  • 远程 Slack MCP Server。

每个 Server 都采用自己的部署方式,而 Agent 在协议层面对它们一视同仁,全部通过 MCP 进行通信。因此,人们常说的 Hybrid Deployment,更准确地说应该理解为:

Agent 同时使用多台不同部署方式的 MCP Server,而不是某一个 MCP Server 本身就是混合部署。

虽然本书主要关注 Agent 如何使用 MCP,但在开始之前,也值得看看像 Claude Desktop 这样的 LLM 应用是如何接入 MCP Server 的。

3.1.5 MCP 为 Agent 的各个功能层提供能力

虽然 MCP 通常是以 Tool 的形式被 Agent 使用,但我们不应该仅仅把它理解为一个工具接口。正如第 1 章介绍 Agent 功能分层时所说,MCP 可以融入 Agent 的各个功能层,为不同层提供能力支持。图 3.7 展示了 MCP 在各层中的作用。

本书插图

图 3.7 MCP 可以通过提供各种工具,为 Agent 的所有功能层增加能力。

从图中可以看到,Agent 的各个功能层都可以借助部署在 MCP Server 上的工具进行增强,不同层关注的能力也各不相同:

  • 工具与行动层:这一层负责连接 Agent 需要操作的应用程序和 API。典型示例包括:

  • GitHub MCP Server(创建 Issue、发起 Pull Request)

  • Slack MCP Server(发送消息、读取频道内容)

  • Filesystem MCP Server(读取和写入本地文件)

这一层对应的是 Agent 能够"做什么"。

  • 推理与规划层:这一层连接的是帮助 Agent 思考问题的 MCP Server。最典型的是 Sequential Thinking Server(顺序思考服务器),它提供一种结构化的 Chain of Thought(思维链)流程,当任务需要明确规划时,Agent 可以调用它来辅助推理。除此之外,还包括:

    • Memory-aware Reasoning Server(具备记忆能力的推理服务器)
    • Graph-of-Thought Server(思维图推理服务器)

    这一层对应的是 Agent 如何思考。

  • 知识与记忆层:这一层负责连接各种存储和检索系统。例如:

    • Postgres MCP Server(查询关系型数据库)
    • Qdrant、Turbopuffer 等向量数据库(基于 Embedding 的语义搜索)
    • Google Drive MCP Server(读取用户有权限访问的文档)
    • Memory Server(跨会话保存长期记忆)

    这一层对应的是 Agent 知道什么,以及如何获取知识。

  • 评估与反馈层:这一层负责评分、评估以及人工审核。例如:

    • LLM-as-Judge Server(利用 LLM 对 Agent 输出进行评分)
    • Evaluation Framework Server(运行 Benchmark 测试)
    • Approval Routing Server(暂停 Agent,等待人工审核确认)

    这一层对应的是 Agent 如何验证自己的结果是否正确。

因此,需要牢记:

MCP 并不仅仅是一种 Tool 调用协议,它实际上是一种 Agent 与外部能力连接的统一模式。

它不仅能够连接执行工具,还能够支持:

  • 推理与规划
  • 知识与记忆
  • 评估与反馈

几乎覆盖 Agent 的所有功能层。

3.2 开始使用 MCP Server

MCP 最初由 Anthropic 提出,目的是为 LLM 提供统一的工具调用标准。同样由 Anthropic 推出的 Claude Desktop,则很好地展示了 LLM 如何连接并使用 MCP Server。Claude Desktop 是 Anthropic 提供的一款免费桌面客户端。除了拥有与网页版 Claude 相同的功能之外,它还支持本地能力,包括:

  • 直接连接本机上的 MCP Server;
  • 调用本地工具;
  • 访问本地资源。

本章选择 Claude Desktop 作为演示环境,是因为它能够通过简单的配置文件快速完成 MCP 集成,非常适合作为学习和实验平台。

不过,截至 2026 年,MCP 已远远不限于 Claude。目前已经支持 MCP 的平台包括:

  • ChatGPT
  • Gemini
  • Cursor
  • VS Code
  • Claude Code
  • Windsurf
  • Vercel AI SDK
  • 以及大多数主流 Agent Framework

因此,本章介绍的 MCP 使用方式几乎都可以迁移到这些平台。真正变化的只有:

  • 各客户端的配置方式
  • 支持哪些 MCP 能力

而底层协议始终都是 MCP。

图 3.8 展示了 Claude Desktop 如何连接多个 MCP Server。关于 Claude Desktop 与 MCP 的安装配置,可参考附录 B,本章后续所有示例都会基于这些环境进行。

本书插图

图 3.8 Claude Desktop 可以连接多个本地或远程部署的 MCP Server,而驱动 Claude 的底层 LLM 则通过这些 MCP Server 提供的能力(通常是 Tools)扩展自身功能。

从图中可以看到:Claude Desktop 可以同时连接多个 MCP Server,每个 Server 又可以提供多个 Tool。这些 MCP Server 可以:

  • 部署在本地计算机;
  • 部署在远程服务器,并通过 HTTP 提供服务。

一旦完成连接配置,驱动 Claude 的底层 LLM 就会自动注册这些 MCP Server 提供的:

  • Tools
  • Prompts
  • Resources

随后,Claude 会根据用户的请求,自主判断哪些能力可以被调用,并在适当的时候使用它们。

3.2.1 为 Claude 编写一个 MCP Server

清单 3.1 展示了一个最简单的 MCP Server,它只暴露了一个 get_research_sources 工具。实际上,这个工具我们在第 2 章已经实现过,代码几乎完全相同。不同之处在于:现在我们不是把它作为 Agent 的内部函数,而是把它包装成一个 MCP Server 对外提供服务。

完成这一工作的关键,就是 @mcp.tool() 装饰器。当它修饰一个函数时,会自动将以下信息注册到 MCP Server 的 Tool Schema 中:

  • 函数签名:告诉 Agent 这个工具有哪些参数。
  • Docstring:作为工具说明,帮助 Agent 判断什么时候应该调用该工具。
  • 返回值类型:告诉 Agent 返回的数据结构是什么样。

因此,在 MCP 中,Docstring 非常重要。它不仅仅是写给开发者看的注释,更重要的是:

它实际上就是 Agent 在选择工具时所阅读的 Prompt。

清单 3.1 01_claude_mcp_server.py
from mcp.server.fastmcp import FastMCP

# 创建 MCP Server
mcp = FastMCP("Research Tools")    #1

@mcp.tool()    #2
def get_research_sources() -> list[str]:
   """返回可用的研究资料来源。"""
   search_sources = [
       "Wikipedia",
       "Google",
       "YouTube",
   ]
   return search_sources

代码说明:

  • #1FastMCP 是一个已经封装好的 MCP Server。它基于 FastAPI,内部已经实现了 MCP 协议,因此无需自己处理底层通信。

  • #2 使用 @mcp.tool() 装饰器,把普通 Python 函数注册成 MCP Tool。

我们不会直接运行这个 Python 文件。相反,我们会把它安装到 Claude Desktop 中,让 Claude 自动启动并使用这个 MCP Server。安装方式如清单 3.2 所示。安装之前,请确保当前终端位于 Python 文件所在目录,或者正确指定文件路径。

清单 3.2 安装 MCP Server
cd chapter_03    #1

mcp install 01_claude_mcp_server.py    #2

输出示例:

INFO     Added server 'Research Tools' to Claude config

INFO     Successfully installed Research Tools in Claude app

代码说明:

  • #1 进入 Python 文件所在目录。

  • #2 将该 Python 文件安装为 Claude Desktop 可识别的 MCP Server。前提是已经安装好了 Claude Desktop。

安装完成之后:如果 Claude Desktop 已经打开,请关闭并重新启动。随后可以进入:File → Settings → Developer,查看 MCP Server 是否已经加载成功,如图 3.9 所示。

本书插图

图 3.9 Claude Desktop 中的 MCP Server 配置页面

注意:

如果提示 Tool 无法加载,请参考附录 B 安装 MCP 与 Claude Desktop 所需的依赖。

Windows 用户在安装或修改 MCP Server 后,可能需要重新安装 Claude Desktop,或者至少重启一次。

从图 3.9 可以看到,我们刚才在 Python 中编写的 Research Tools Server 已经成功作为 MCP Server 启动。如果 Server 无法正常运行,可以直接复制设置窗口中显示的启动命令,在终端中执行查看错误信息。常见问题包括:

  • 没有安装 uv
  • Python 没有正确加入终端环境变量
  • Python 文件路径配置错误

确认 Server 正常运行后,就可以关闭设置窗口。然后新建一个 Claude 对话,并输入:

get_research_sources

如图 3.10 所示。

本书插图

图 3.10 Claude Desktop 中执行 MCP Tool

从截图可以看到,按下 Enter 后,Claude 并不会立即执行工具,而是先弹出一个授权提示。这是 Claude Desktop 内置的安全机制。只有获得用户允许之后,Tool 才会真正执行。而当我们自己开发 Agent 时,通常不会有这样的人工确认步骤,因此:

一定要充分了解每一个 Tool 能做什么,避免赋予 Agent 过大的权限。

授权之后,LLM 就会调用该 Tool,并返回我们在代码中配置好的研究资料来源列表。不过,你会发现:Claude 返回的不仅仅是列表本身,它还会补充一些解释和说明。这是因为 Claude 希望回答得更加有帮助。而在真正开发 Agent 时,我们通常会通过 Prompt 或系统指令,对这种行为进行约束,使输出保持更加严格、稳定。

Claude Desktop 是测试 MCP Server 的一个非常好的工具。它能够帮助我们:

  • 验证 MCP Server 是否工作正常;
  • 观察 LLM 如何选择并调用 Tool;
  • 理解 Tool 在真实对话中的执行过程。

不过,如果希望更深入地查看 MCP Server 提供的接口、参数以及返回结果,仅靠 Claude Desktop 还不够方便。幸运的是,MCP 官方还提供了 MCP Inspector,下一节我们将介绍如何使用它。

3.2.2 使用 MCP Inspector

MCP 提供了一个 Inspector 工具,可以用于探索任何 MCP Server,无论它是远程、本地、自行开发,还是下载获得的服务器。当工具没有按预期工作时,Inspector 通常是你首先应该使用的调试工具。它能够:

  • 连接到你的 MCP Server;
  • 展示 Agent 实际看到的工具列表(包括描述、参数和返回类型);
  • 使用任意参数直接调用工具,验证返回数据格式是否正确;
  • 显示 Client 与 Server 之间完整的 JSON-RPC 通信过程,帮助定位调用失败的位置;
  • 展示初始化过程中发生的错误,这些错误通常在 Agent 使用服务器时是不可见的。

Inspector 最常见的用途,就是确认由装饰器自动生成的 Tool Schema 是否符合预期,尤其是在 工具已经注册,但 Agent 却始终不调用它的情况下。下面的清单展示了如何使用 Inspector 检查上一节创建的 Research Tools MCP Server。

清单 3.3 检查一个 MCP Server

mcp dev {absolute path to file}01_claude_mcp_server.py    #1

OUTPUT
Starting MCP inspector...
🟢 Proxy server listening on port 6277
🚀 MCP Inspector is up and running at http://127.0.0.1:6274    #2

说明:

  • #1 必须使用 Python 文件的绝对路径,否则 Inspector 会提示无法找到该文件。
  • #2 在浏览器中打开输出的 URL 即可访问 Inspector。

运行该命令时,请务必使用绝对路径,而不要使用相对路径,否则 Inspector 无法正确启动目标服务器。

图 3.11 展示了 Inspector 成功连接到 Research Server 后的界面。

本书插图

图 3.11 MCP Inspector 界面,可用于查看可用工具并直接执行它们。

从图中可以看到:

  • 已成功连接到 MCP Server;
  • 当前选中了 Tools 页面;
  • 页面列出了服务器提供的所有工具;
  • 点击 Run Tool 按钮即可执行 get_research_sources 工具;
  • 按钮下方会显示工具返回结果;
  • 页面底部记录了服务器执行历史及每一次调用的结果。

图中还有一个值得注意的细节:Server 使用的是 STDIO 作为 传输方式。之所以采用 STDIO,是因为这是一个本地 MCP Server,它作为一个子进程启动,并通过标准输入和标准输出与客户端通信。

需要注意的是,这与是否使用 uv 来启动服务器没有关系。 uv 只是 Python 的运行器,负责启动 Server 进程,真正决定通信方式的是 Server 本身的配置。也就是说,同样是清单 3.1 中创建的 FastMCP Server,只需修改启动配置,就可以改为使用 HTTP 或 SSE 作为传输协议,而无需更换 uv。下面我们来进一步了解这些不同的传输方式。

3.2.3 理解 MCP 的传输类型

前面我们已经介绍过 MCP 支持的两种通信传输方式:STDIO和SSE。理解它们之间的区别,对于开发或使用 MCP Client 与 MCP Server 都十分重要。

表 3.1 对两种 Transport 的工作方式、底层协议以及适用场景进行了详细比较。

表 3.1 MCP Server 中 STDIO 与 SSE 两种 Transport 的区别
名称 工作方式 适用场景
STDIO Client 在本机启动 MCP Server 作为一个子进程,并通过进程的 stdin/stdout 管道交换 JSON-RPC 2.0 消息。整个过程无需网络,因此延迟极低。但这种方式属于一对一通信:只有启动该 Server 的父进程能够与其通信。 • 本地开发
• 命令行实验
• CLI 工具临时启动 MCP Server(如 mcp dev my_server.py
• 使用 docker run -it 进行交互式容器调试
• 不需要远程访问或多客户端支持的简单进程间通信
SSE MCP Server 作为 HTTP 服务运行。客户端通过 /messages 发送 JSON-RPC 请求,服务器通过持续保持的 text/event-stream(例如 /sse)流返回响应和通知。客户端→服务器采用 HTTP POST,服务器→客户端采用 SSE,因此属于半双工通信,但能够通过网络供多个客户端同时访问。 • 云端部署
• 远程 MCP Server
• 需要通过反向代理或负载均衡访问
• 浏览器应用
• 需要实时流式输出(例如 Token Streaming)而又不希望使用 WebSocket 的场景
• SSE 对大多数防火墙和代理都具有良好的兼容性

本书后续构建的许多 Agent Workflow 都会同时涉及这两种 Transport。除此之外,在其他基于 LLM 的应用中(例如 Claude Desktop),理解它们同样十分重要。接下来,我们将进一步探讨 Agent 与 Claude 等 LLM 应用之间的关键区别。

3.2.4 从 Claude Desktop 到 Agent:二者的关键区别

Claude Desktop 是学习 MCP 的绝佳起点,但它本质上仍然是一个 Assistant,而不是一个 Agent。二者的区别并不在于是否会向用户请求授权。是否弹出权限确认窗口,只是一种安全机制,而不是架构上的区别。事实上,大多数代码 Agent(如 Claude Code、Cursor、Aider)默认都会请求用户授权,但它们依然属于真正意义上的 Agent。真正的区别在于自主性和执行循环。如图 3.12 所示。

本书插图

图 3.12 LLM 应用(Assistant)与 Agent 使用 MCP Server 工具时执行流程的对比。两者最大的区别在于:Assistant 需要人工监督,而 Agent 可以自主执行;Assistant 以交互式工作,而 Agent 以程序化方式运行;Agent 能够完成复杂、多步骤工作流,而 Assistant 通常只能执行简单计划。

Assistant 的工作流程通常是:

  1. 接收用户请求;
  2. 判断应该做什么;
  3. 执行任务;
  4. 返回结果;
  5. 结束。

整个流程只执行一次。而 Agent 则不同。Agent 会不断运行一个循环:

  1. 调用工具;
  2. 观察执行结果;
  3. 判断下一步应该做什么;
  4. 再次调用工具;
  5. 持续执行多个步骤;
  6. 直到完成目标后才返回给用户。

整个过程中,无需每完成一步都重新等待用户输入。Agent 拥有自主性,也意味着工具设计与工具选择的重要性大大提高。对于 Assistant 来说,调错一个工具,通常只会得到一次错误回答。而对于 Agent 来说, 一次错误的工具调用,可能会连锁触发后续十几步错误,在没人发现之前不断扩大影响。因此,在生产环境中,Agent 系统通常会将 MCP 与纵深防御结合使用。这种防御体系通常包括:

  • 工具白名单:Agent 只能看到部署者明确允许使用的工具。

  • 沙箱:文件系统、Shell 等高风险工具运行在隔离环境中。

  • 输出校验::在 Agent 使用工具返回结果之前,先验证结果是否符合预期 Schema。

  • 限流与预算控制: 即使 Agent 进入死循环,也会因为预算或调用次数达到上限而自动停止。

  • 人工审核: 对于发送邮件、转账、删除数据等不可逆操作,仍然要求人工确认。

正如我们之前看到的,在 Claude Desktop 等 LLM 应用中。每一次工具调用都需要用户审核和批准。而 Agent 则拥有真正的自主能力,它能够自行决定:

  • 是否调用工具;
  • 调用哪个工具;
  • 什么时候调用;
  • 如何组合多个工具完成目标。

因此,在本书第 7 章中,我们将重点介绍如何测试 Agent 的工具使用,确保无论是否通过 MCP,其行为都符合预期且安全可靠。

⚠️ 警告:失控的 Agent

开发 Agent 时,请始终牢记一句话:

Expect the unexpected(永远要预料到意料之外的情况)。

Agent 带来的风险远不止误删文件那么简单。任何 Agent 能调用的工具,都可能被它误用。而且,这些风险会不断叠加。常见风险包括:

  • 破坏性操作:拥有删除或写入权限的 Agent,迟早会删除或覆盖某些你本不希望它修改的内容。风险对象包括文件系统、数据库、Git 仓库、云资源。

  • 数据泄露:如果 Agent 同时具有读取敏感数据的能力和发送消息的能力,那么它可能被诱导,将敏感信息发送到外部环境。攻击来源可能包括:恶意文档、被污染的搜索结果、精心构造的 Prompt。

  • 隐私泄露:使用用户身份运行的 Agent,拥有与用户相同的权限。因此,它可能访问用户本人都没有意识到会共享的数据,例如:被发送给模型提供商的数据、Tracing 系统保存的数据、向量数据库中的数据。

  • 成本失控:如果 Agent 不断循环调用高成本工具,例如 LLM-as-Judge、Web Search、付费 API,那么在监控系统发现之前,就可能已经耗尽预算。如果没有预算限制,一个陷入死循环的 Agent 甚至可能一夜之间产生数万美元的账单。

  • 来自工具输出的Prompt 注入:Agent 会把工具返回的内容加入自己的上下文。因此一个恶意网页、一份恶意文档、一封恶意邮件,都可能包含 Prompt Injection,从而诱导 Agent 执行攻击者希望执行的操作。对于所有读取外部内容的工具来说,这应该被视为默认风险,而不是极端情况。事实上,在 2026 年,Prompt Injection 已成为针对 Agent 最具威胁、影响最大的攻击方式之一。

理解 Agent 所使用的工具以及 MCP Server,仅仅只是安全工作的起点,而不是终点。除此之外,还应该结合前面介绍的各种防御策略:

  • 工具白名单
  • 沙箱隔离
  • 输出校验
  • 调用限流
  • 人工审批

共同构建一个安全可靠的 Agent 系统。接下来,我们将开始使用 OpenAI Agents SDK 构建 MCP Server,并进一步了解我们的 Agent 能够接入和使用哪些类型的 MCP Server。

3.3 在 Agent 中使用 MCP Server

MCP Server 承载了 Agent 能够执行的各种 Action。这里所说的 Action,是指 Agent 为了完成目标所执行的任何操作,例如:

  • 调用某个工具
  • 获取某项资源
  • 将任务交给其他 Agent
  • 与其他 Agent 协作完成任务

本书将利用 MCP 来:

  • 构建并托管自己的工具;
  • 访问各种 API;
  • 对接不同的数据源;
  • 为 Agent 提供各种可执行动作。

图 3.13 展示了 MCP Server 的几种构建和使用方式。

本书插图

图 3.13 Agent 与 MCP Server 的几种交互方式

从图中可以看到,Agent 可以通过多种方式使用 MCP,为自己增加工具或其他能力。常见方式包括:

  • 本地托管:MCP Server 作为 Agent 的子进程运行,双方通过 STDIO 通信。

  • 本地独立服务:MCP Server 独立运行,但仍位于同一台机器或同一个容器中,Agent 通过 HTTP + SSE 与其通信。

  • 远程部署:MCP Server 部署在远程服务器(例如云端),Agent 通过 HTTP + SSE 访问。

由于图中涉及多种部署方式,表 3.2 对各种典型使用场景进行了总结。

表 3.2 MCP 的典型使用场景
使用场景 用途 通信方式 部署位置
子进程内部工具 Python 进程负责启动并管理 MCP Server 子进程。 STDIO 当前进程内部
本地独立工具 对外提供本地服务,例如搜索数据库、索引服务等。 STDIO 本地机器
远程工具 调用网络上的工具服务,例如 Google、Wikipedia、播客搜索、研究搜索等。 SSE 远程服务器
远程 Agent 调用远程部署的完整 Agent 或 Agent Workflow,每个 Agent 都提供自己的 Tool 集合。 SSE 远程服务器

本书后续的大部分 MCP 示例都会围绕表 3.2 中这些场景展开。即使是在内部工具场景下使用 MCP,也具有明显优势:

  • 职责划分更加清晰;
  • Agent 与 Tool 解耦;
  • 更容易扩展;
  • 更方便在多个 Agent 之间复用。

当然,MCP 并不是免费的。它也会带来一些额外成本,这一点值得明确指出。相比直接调用 Python 函数,MCP 多了一层:

  • MCP Server 进程;
  • MCP Client 连接;
  • MCP 协议通信。

如果 Tool 本来就在 Agent 所在的 Python 进程中,那么使用 MCP 意味着:

  • 每次调用都会增加额外延迟: STDIO 通常增加几毫秒,HTTP 通常更慢。

  • 增加新的故障点:函数本来可以直接调用,但 MCP Server 可能因为未启动、连接失败等原因不可用。

此外,还需要维护额外的:

  • Server;
  • 部署;
  • 调试;
  • 生命周期管理。

因此,一个简单的经验法则是:

真正属于外部能力时,再使用 MCP。

例如:

  • 不同团队维护的服务;
  • 独立部署的系统;
  • 希望多个 Agent 共享的能力;
  • 第三方 API。

而对于:

  • 当前 Agent 私有的业务逻辑;
  • 普通 Python 函数;

则应优先直接进行进程内函数调用。如果所有内部函数都强行改造成 MCP,只会让架构图看起来很漂亮,却让生产系统变得更加复杂。虽然本章不会覆盖表 3.2 中所有使用场景,但接下来首先介绍如何在 OpenAI Agents SDK 中使用 MCP。

3.3.1 使用 STDIO 连接本地 MCP Server

清单 3.4 展示了如何从代码中启动清单 3.1 创建的 MCP Server。这样,我们之前编写的 get_research_sources 工具,就可以作为一个 MCP Server,在子进程中运行,并由 Agent 应用统一管理。这种本地部署方式(STDIO)与前面 Claude Desktop 使用 MCP Server 的方式完全一致:都是通过命令行启动一个子进程,然后使用 STDIO 通信。

清单 3.4 02_mcp_agent_stdio_server.py
SCRIPT = Path(__file__).with_name(
   "01_claude_mcp_server.py").resolve()    #1

async def main():
   async with MCPServerStdio(    #2
       name="Research Tools",
       params=MCPServerStdioParams(
           command="mcp",
           args=["run", str(SCRIPT)],
       ),
   ) as research_server:

       agent = Agent(
           name="Assistant",
           instructions="Use the research tools to perform research.",
           mcp_servers=[research_server],
       )

       print("Running: Get the available research sources")

       result = await Runner.run(
           agent,
           "Get the available research sources"
       )

       print(result.final_output)    #3

if __name__ == "__main__":
   asyncio.run(main())

运行结果:

Running: Get the available research sources

The available research sources are:

1. Wikipedia
2. Google
3. YouTube

说明:

  • #1 获取 MCP Server Python 文件的完整路径。
  • #2 使用 STDIO Transport 启动 MCP Server。当 async with 代码块结束时,对应的子进程也会自动关闭。
  • #3 输出 Agent 返回的最终结果。

只要 async with 代码块仍在执行,MCP 子进程就会一直运行,Tool 也始终可供 Agent 调用。当退出 with 代码块时, MCP Server 自动关闭,子进程结束。

这种模式非常适合在本地运行各种 MCP Server,只要该 Server 能够通过命令行启动,就可以由 Agent 作为子进程统一管理。同时,也便于在 Agent 服务内部完整控制 Server 的:

  • 启动
  • 生命周期
  • 关闭

在进入下一节之前,还有一点非常值得注意。下一节(清单 3.7)中,我们将使用的 MCP Server 是:

@modelcontextprotocol/server-filesystem

这是一个 Node.js 实现的 MCP Server,需要通过 npx 启动。而我们的 Agent 则是使用 Python 编写的。神奇的是:我们既没有编写任何绑定,也没有安装 Node 与 Python 的桥接库,更没有处理任何跨语言通信。这正是 MCP 最具价值的优势之一。MCP 在协议层完全与编程语言无关。因此:

  • Python Agent 可以调用 Node.js Server;
  • TypeScript Agent 可以调用 Rust Server;
  • Go Agent 也可以调用上述任何一种 Server。

Agent 与 MCP Server 之间唯一需要遵守的,就是 MCP 协议。它们无需使用相同语言、相同运行时,甚至无需使用同一个框架。正因为如此,在生产环境中,MCP 才真正成为了一层可移植的集成协议,而不是某个框架专属的工具生态。下一节,我们将进一步介绍如何通过 STDIO 使用本地独立运行的 MCP Server。

3.3.2 在 Agent 中通过 SSE 使用本地 MCP Server

有时,你可能会发现某些 MCP 服务并不适合作为子进程运行。这时,可以考虑将 MCP Server 作为一个独立的 HTTP 服务运行,Agent 再通过 SSE与其通信。在演示这种方式之前,我们首先需要从命令行启动上一节创建的 MCP Server。

清单 3.5 使用 SSE 运行 MCP Server
cd chapter_03    #1

mcp run -t sse 01_claude_mcp_server.py    #2

说明:

  • #1 先进入本章代码目录。
  • #2 使用 -t sse 参数,将 Python 文件作为 SSE 模式 的 MCP HTTP Server 启动。

该命令会启动一个基于 HTTP 的 MCP Server,并使用 SSE 作为通信协议。之所以能够这样做,是因为清单 3.1 中使用的 FastMCP 类同时支持 STDIO 和 SSE 两种 Transport。当 Python 文件作为子进程运行时,FastMCP 默认采用 STDIO。而当使用命令行启动时,则可以通过-t sse指定使用 SSE。这样做的好处是:

  • MCP Server 可以长期运行;
  • 多个 Agent 可以同时连接到同一个 Server;
  • Server 与 Agent 生命周期完全解耦。

下面的清单展示了如何修改上一节代码,使 Agent 改为连接 SSE Server。

清单 3.6 03_mcp_agent_sse_server.py
# 仅展示相关代码

async def main():
    async with MCPServerSse(    #1
        name="SSE Python Server",
        params={
            "url": "http://localhost:8000/sse",    #2
        },
    ) as research_server:

        agent = Agent(
            name="Assistant",
            instructions="Use the research tools to perform research.",
            mcp_servers=[research_server],    #3
        )

说明:

  • #1 将 Server 类型改为 MCPServerSse,表示使用 SSE Transport。
  • #2 不再通过命令启动 Server,而是通过 HTTP URL 连接到已经运行的 MCP Server。
  • #3 其余 Agent 代码保持不变。

可以看到,这次最大的变化只有两点:

  1. Server 类型由 STDIO 改为 SSE;
  2. 连接方式由命令改为 URL。

除此之外,其余 Agent 代码几乎完全一致。这种设计带来了很大的灵活性:

  • 可以轻松切换不同的 Transport;
  • 可以连接部署在任意位置的 MCP Server;
  • 可以方便地接入其他团队提供的 MCP 服务。

下一节,我们将开始介绍一些标准 MCP Server,以及 Agent 如何与它们进行交互。

3.3.3 连接标准 MCP Server

在 MCP 发布时,Anthropic 提供了多个官方参考实现的共享服务器。这些服务器内置了一系列标准工具,开发者无需自己编写即可直接使用。目前可用的服务器仍在不断增加,表 3.3 列举了一些较为常见的 MCP Server。

表 3.3 常见的标准 MCP Server

名称 描述 启动命令(npx ...) 典型用途
Filesystem 安全访问指定目录中的本地文件(读/写) npx -y @modelcontextprotocol/server-filesystem <path> 通过 AI 助手打开、编辑或创建本地文件
Sequential Thinking 将复杂任务拆解并规划执行步骤 npx -y @modelcontextprotocol/server-sequential-thinking 将目标拆分为可规划、可执行的任务
Google Drive 浏览、搜索和获取 Google Drive 文件 npx -y @modelcontextprotocol/server-gdrive 访问或共享云端文档,并借助 AI 协作
Google Calendar 读取日历、查找空闲时间、添加或删除日程 npx -y mcp-google-calendar 使用自然语言安排会议或查看日程
Todoist 管理待办事项(查看、创建、完成) npx -y @abhiz123/todoist-mcp-server 管理个人或团队的待办任务
Notion 读取、更新或创建 Notion 页面和数据库 npx -y @notionhq/notion-mcp-server 以编程方式管理笔记、Wiki 和项目计划
Slack 通过 Slack API 发送消息、读取频道 npx -y @modelcontextprotocol/server-slack 在 Slack 中发送通知或总结讨论内容
Brave Search 执行网络搜索并返回排序结果 npx -y @modelcontextprotocol/server-brave-search 获取最新网页信息或快速开展研究
GitHub 浏览仓库、读取文件、创建 Commit 或 PR npx -y @modelcontextprotocol/server-github 自动化代码审查、更新文档或获取项目文件
Google Maps 查询地点、路线及出行信息 npx -y @modelcontextprotocol/server-google-maps 回答位置相关问题或规划行程
Fetch 获取网页内容并进行预处理,供 LLM 使用 npx -y @modelcontextprotocol/server-fetch 抓取文章或 API 数据,再进行总结和分析

MCP 还极大简化了服务器的连接和运行过程,无需复杂的依赖安装。只需使用框架提供的 MCPServerStdio 类启动服务器,框架便会自动管理子进程的生命周期。

清单 3.7 展示了如何将前面的 Python 本地文件服务器升级为使用 MCP Filesystem Server。

前提条件

Filesystem Server 是一个 Node.js 包,通过 npx 启动,因此你的电脑需要安装 Node.js 和 npm(安装 Node.js 后会自动包含 npx)。

Python 端代码无需做任何修改。

清单 3.7 04_mcp_agent_server_files.py
async def main():
   current_dir = os.path.dirname(os.path.abspath(__file__))    #1

   async with MCPServerStdio(    #2
       name="Filesystem Server, via npx",
       params={
           "command": "npx",
           "args": ["-y",
                    "@modelcontextprotocol/server-filesystem",    #3
                    current_dir],
       },
   ) as server:
       agent = Agent(
           name="Filesystem Agent",
           instructions="Use the filesystem tools to help the user with their tasks.",    #4
           mcp_servers=[server],
       )

       print("Running: Get the available files")
       result = await Runner.run(
           agent,
           """
List the files in the current directory.
"""
       )
       print(result.final_output)

if __name__ == "__main__":
   asyncio.run(main())

输出:

- Files:
  - 01_claude_mcp_server.py
  - 02_mcp_agent_stdio_server.py
  - 03_mcp_agent_sse_server.py
  - 04_mcp_agent_local_server_files.py
  ...

代码说明

  • #1 获取当前脚本所在目录。
  • #2 使用异步上下文管理器初始化 MCP Server。
  • #3 使用 npx 在本地启动 Filesystem MCP Server。
  • #4 更新 Agent 的提示词,使其使用新的文件系统工具。
  • #5 输出当前脚本目录下的所有文件。

通过引入 MCP Filesystem Server,我们赋予了 Agent 对脚本所在目录的访问能力。这意味着 Agent 可以执行如下操作:

  • 列出文件
  • 打开文件
  • 读取文件
  • 写入文件
  • 删除文件
  • 创建文件

因此,在实际使用时,应尽量将 Filesystem Server 限制在一个隔离、安全的目录中,避免 Agent 对重要文件产生影响。

一般来说,无论使用哪种 MCP Server,都应首先验证其功能和行为,确保了解它能够代表 Agent 执行哪些操作。Agent 虽然能够带来很大的便利,但它更像一个天真且执行力极强的助手——它理解的“帮助”并不一定符合你的真实意图,因此必须谨慎授予权限。

幸运的是,有多种方式可以降低这些风险,其中一种有效的方法就是自行开发 MCP Server。自己编写 MCP Server 不仅可以访问官方服务器无法覆盖的资源,还能够:

  • 精确控制 Agent 能访问哪些资源;
  • 限制 Agent 可以调用哪些工具;
  • 自定义权限、安全策略和业务逻辑。

正如下一节将介绍的,自建 MCP 服务能够让你真正掌控 Agent 的行为和能力。

3.4 为 Agent 构建 MCP Server

在本章的最后一节,我们将通过一个示例,将一个使用本地工具的 Agent 改造成使用 MCP Server 提供相同工具的 Agent。这需要我们把原本定义在 Agent 内部的工具复制到另一个 Python 文件中,并使用 MCP 将它们封装起来。

图 3.14 展示了一个时间旅行记录Agent。它的职责是在时间旅行过程中记录每一次事件,并在用户请求时返回整个旅行日志的总结。

本书插图

图 3.14 时间记录 Agent 在循环处理每一次时间旅行事件时,会调用 record_event 工具记录事件。循环结束后,当用户要求生成总结时,它会调用 load_journal 工具读取完整日志,并生成最终摘要。

从图中可以看到,Agent 在循环运行过程中,每一次都会捕获一条时间旅行事件,并调用自身代码中的工具进行记录。这里使用了两个工具:

  • record_event:记录一条时间旅行事件。
  • load_journal:读取日志中的所有事件。

所有事件记录完成后,Agent 再次运行,根据用户请求调用load_journal,读取所有历史记录,并生成最终总结。

下面的代码展示了这个 Agent 及其工具实现。

清单 3.8 05_time_travel_agent.py
_journal = []    #1

@function_tool
def record_event(entry: str) -> dict:    #2
   """Add a new travel event to the journal."""
   _journal.append(entry)
   print(f"Event recorded: {entry}")
   return {"status": "recorded", "entry": entry}

@function_tool
def load_journal() -> dict:    #3
   """Load the current travel journal entries."""
   print("Loading journal entries...")
   return {"status": "loaded", "journal": "\n".join(_journal)}

agent = Agent(
   name="Time Tracker Agent",
   instructions="""You are a time tracking journaling agent.
Always use the 'load_journal' tool at the start to get past entries.
For a new event, call 'record_event' to save it.
If asked for a summary or to show the journal, output all recorded events.""",
   tools=[record_event, load_journal],    #4
)

# Simulate a series of historical travel events
travel_events = [
   "Traveled to Ancient Rome and watched a gladiator fight",
   "Visited the signing of the Declaration of Independence in 1776",
   "Witnessed the moon landing in 1969",
]

async def main():
   print("Recording travels:")
   for event in travel_events:    #5
       await Runner.run(agent, event)

   # Ask the agent to summarize the adventures
   result = await Runner.run(agent, "Show my travel history")
   print("\nFinal Journal:")
   print(result.final_output)

asyncio.run(main())

代码说明

  • #1 日志只是一个保存在内存中的列表。
  • #2 用于记录事件的工具函数。
  • #3 用于读取全部事件的工具函数。
  • #4 将两个工具注册给 Agent。
  • #5 通过循环模拟用户连续输入多条事件。

这个示例虽然是人为构造的,但它展示了 Agent 中一个非常常见的概念——临时记忆,也称为Scratchpad Memory(草稿板)。很多 Agent 在执行过程中都会把中间结果写入:

  • Journal(日志)
  • Scratchpad(草稿板)
  • Memory(记忆,详见第 6 章)

方便自己或者其他 Agent 在后续再次读取。需要注意的是:循环中的每一次 Runner.run() 都是一次全新的 Agent 调用。也就是说,默认情况下,每次运行时 Agent 并不会记住之前发生过什么。如果没有 Journal 这样的存储工具,Agent 根本不知道自己前面执行过哪些操作。正是 record_eventload_journal 这两个工具,为 Agent 提供了这种跨多次调用共享信息的能力。

一个值得注意的实现细节:在阅读代码之前,需要特别说明一点:本例中的 Journal 使用的是一个模块级的内存列表_journal = []。对于当前示例来说,这没有任何问题,因为这里只有一个 Agent和一个 Python 进程。但是,在 3.4.1 节中,我们会把这套代码迁移到 MCP Server 中,并允许多个 Agent通过 SSE 同时连接这个服务器。这时,同样的 _journal 就会变成:所有连接到该 MCP Server 的 Agent 共享的一份状态。也就是说:Agent A 写入的数据,Agent B 也能读取。这已经完全是另一种架构,其一致性、隔离性和正确性都会发生变化。作者将在 3.4.2 节进一步讨论这些问题。因此,目前阅读代码时,应把 _journal 看作:一个为了演示单 Agent 工作流程而刻意采用的简单实现,而不是多 Agent 场景下推荐的设计。

提示 很多时候,Agent 需要处理大量信息。为了避免上下文越来越长,最终超过模型的上下文窗口,我们通常会让 Agent 将中间数据写入某种存储中,然后在需要时再进行总结。这种存储可以是:Scratchpad(草稿板)、Journal(日志)或Memory(长期记忆)。Agent 之后只需要读取这些存储,并总结其中的重要事件或关键信息,而不必一直把所有内容放在上下文里,从而有效避免 Context Overflow(上下文溢出)。

后续章节中,我们还会在推理与规划以及记忆与知识部分看到更多关于 Scratchpad 的应用。由于这种模式非常常见,因此我们可以考虑把这些工具封装成一个 MCP Server。下一节将介绍如何把这些工具迁移为 MCP Server,并让 Agent 通过 MCP 来调用它们。

3.4.1 将工具转换为 MCP Server

MCP SDK 提供了一种非常简单的方法,可以将 Agent 的内部工具转换为由 MCP Server 托管的工具。这样做不仅能够将 Agent 逻辑与工具实现解耦,还可以让这些工具方便地被多个 Agent 复用。图 3.15 展示了改造后的时间旅行记录 Agent。此时,Journal 工具已经不再存在于 Agent 内部,而是部署在独立的 MCP Server 中,由 Agent 通过 MCP 调用。

本书插图

图 3.15 将工具从 Agent 中分离出来,封装为独立的 MCP Server。该 Server 可以部署在本地,也可以部署到远程,通过 STDIO(本地)或 SSE(远程)提供访问。现在 Agent 不再注册单独的工具,而是注册整个 MCP Server,再由 MCP 自动发现 Server 提供的工具及其使用方式。

实际上,我们只需要把代码拆分成两个文件:

  • 一个负责实现 MCP Server 和工具;
  • 一个负责实现 Agent。

下面的代码展示了如何把之前的 Journal 工具迁移到一个独立的 MCP Server 中,该 Server 可以部署在本地,也可以部署到远程。

清单 3.9 06_mcp_time_travel_tracker.py
from mcp.server.fastmcp import FastMCP

mcp = FastMCP("Time Travel Tracker")

_journal = []    #1

@mcp.tool()    #2
def record_event(entry: str) -> dict:
   """Add a new travel event to the journal."""
   _journal.append(entry)
   print(f"Event recorded: {entry}")
   return {"status": "recorded", "entry": entry}

@mcp.tool()    #2
def load_journal() -> dict:
   """Load the current travel journal entries."""
   print("Loading journal entries...")
   return {"status": "loaded", "journal": "\n".join(_journal)}

if __name__ == "__main__":
   mcp.run(transport="sse")    #3

代码说明

  • #1 Journal 仍然是一个保存在内存中的列表,但以后可以替换为数据库、Redis 等任意存储。
  • #2 将原来的 @function_tool 装饰器替换为 @mcp.tool()
  • #3 独立运行该文件时,会启动一个使用 SSE 的 HTTP MCP Server。

代码本身并没有太多变化。之前编写的工具只是移动到了新的 Python 文件中,并使用 MCP 的装饰器进行了包装。之后:

  • 工具发现
  • 工具通信
  • 工具调用

全部由 MCP 自动完成。

看似复制粘贴,实际上完成了一次架构升级。表面上看,我们只是把代码复制到了另一个文件。实际上,这代表着整个系统架构发生了变化。改造之前,Agent 与 Journal 工具位于同一个 Python Module,运行在同一个进程,共用同一份实现。Agent 可以直接访问_journal工具,如果愿意,也可以直接访问 Agent 内部状态。两者高度耦合。改造之后,Agent 与工具之间唯一的通信方式就是 MCP 协议。Agent 能看到的只有:

  • Tool 名称
  • Tool 描述
  • 参数 Schema
  • 返回值类型

Agent 完全不知道工具是如何实现的,也无法访问其内部实现。例如,Journal 的底层实现可以是:

  • Python List
  • PostgreSQL
  • Redis
  • 远程 API

无论如何变化,Agent 的代码都完全不用修改。这正是 MCP 带来的关注点分离。对于 Agent 来说,真正需要依赖的是 Tool Schema,而不是工具内部如何实现。这种解耦带来了很多好处:

  • 工具可以被多个 Agent 组合使用
  • 不同部署环境可以替换不同实现
  • 不同团队可以共享同一套 MCP Server

下一节,我们就会分别通过STDIO(本地)和 SSE(远程)来消费这个 MCP Server。

3.4.2 本地或远程消费 MCP Server

要使用这个新的 MCP Server,有两种方式:

  1. STDIO

    • 本地子进程
    • Agent 自己启动并管理 Server
  2. SSE

    • Server 独立运行
    • Agent 通过 HTTP 连接

虽然两种方式底层实现不同,但 MCP 已经屏蔽了大部分细节,代码上的区别非常小。

清单 3.10 展示了 Agent 如何注册并使用这个 Journal MCP Server。

清单 3.10 06_time_travel_agent_mcp_stdio.py(节选)
async with MCPServerStdio(    #1
   name="Time Tracker Server",
   params=MCPServerStdioParams(    #2
       command="mcp",
       args=["run", str(SCRIPT)],
   ),
) as time_tracker_server:
   agent = Agent(
       name="Assistant",
       instructions="""
You are a time-travel journaling agent.
Always use the 'load_journal' tool at the start to get past entries.
For a new event, call 'record_event' to save it.
If asked for a summary or to show the journal, output all recorded events.
       """,
       mcp_servers=[time_tracker_server],    #3
   )

代码说明

  • #1 创建一个 STDIO MCP Server 包装器,用于代理工具调用。
  • #2 因为 Server 在本地运行,所以通过子进程启动对应 Python 文件。
  • #3 Agent 注册的是 MCP Server,而不是一个个工具。

运行时:整个 Server 会被包裹在 with 上下文中。随后,将 Server 注册给 Agent。Agent 会自动完成工具发现、工具调用和工具参数生成。如果运行该程序,最终输出与清单 3.8 基本一致。不过,由于 MCP Server 是独立进程,因此终端中不会显示 Server 内部的日志输出。

另一种方式,是让 MCP Server 运行在完全独立的进程中。它可以在本机运行或在另一台服务器运行,也可以在云端运行。

Agent 通过 SSE 连接即可。下面展示对应代码。

清单 3.11 06_time_travel_agent_mcp_sse.py(节选)

async with MCPServerSse(    #1
   name="Time Tracker Server",
   params={
       "url": "http://localhost:8000/sse",    #2
   },
) as time_tracker_server:
   agent = Agent(
       name="Assistant",
       instructions="""
You are a time-travel journaling agent.
Always use the 'load_journal' tool at the start to get past entries.
For a new event, call 'record_event' to save it.
If asked for a summary or to show the journal, output all recorded events.
       """,
       mcp_servers=[time_tracker_server],    #3
   )

代码说明

  • #1 使用 MCP 的 SSE 包装器建立连接。
  • #2 通过 HTTP + SSE 连接 MCP Server,因此 Server 可以部署在本地,也可以部署在远程。
  • #3 注册 MCP Server,Agent 自动完成工具发现和调用。

运行该示例之前,需要先打开一个新的终端,启动清单 3.9 中实现的 MCP Server,例如:

python 06_time_travel_agent_mcp_sse.py

之后,再运行 Agent 程序即可。

无论采用 STDIO 还是 SSE ,最终得到的结果基本一致。但是,如果多个 Agent 同时连接同一个 SSE MCP Server,可能会发现 Journal 中出现重复内容。原因在于,STDIO 模式下:

每个 Agent 都会启动一个新的 MCP Server 进程。

而 SSE 模式下:

所有 Agent 都连接到同一个 Server。

因此,Server 中维护的_journal会变成共享状态。多个 Agent 都可能写入同一个 Journal和读取同一个 Journal,甚至同一个 Agent 多次运行,也可能读取到之前留下的数据。

可以看到,MCP 不仅让工具迁移到 Server 变得非常简单,还带来了许多额外优势:

  • 代码解耦
  • 代码复用
  • 实现隔离
  • 统一部署

更重要的是,它改变了我们组织工具的方式。过去,我们更多考虑:

一个 Agent 有哪些 Tool?

现在,我们更倾向于思考:

一个 MCP Server 提供哪一组相关的 Tool?

这种按能力组织工具的方式,更适合大型 Agent 系统,也方便多个 Agent 或不同 LLM 应用共享同一套能力。

3.5 练习

练习 1:启动并检查你的第一个 MCP Server

目标: 完整体验一次 安装 → 运行 → 调试 的 MCP Server 流程。

任务:

  1. 01_claude_mcp_server.py 复制为 exercise1_run_mcp.py
  2. 在该目录下运行:
mcp run -t sse exercise1_run_mcp.py
  1. 打开第二个终端,启动 MCP Inspector:
mcp dev "$(pwd)/exercise1_run_mcp.py"
  1. 打开终端输出的网址(例如 http://127.0.0.1:6274)。
  2. 在 Tools 页面中,选择 get_research_sources → Run Tool,确认能够看到三个研究数据源。
  3. 完成后关闭两个终端。

预计耗时: 7 分钟

练习 2:通过 STDIO 在 Agent 中调用 MCP Server

目标: 在 Agent 内部启动一个 MCP Server,并使用 OpenAI Agents SDK 调用它。

任务:

  1. 将示例 3.4(02_mcp_agent_stdio_server.py)复制为 exercise2_agent_stdio.py
  2. 将 Agent 的提示词修改为:

List the available research sources.

(列出可用的研究数据源。)

  1. 运行脚本,确认控制台输出包含三个数据源。
  2. 修改 01_claude_mcp_server.py 中的 get_research_sources(),让它只返回两个数据源。
  3. 再次运行脚本,确认输出也变成两个数据源。

预计耗时: 10 分钟

练习 3:切换到 SSE,而无需修改 Agent 逻辑

目标: 证明 MCP 传输协议切换只需修改一行代码。

任务:

  1. exercise2_agent_stdio.py 复制为 exercise3_agent_sse.py
MCPServerStdio

替换为

MCPServerSse

并设置连接地址:

http://localhost:8000/sse
  1. 删除 commandargs 参数。
  2. 启动 SSE Server:
mcp run -t sse 01_claude_mcp_server.py
  1. 运行 exercise3_agent_sse.py,确认输出与练习 2 完全一致。

预计耗时: 8 分钟

练习 4:封装一个只允许列目录的 Filesystem Server

目标: 从 Filesystem MCP Server 中仅暴露一个工具。

任务:

  1. 将示例 3.7 复制为 exercise4_wrapper.py,并将 Server 名称改为 Mini FS。
  2. 删除所有 @mcp.tool,只保留用于列出目录内容的那个工具。
  3. 新建 exercise4_test_wrapper.py,使用 MCPServerStdio 连接该 Server,并调用 list_directory
  4. 运行测试程序,确认:
    • Server 中只暴露了一个 Tool;
    • 能正确返回目录中的文件名。

预计耗时: 12 分钟

练习 5:把一个 Agent 封装成可复用的 MCP Tool

目标: 将时间旅行记录 Agent 封装为 MCP Server,并由另一个 Agent 调用。

任务:

  1. 将示例 3.9 复制为 exercise5_tracker_server.py,运行它,使其通过 SSE 在 8000 端口提供服务。
  2. 基于示例 3.11 创建 exercise5_consumer_agent.py
  3. 将示例中的三条旅行事件替换成你自己的三条事件。
  4. 运行消费者脚本,确认控制台能够输出所有记录事件的汇总。
  5. 打开 OpenAI Traces,确认可以看到两次执行记录:
    • 外层 Assistant Agent;
    • MCP Server 中托管的 Time Tracker Agent。

预计耗时: 15 分钟

本章总结

  • MCP 就像是面向 LLM 和智能体的 USB-C——它是一套基于 JSON-RPC 2.0 的规范,用于消除工具、数据源,甚至其他智能体之间大量定制化的胶水代码。

  • MCP 通过为所有能力提供统一接口,解决了生态碎片化(多种工具 Schema)、脆弱的数据访问方式、临时拼凑的编排逻辑以及安全性不一致等问题。

  • MCP 支持三类核心组件:工具(Tools,动作)、资源(Resources,数据/对象)和提示(Prompts,可复用模板)。智能体可以将它们都视为可调用的能力。

  • MCP 架构由三个部分组成:MCP Client、MCP Server,以及其背后的服务/资源。智能体只是众多客户端类型中的一种。

  • STDIO —— 以子进程方式运行、零网络延迟、单调用方模式,非常适合本地开发。

  • SSE(Server-Sent Events)与 HTTP —— 支持多客户端访问,更适合云环境部署。两种模式之间的切换通常只需要替换构造函数即可完成。

  • MCP 不仅适用于工具/动作层,还可以支持其他功能层,包括推理与规划、知识与记忆、以及评估与反馈。

  • MCP 可以采用多种部署模式:本地、远程或混合。你可以根据需要自由组合,将敏感操作保留在本地,同时将高负载 API 部署到远端。

  • MCP Inspector 提供了对任意 MCP Server 的实时可视化查看能力,非常适合在将智能体接入之前,用于调试工具 Schema 和输出结果。

  • 官方提供了多个 MCP Reference Server,可用于直接使用或学习实现,包括 Filesystem、Brave Search、Google Calendar、GitHub 等,只需一条 npxmcp 命令即可安装运行。

  • 智能体本身也可以被封装成 MCP Server,从而将完整的推理流程暴露为一个可复用、强类型的工具。

  • 基于 Pydantic 的类型化输入/输出(I/O)能够贯穿整个调用链,从而消除多智能体协作过程中脆弱的字符串解析逻辑。

  • MCP 让智能体系统能够像 LEGO 积木一样进行组合:每个模块彼此隔离、易于测试,并且可以在不影响其他模块的情况下随时替换。

输入关键词,在全书中查找。

支持中文、英文与代码关键词 · 按 Esc 关闭