9.1 Client 的构造:按参数类型自动选传输 本节摘要:客户端主线开始。本节讲透 v2 第一类 最聪明的设计——按参数类型自动选传输。传服务端对象→内存传输;传 URL 字符串→流式 HTTP 传输;传传输对象→直用。这个「按类型分发」的设计,让你在不同部署形态间切换几乎零成本——业务代码(调用方法)不变,只改构造参数。读完本节,你能用三种方式构造 Client,并说清它如何自动选传输。 一、Client 的三种构造形态 v2 的 接受单个参数,根据参数类型自动决定用哪种传输: 这三种形态覆盖了所有常见场景,且业务代码完全一致——构造之后的调用方法( 、 等)对三种传输都一样。 二、形态一:服务端对象(内存传输) 最简单的形态——把 实例直接传给 : 这是第 1.3 节的骨架。
本节摘要:客户端主线开始。本节讲透 v2 第一类
Client最聪明的设计——按参数类型自动选传输。传服务端对象→内存传输;传 URL 字符串→流式 HTTP 传输;传传输对象→直用。这个「按类型分发」的设计,让你在不同部署形态间切换几乎零成本——业务代码(调用方法)不变,只改构造参数。读完本节,你能用三种方式构造 Client,并说清它如何自动选传输。
v2 的 Client 接受单个参数,根据参数类型自动决定用哪种传输:
from mcp import Client # 形态一:传服务端对象 → 内存传输 client = Client(my_mcp_server) # 形态二:传 URL 字符串 → 流式 HTTP 传输 client = Client("http://localhost:8000/mcp") # 形态三:传传输对象 → 直用该传输 client = Client(my_transport)
这三种形态覆盖了所有常见场景,且业务代码完全一致——构造之后的调用方法(call_tool、read_resource 等)对三种传输都一样。
最简单的形态——把 MCPServer 实例直接传给 Client:
from mcp import Client from mcp.server import MCPServer mcp = MCPServer("Demo") @mcp.tool() def add(a: int, b: int) -> int: return a + b # 内存连接 async with Client(mcp) as client: result = await client.call_tool("add", {"a": 1, "b": 2})
这是第 1.3 节的骨架。Client 看到 mcp 是一个 MCPServer 实例,自动用内存传输——直接函数调用,无序列化、无网络。
适用场景:测试、进程内嵌入。客户端与服务端在同一进程,无需起子进程或开服务。
最常用的生产形态——传一个 URL 字符串:
from mcp import Client # 连接远程的流式 HTTP 服务 async with Client("http://localhost:8000/mcp") as client: result = await client.call_tool("add", {"a": 1, "b": 2})
Client 看到参数是字符串,自动用流式 HTTP 传输——发 HTTP 请求到指定 URL。这是连接远程 MCP 服务端的标准方式。
适用场景:连接远程服务、生产部署。客户端与服务端在不同进程/机器,通过网络通信。
最灵活的形态——传一个预先配置好的传输对象:
from mcp import Client from mcp.client.stdio import stdio_client, StdioServerParameters # 配置 stdio 传输(连本地子进程服务) stdio_params = StdioServerParameters( command="python", args=["server.py"], env={"DATABASE_URL": "..."} ) # 用 stdio_client 上下文管理器拿到传输 async with stdio_client(stdio_params) as (read, write): async with Client(read, write) as client: # 把传输传给 Client result = await client.call_tool("add", {"a": 1, "b": 2})
这种形态用于需要精细控制传输的场景——如 stdio 要配命令/环境、SSE 要配特殊选项。你先用传输的上下文管理器拿到读写流,再传给 Client。
适用场景:stdio 连本地子进程、特殊传输配置、需要传输级控制。
把三种形态放一起对照,差异与适用场景清晰:
| 形态 | 参数 | 自动选的传输 | 适用场景 |
|---|---|---|---|
| 服务端对象 | MCPServer 实例 |
内存 | 测试、进程内嵌入 |
| URL 字符串 | "http://..." |
流式 HTTP | 连远程服务、生产 |
| 传输对象 | (read, write) 或传输 |
直用 | stdio、特殊配置 |
关键共性:构造之后的业务代码完全一致。无论用哪种形态,call_tool、read_resource 等方法的用法都一样。这是「按类型自动选传输」设计的核心价值——业务代码与部署形态解耦。
「按类型自动选传输」看似只是个语法糖,实际上带来重要的工程价值:
| 价值 | 说明 |
|---|---|
| 零成本切换部署 | 同一份业务代码,改构造参数就能切部署形态 |
| 降低心智负担 | 不用记住「连内存要用 X 类、连 HTTP 要用 Y 类」 |
| 统一 API | 一种 Client,三种传输,调用方法一致 |
| 便于测试 | 测试用内存(快)、生产用 HTTP(远),代码不变 |
举个例子:你写一个客户端脚本,开发期用内存连测试,生产改成 URL 连远程服务——只改一行构造参数,其余代码不动。
💡 技巧:这个设计让你能写「传输无关的客户端代码」。把构造参数提成变量/配置,业务代码只依赖
Client接口,这样部署形态变化时只改配置,不动业务逻辑。这是良好的客户端架构实践。
理解 Client 的一个关键:构造期与连接期分离。
client = Client(url) # 构造期:只选传输,不开连接 # 此时 client 还没连上 async with client as c: # 连接期:进入上下文,真正打开连接 # 此时才连上,可以调用方法 await c.call_tool(...)
构造期(Client(...))只决定「用哪种传输」,不实际打开连接。连接期(async with ... as ...)才真正打开连接、协商协议、交换能力声明。
这个分离的设计带来好处:构造不失败(不涉及网络),连接失败在 async with 时才暴露,异常处理更清晰。第 9.2 节会详讲连接生命周期。
async with 时打开。构造清楚了,下一节讲连接生命周期——async with 里发生了什么、协议版本怎么协商。