跳转至

第3章 MCP Client

学习目标

  • 掌握使用 mcp Python SDK 实现 Client 的完整流程
  • 理解 Client 与 Server 的连接建立、能力协商、消息路由
  • 能够使用 OpenAI Agent SDK 集成 MCP(HostedMCPTool / MCPServerStreamableHttp)
  • 掌握连接管理、工具发现、工具调用的工程实践
  • 构建一个能自动发现并调用 MCP 工具的 Agent

问题导入

第 2 章我们造好了"工具供给侧"——MCP Server。但工具只有被 Agent 调用才能产生价值,这就需要 MCP Client。一个直观的问题是:为什么不能让 Agent 直接读 Server 的代码来发现工具?因为 Server 可能跑在另一台机器上,可能是别人开源的,你根本看不到源码。Client 通过标准协议完成"发现—协商—调用",让 Agent 无需关心 Server 的实现细节。

更进一步,OpenAI Agent SDK 把 MCP Client 封装为 HostedMCPTool,让 Agent 把整个 MCP Server 当作"一个超级工具"来用——一次配置即可获得 Server 上所有工具的能力。本章我们从底层 Client API 讲到上层 Agent 集成,覆盖完整的 Client 侧工程。

核心概念

1. Client 的核心职责

MCP Client 在 Host 内部承担四件事:

  • 连接管理:与 Server 建立 stdio 或 HTTP 连接,维持会话。
  • 能力协商:握手阶段交换双方支持的协议版本与原语集合。
  • 原语发现:调用 list_tools / list_resources / list_prompts 获取 Server 暴露的能力。
  • 调用转发:把 Agent 的调用请求通过 call_tool 转发给 Server,并返回结果。

2. 底层 Client API:ClientSession

mcp.ClientSession 是协议层的客户端,直接对接 JSON-RPC 消息。它需要配合一个传输层(stdio 或 HTTP)使用。

from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client

params = StdioServerParameters(command="python", args=["server.py"])
async with stdio_client(params) as (read, write):
    async with ClientSession(read, write) as session:
        await session.initialize()  # 握手
        tools = await session.list_tools()

stdio_client 负责拉起子进程并管理 stdio 管道,ClientSession 在其上建立协议会话。HTTP 模式则用 streamablehttp_client。

3. OpenAI Agent SDK 集成:HostedMCPTool

OpenAI Agent SDK(agents 包)提供了更上层的封装。HostedMCPTool 把一个 MCP Server 包装成一个 Agent 可用的工具集,自动完成发现与转发。

from agents import Agent, Runner
from agents.mcp import HostedMCPTool, MCPServerStreamableHttp

# 连接一个 HTTP 模式的 MCP Server
mcp_server = MCPServerStreamableHttp(
    params={"url": "http://localhost:8000/mcp"},
    cache_tools_list=True,
)
await mcp_server.connect()

# 把整个 Server 转成 Agent 可用的工具集合
mcp_tool = HostedMCPTool(server=mcp_server)
agent = Agent(name="助手", model="gpt-4.1", tools=[mcp_tool])

HostedMCPTool 的优势是:Agent 不需要逐个声明工具,SDK 在运行时自动从 Server 拉取工具列表并暴露给模型。

4. MCPServerStdio 与 MCPServerStreamableHttp

Agent SDK 提供两个 Server 连接器:

连接器 适用 特点
MCPServerStdio 本地 Server 拉起子进程,低延迟
MCPServerStreamableHttp 远程 Server HTTP 连接,可鉴权

二者都继承自 MCPServer,对外接口一致,便于切换。

5. 连接生命周期管理

MCP Client 是有状态的(需维持会话),因此生命周期管理很关键:

  • 连接:connect(),建立底层传输并完成握手。
  • 复用:一个 Server 连接可被多个 Agent 共享,避免重复握手。
  • 断开:cleanup() 或退出 async with 上下文,释放资源。
  • 重连:HTTP 模式下网络抖动需重连,建议用指数退避。

cache_tools_list=True 可缓存工具列表,避免每次对话都重新发现,但 Server 更新工具后需手动 invalidate。

原理图

sequenceDiagram
    participant Agent as Agent(Host 内)
    participant Tool as HostedMCPTool
    participant Conn as MCPServerStreamableHttp
    participant Server as MCP Server(远程)

    Agent->>Tool: 配置工具
    Tool->>Conn: connect()
    Conn->>Server: initialize(握手/能力协商)
    Server-->>Conn: 协议版本 + capabilities
    Conn->>Server: list_tools()
    Server-->>Conn: [toolA, toolB, toolC]
    Tool-->>Agent: 暴露工具集

    Note over Agent,Server: 用户提问,模型决定调用 toolA
    Agent->>Tool: call_tool("toolA", args)
    Tool->>Conn: call_tool
    Conn->>Server: tools/call(JSON-RPC)
    Server-->>Conn: 结果内容
    Conn-->>Tool: CallToolResult
    Tool-->>Agent: 工具结果
    Agent->>Agent: 模型基于结果生成回复

图:Client-Server 交互全流程——握手、发现、调用、回填

示例代码

下面演示两种 Client 集成方式:底层 ClientSession 与上层 HostedMCPTool。

# mcp_client_demo.py
# pip install "mcp[cli]" openai-agents
import asyncio
from agents import Agent, Runner
from agents.mcp import MCPServerStreamableHttp, MCPServerStdio


# ---------- 方式一:底层 ClientSession 直接调用 ----------
async def raw_client_demo():
    from mcp import ClientSession
    from mcp.client.streamable_http import streamablehttp_client

    async with streamablehttp_client("http://localhost:8000/mcp") as (read, write, _):
        async with ClientSession(read, write) as session:
            await session.initialize()
            tools = await session.list_tools()
            print("发现工具:", [t.name for t in tools.tools])
            # 调用第一个工具
            result = await session.call_tool(
                tools.tools[0].name, {"city": "北京"}
            )
            print("调用结果:", result.content[0].text)


# ---------- 方式二:Agent SDK 集成 ----------
async def agent_demo():
    # 连接远程 HTTP Server
    http_server = MCPServerStreamableHttp(
        params={"url": "http://localhost:8000/mcp"},
        cache_tools_list=True,
    )
    await http_server.connect()

    # 也可同时连接本地 stdio Server
    stdio_server = MCPServerStdio(
        params={"command": "python", "args": ["enterprise_server.py"]}
    )
    await stdio_server.connect()

    agent = Agent(
        name="企业助手",
        instructions="你是企业内部助手,优先使用 MCP 工具回答问题。",
        model="gpt-4.1",
        mcp_servers=[http_server, stdio_server],  # 直接挂载多个 Server
    )

    result = await Runner.run(
        agent, "帮我查一下 cpu 指标,并创建一个标题为'CPU 告警'的工单"
    )
    print("Agent 回复:", result.final_output)

    # 清理连接
    await http_server.cleanup()
    await stdio_server.cleanup()


if __name__ == "__main__":
    # 选一种方式运行
    asyncio.run(agent_demo())

关键点说明:

  • mcp_servers 参数让 Agent 自动发现所有挂载 Server 的工具,无需手动转 HostedMCPTool。
  • 多个 Server 可同时挂载,工具名冲突时 SDK 会加前缀。
  • Runner.run 是异步的,内部会处理"模型决策→工具调用→结果回填→继续推理"的循环。

企业案例

案例 1:Agent 集成 MCP

某企业客服 Agent 需要查订单、查物流、创工单。过去这些能力散落在三个微服务,Agent 代码里硬编码了三套调用逻辑。改造后,三个微服务各自暴露 MCP Server,Agent 通过 mcp_servers 一次挂载,工具发现与调用全自动,新增"退款查询"能力时只需让财务团队上线一个 MCP Server,Agent 代码零改动。

案例 2:多工具聚合 Host

某研发效能平台构建了一个"超级 Host",同时连接 GitHub MCP Server、Jira MCP Server、内部监控 MCP Server。开发者用自然语言说"看看昨天的 PR 是否触发了告警",Host 自动从 GitHub 拉取 PR 列表,再交叉查询监控 Server 的告警记录,最后由模型汇总分析。

案例 3:连接池化

某高并发场景下,Agent 平台为每个用户会话创建一个 Agent,每个 Agent 都要连 MCP Server。为避免连接爆炸,平台实现了"连接池":多个 Agent 共享同一组 Client 连接,按 Server 实例复用,连接数从万级降到十级。

常见问题

Q1:HostedMCPTool 和直接用 mcp_servers 有什么区别? mcp_servers 是更便捷的写法,SDK 内部会自动创建 HostedMCPTool。如果你需要对工具做过滤、重命名等定制,应手动用 HostedMCPTool 包装。

Q2:工具名冲突怎么办? SDK 会用 Server 名作为前缀消歧,例如 github__search 与 jira__search。也可在 HostedMCPTool 中配置别名。

Q3:cache_tools_list 何时该开? Server 工具列表稳定不变时开启,减少每次对话的发现开销。若 Server 会动态增减工具,应关闭缓存或手动调用 invalidate_tools_list()。

Q4:HTTP Server 重连怎么做? SDK 内部有简单重试,但生产环境建议在应用层捕获连接异常,用指数退避重连,并在重连失败时降级(移除该 Server 的工具)。

Q5:一个 Agent 挂太多 Server 会怎样? 工具过多会导致模型选择困难、上下文膨胀、首 token 延迟增加。建议按任务场景动态加载 Server,或用"路由 Agent"先决定需要哪些工具。

面试题

1. MCP Client 的四大职责? 答:连接管理、能力协商、原语发现、调用转发。

2. ClientSession 与 HostedMCPTool 的关系? 答:ClientSession 是协议层客户端,直接操作 JSON-RPC;HostedMCPTool 是 Agent SDK 的上层封装,内部使用 Client 连接,把 Server 的工具暴露给 Agent 模型。

3. MCPServerStdio 与 MCPServerStreamableHttp 如何选? 答:本地工具用 Stdio(拉子进程、低延迟);远程或需鉴权的 Server 用 StreamableHttp。

4. 工具发现结果可以缓存吗?有什么风险? 答:可以,用 cache_tools_list=True。风险是 Server 增删工具后缓存过期,需手动 invalidate_tools_list() 或关闭缓存。

5. 多个 Agent 共享一个 Server 连接有什么好处? 答:减少连接数、降低握手开销、便于集中鉴权与监控。生产环境建议做连接池。

练习题

  1. 用 ClientSession 写一个脚本,连接第 2 章的 enterprise_server.py,打印出所有工具、资源、提示的列表。
  2. 用 Agent SDK 构建一个 Agent,同时挂载本地 stdio Server 和远程 HTTP Server,让模型回答"查 cpu 指标并读取 onboarding 文档"。
  3. 实现一个简单的连接池类,封装 MCPServerStreamableHttp 的 connect / cleanup,支持多个 Agent 复用同一连接。

小结

本章从底层 ClientSession 讲到上层 Agent SDK 集成,覆盖了 MCP Client 侧的连接、发现、调用全流程。核心要点:ClientSession 是协议层基础;HostedMCPTool 与 mcp_servers 是 Agent 集成的便捷方式;HTTP 模式需关注重连与连接复用;工具过多应分组加载。下一章我们将进入实战,调用真实的向量数据库、浏览器、文件系统等 MCP Server。