想象一个画面:你是一家公司的老板,Agent 是你请来的“智能参谋”,每天帮你做各种决策。可参谋自己不会查天气、算数据、看股价,他得靠外部机构——气象局、交易所、内部数据库。MCP Host 是参谋身边的秘书处,负责判断“这事该找谁”;而 MCP Client,就是秘书处桌上那台贴着标签的智能总机。老板一声令下,秘书处写好转办单,总机员的职责是:拨通正确的机构、把事情说清楚、把回复原样带回来。如果占线,他得按规矩等一等再拨;如果这个机构不熟,他甚至要先确认对方还营业、还认不认账。本文就把这台总机拆开看:它怎么和机构建立连接、怎么同时管好几条专线、拨不通怎么办、和秘书处怎么分工,以及不同品牌总机的差异。

岗位关联

学习 MCP Client,是你从“会调大模型”走向“能造真正自主 Agent”的关键一步:所有工具调用、多服务集成和可靠通信,最后都落在这薄薄的一层客户端上。

目录
  1. 可视化演示
  2. 角色定位
  3. 生命周期
  4. 多Server管理
  5. 工具调用
  6. 错误重试
  7. 与Host协作
  8. SDK对比
  9. AI 应用场景
  10. Python 演示
  11. 常见误区
  12. 面试题

可视化演示

本节配有交互动效(进入视口后自动加载,约 3KB)。

(Agent) MCP Client MCP Server MCP Client可视化:展示协议客户端生命周期和多Server连接管理 MCP Client可视化:展示协议客户端生命周期和多Server连接管理

角色定位

老板第一次把总机摆在桌上时,秘书处特意嘱咐:你只需要对总机说话,剩下的事不用管。这台总机的全部意义,就是充当老板与外部机构之间的“翻译和管道”。老板不必知道气象局的具体分机号,也不必纠结电话线怎么走,他只要对着总机说一句“我要北京明天的天气”,剩下的环节全部由这台机器负责衔接。

在 MCP 协议中,这个“管道”被具体化为 Client 对象。它与运行中的 MCP Server 一一对应,是协议通信的真正发起方。你可以把 MCP Client 理解为一个协议会话的代名词:它不负责思考“为什么调用”,也不负责决定“调用哪个工具”,它只负责让一次远程调用安全、准确、可追溯地完成。

MCP Client 的核心职责可以收敛为三件事:第一,能力发现,向 Server 发起 initialize 和 tools/list,弄清对方提供什么工具、每个工具接受什么参数;第二,工具调用,把 Agent 的意图封装成 tools/call 请求发出去,并检验返回结果;第三,状态维护,记录这个连接里有哪些工具、最近调用了什么、结果是否出错,这些状态是后续决策的上下文来源。

你会发现,接线员的工作从来不是替老板做决策——他负责查号码、拨电话、记本子。MCP Client 也是如此:它被设计得足够“笨”,只做协议层的事,把聪明留给 Host 和 Agent。

Cclient={Discover,Invoke,State}C_{\text{client}} = \{ \text{Discover}, \text{Invoke}, \text{State} \}
这个集合告诉你:MCP Client 的本质就是“发现、调用、记录”三件事的组合,再没有别的。
  1. 元组含义用集合符号把客户端职责收拢成三个可独立设计的功能域。
    {Discover,Invoke,State}\{ \text{Discover}, \text{Invoke}, \text{State} \}
  2. 能力发现Discover 指初始化握手和工具列表查询,是后续一切调用的前置动作。
    Discover:list tools⇒tool signatures\text{Discover}: \text{list tools} \Rightarrow \text{tool signatures}
  3. 状态维护State 指消息 ID、调用历史、上下文的保存,让连接具备“记忆”。
    State:(id,tool,args,result)\text{State}: (\text{id}, \text{tool}, \text{args}, \text{result})

生命周期

总机员每天上班有一套固定流程:先检查线路,报出自己的工号,和对方确认合作范围,确认无误后才会开始接活;下班时再挂断电话、合上记录本。这套“上班—上岗—干活—下班”的流程,在 MCP 协议里被称为 Client 与 Server 的连接生命周期。

MCP 协议中,Client 与 Server 的每次连接都遵循一套严格的状态序列。为什么不能省掉握手直接调用?就像你不能电话一接通就开始念订单而不先报身份一样,Server 需要知道“你是谁、协议版本是否兼容、你能处理哪些能力”,Client 也需要知道“你提供哪些能力、工具长什么样”。

技术细节上,初始化阶段 Client 发送 initialize 请求,带上协议版本、客户端能力和客户端信息;Server 回复协议版本、服务端能力和服务器信息。随后 Client 发送 notifications/initialized 通知,双方进入 ready 状态。运行期可以收发 tools/list、tools/call 等消息。最后是关闭阶段,Host 决定不再使用该连接,Client 清理本地会话状态和未完成请求。

就像下班前必须挂断电话、合上记录本,一次不完整的关闭会留下占着的线路。在 MCP 开发里,忘记关闭 Client 连接是最常见的资源泄漏根源之一,尤其在长驻服务中,每个未关闭的会话都会持续占用内存和网络句柄。

L=⟨init,ready,run,close⟩L = \langle \text{init}, \text{ready}, \text{run}, \text{close} \rangle
生命周期是一条有序的状态流水线,从初始化开始,到关闭结束,中间不允许跳步。
  1. 初始状态init 表示连接尚未建立,Client 准备发起握手,称为初始化。
    init→initializehandshake\text{init} \xrightarrow{\text{initialize}} \text{handshake}
  2. 就绪状态ready 表示握手完成、能力协商通过,双方可以交换业务消息。
    ready⇒capabilities agreed\text{ready} \Rightarrow \text{capabilities agreed}
  3. 运行状态run 是工具调用最活跃的阶段,所有 tools/call 都发生在这里。
    run:send/receive JSON-RPC\text{run}: \text{send/receive JSON-RPC}
  4. 关闭状态close 负责释放资源,把会话状态归档或丢弃。
    close⇒cleanup state\text{close} \Rightarrow \text{cleanup state}

多Server管理

桌上的电话不止一部,气象局、交易所、数据库各占一条专线。接线员心里有本通讯录,哪条线对应哪个机构,绝不能串线——不能把天气查询拨到交易所去。更关键的是,每条专线的通话记录是分开记的,不能混在一起。

一个 Agent 要提供有用能力,几乎总要连接多个 Server:一个做联网搜索、一个访问公司数据库、一个调用内部业务 API。有些初学者以为“连接一个 Server 就够了”,但真实场景中,一个工具调用动辄需要三四个外部服务协同。

工程上的标准做法是:每个 Server 连接都使用独立的 Client 会话,状态绝不混用。再用一个会话注册表管理这些会话,key 是 server 标识,value 是会话对象。调用前先查表拿到正确的会话,连接不可用时移除条目并允许重建。这样既保证了路由正确,也隔离了故障——某个 Server 挂了不会拖垮其他连接。

接线员最怕串线,MCP Client 管理多 Server 时最怕会话串扰。如果你在代码里让两个 Server 共用同一个 Client 对象,就会发现消息 ID 冲突、工具清单互相覆盖——这就像把两家机构的电话打到彼此的记录本上,场面会彻底失控。

f:S↦C,C={(Si,Ci)∣i=1,2,…,N}f: S \mapsto C, \quad C = \{ (S_i, C_i) \mid i = 1, 2, \dots, N \}
这是多 Server 管理的数学表达:一个 Server 标识映射到一个连接实例,库里存着所有这样的映射对。
  1. 映射函数f 表示从 Server 标识到 Client 会话的对应关系,是路由的核心。
    f:S↦Cf: S \mapsto C
  2. 映射对集合元组 (S_i, C_i) 表示第 i 个 Server 与其专属会话的成对关系。
    (Si,Ci)(S_i, C_i)
  3. 下标范围i 从 1 到 N,说明系统支持任意多个 Server,只需新增映射即可扩展。
    i=1,2,…,Ni = 1, 2, \dots, N

工具调用

老板说“我要北京天气”,秘书处写一张转办单,总机员把它翻译成标准格式报给对方,对方回复后再原样带回来。这里有一个细节:转办单上必须有一个编号,机构的回复上也要带同一个编号,总机员才能确认这是那张单子的回执。

MCP 协议里的“标准格式”就是 JSON-RPC 2.0 消息。一次完整的工具调用分四步:工具发现、参数构建、发送请求、校验响应。Client 先通过 tools/list 拿到工具清单,包括工具名称和参数 Schema;Agent 侧基于 Schema 补全参数后,Client 构造 tools/call 请求,包含唯一 id、方法名和 params;Server 返回带同一 id 的响应。

Client 在收到响应时要做的第一件事不是解析业务数据,而是校验 id 是否与某个未完成请求匹配。这个匹配机制决定了并发能力:多个工具调用可以同时在空中飞行,谁回来就发给谁。响应还带 isError 标记,即使是业务错误也会以正常消息返回,Client 只需如实上报给 Host。

总机员拿着编号去对账,Client 拿着 id 去对消息。如果你在封装 SDK 时忽略 id 配对,就会出现响应错乱、超时误判——就像把张三的订单回执交给了李四,客户体验会瞬间崩塌。

Request=⟨id,m,p⟩,Response=⟨id,r⟩,idreq=idres\text{Request} = \langle id, m, p \rangle, \quad \text{Response} = \langle id, r \rangle, \quad id_{\text{req}} = id_{\text{res}}
请求和响应都带同一个 id,这是 Client 维持秩序、支持并发的基础。
  1. 请求结构请求由请求 ID、方法名和参数三部分组成,m 通常为 tools/call。
    Request=⟨id,m,p⟩\text{Request} = \langle id, m, p \rangle
  2. 响应结构响应由请求 ID 和结果组成,r 可以是业务数据或错误信息。
    Response=⟨id,r⟩\text{Response} = \langle id, r \rangle
  3. 配对条件只有请求 ID 等于响应 ID,这次调用才算对齐,否则视为无关消息。
    idreq=idresid_{\text{req}} = id_{\text{res}}

错误重试

也有拨不通的时候:占线、无人接、说到一半断线。老练的接线员从不立刻重拨,也不无限重拨。他会先看是哪种失败——是号码不存在(协议错),还是对方忙(资源紧张),还是对方接了但信号断了(连接中断),然后按场景决定要不要重试。

MCP Client 同样要把错误分成三类:连接类错误(握手失败、连接中断)、协议类错误(非法 JSON-RPC、超时)、业务类错误(工具执行失败返回 error)。处理手段包括:给建连和读写设置超时时间;失败后只重试有限次数;每次重试等待时间按指数规律增长,避免把服务打爆。

更 subtle 的一点是幂等。像“转账”“创建订单”这类有副作用的操作,重放时必须保证只执行一次。办法是复用请求 id 或业务 token,Server 端识别到同一 id 就返回上次结果,而不是再执行一遍。MCP 的 JSON-RPC 结构中,请求 id 天然适合承担这个角色。

重拨的智慧在于:等得够久,又不至于让机构被电话打爆。没有退避的重试是“雪崩发生器”——一旦 Server 出现过载,所有 Client 同时立刻重试,瞬间把 Server 彻底压垮;有了指数退避,大家自动错开节奏,Server 才有机会缓过来。

delay(n)=min⁡(D0×2n−1,Dmax⁡)\text{delay}(n) = \min(D_0 \times 2^{n-1}, D_{\max})
第 n 次重试前等待的时间等于基础延迟乘以 2 的 n-1 次方,但设一个上限,防止无限期等待。
  1. 基础延迟D_0 是第一次重试前的基准等待时间,比如 0.2 秒。
    D0D_0
  2. 指数增长每次失败后等待时间翻倍,2 的 n-1 次方让重试节奏越来越慢。
    2n−12^{n-1}
  3. 封顶保护min 函数把等待时间限制在 D_max 内,避免最坏情况下等上几年。
    min⁡(⋅,Dmax⁡)\min(\cdot, D_{\max})

与Host协作

秘书处和总机员各司其职:前者决定找谁、问什么,后者负责打通线路、原样传话。秘书处不会冲到机房里去拨号,总机员也绝不会替老板拍板“这事不用问了”——一旦越权,整个办公秩序就乱了。

在 MCP 架构里,Host 与 Client 的关系正是这种分工。Host 是 Agent 应用的核心容器,掌握着任务编排、模型调度和用户交互;Client 只是 Host 手下一个“外卖骑手”,负责把任务送到 Server 再取回来。两者通过回调协作,职责边界非常清晰。

技术实现上,Host 负责高层编排:决定调用哪个工具、什么时候创建新连接、收到结果后下一步做什么;Client 负责协议细节:initialize 握手、消息编码解码、重试策略、id 配对,并通过回调把结果和通知交还给 Host。Host 依赖 Client 的稳定传输,Client 不感知 Host 的业务意图。

总机如果擅自替老板回答或者替秘书处做决定,那就越权了。MCP 里的 Client 如果试图介入业务编排,就会让职责混乱、协议层与应用层耦合,最终难以测试和维护。记住这句话:Host 是大脑,Client 是手脚。

A→H→C→S→C→H→AA \to H \to C \to S \to C \to H \to A
一次工具调用在四层之间走一个往返:意图从 Agent 出发,依次经过 Host、Client 抵达 Server,结果再原路返回。
  1. 意图出发A 到 H,Agent 把“我想知道天气”的意图交给 Host 编排。
    A→HA \to H
  2. 选中工具H 到 C,Host 决定调用哪个工具,把调用指令交给 Client。
    H→CH \to C
  3. 远程执行C 到 S,Client 将请求封装成协议消息发给远端 Server 执行。
    C→SC \to S
  4. 结果返回S 到 C、C 到 H、H 到 A,结果沿原链路逐层回传,最终交还 Agent。
    S→C→H→AS \to C \to H \to A

SDK对比

市面上的总机品牌众多,功能都能打通电话,但操作台布局、配套培训、扩容方案各不相同。有的总机按键少、易上手,适合初创团队;有的总机扩展槽多、支持并发线路,适合大企业;选型从来不是“哪个最好”,而是“哪个最适合你的团队”。

MCP 生态同样如此,官方 SDK 已经覆盖 Python、TypeScript、Java/Kotlin、Ruby、C# 等主流语言,还有社区维护的 Go、Rust 等实现。它们遵循同一份协议规范,所以 Server 侧互操作没有问题,差异集中在开发体验和运行特性上。

选型时主要看三个维度:传输层支持(是否同时支持 stdio、HTTP+SSE、Streamable HTTP);异步模型(Python 的 asyncio、TypeScript 的回调与响应式流、Java 的阻塞与响应式);能力完善度(工具发现、自动重连、对 notifications 和采样的支持)。AI Agent 原型常用 Python SDK,Web 集成偏爱 TypeScript,企业级系统倾向 Java。

选总机不是越贵越好,而是看秘书处的人会不会用、设备稳不稳。选 MCP SDK 也不是越流行越好:如果你的 Agent 跑在 Node 服务里,硬塞一个 Python SDK 就非常别扭;如果你的团队全是 Java 工程师,为了快速原型引入不熟悉的语言栈,后续维护成本会远高于节省的那点开发时间。

Score(SDK)=w1E+w2T+w3P\text{Score}(\text{SDK}) = w_1 E + w_2 T + w_3 P
选型评分是三个因素的加权和:生态成熟度、类型与异步模型、性能表现,权重由团队自身情况决定。
  1. 生态成熟度E 代表文档质量、社区活跃度和版本稳定性。
    EE
  2. 类型与异步T 代表类型系统的严格程度和异步模型的契合度。
    TT
  3. 性能表现P 代表吞吐、延迟和资源占用等可量化指标。
    PP

AI 应用场景

智能客服 Agent 接入多业务系统

阶段:工具调用与多 Server 连接管理

解决的问题:传统客服系统每接入一个数据源(订单库、CRM、物流平台)就要写一套对接代码,业务流程写死,后期加一个查询维度就要改代码。用 MCP Client 管理多个 Server 会话后,每个业务系统都变成标准 MCP Server,Agent 通过统一工具接口查询,新增系统只需添加一条 Server 注册记录。

具体案例:某电商客服 Agent 同时连接订单管理系统、物流查询服务和退款接口三个 MCP Server。Host 根据用户问题编排调用链:先查订单、再查物流、必要时发起退款申请。每个 Server 有独立 Client 会话,请求 id 不冲突,任一系统故障不影响其他查询。

数据分析 Agent 的容错查询

阶段:错误重试与超时控制

解决的问题:数据分析 Agent 经常要调用外部 BI 服务的慢查询接口,回应时间不稳定,传统同步调用遇到超时就直接报错,用户体验差。MCP Client 内置指数退避重试逻辑,配合超时控制,把不稳定服务变成可用服务。

具体案例:某数据分析工具用 Python MCP SDK 连接内部 BI Server,执行 SQL 类工具调用。当 BI 服务负载高时,Client 自动按 0.2s、0.4s、0.8s 间隔重试,最多 3 次;对于聚合统计这类幂等操作,复用请求 id 防止重复计算导致数据翻倍。

代码助手 Agent 的本地工具链

阶段:SDK 选型与生命周期管理

解决的问题:IDE 里的代码助手 Agent 需要调用本地文件检索、git 状态查询等功能。通过 stdio 传输与本地 MCP Server 通信最安全,不需要开网络端口。生命周期管理在这里很关键:编辑会话结束就要主动关闭 Client 连接,避免长驻服务内存泄漏。

具体案例:某 IDE 插件用 TypeScript MCP SDK 创建 Client 连接本地代码分析进程。初始化时握手协商版本,运行期执行 grep 文件工具,用户关闭编辑器时触发优雅关闭:先结束消息处理,再断开传输,最后清空会话状态。

Python 演示

python107 行
import time  class MCPSession:    def __init__(self, server_id, endpoint):        self.server_id = server_id        self.endpoint = endpoint        self.connected = False        self.tools = {}     def initialize(self, server_meta):        """握手:登记工具表,进入就绪状态。"""        if not isinstance(server_meta, dict) or "tools" not in server_meta:            raise ValueError("invalid server metadata")        self.tools = server_meta["tools"]        self.connected = True        return {"status": "ok", "tools": sorted(self.tools)}     def call_tool(self, name, args):        """执行工具调用,失败时抛出异常。"""        if not self.connected:            raise ConnectionError("session not initialized")        if name not in self.tools:            raise KeyError("tool not found: " + name)        return self.tools[name](args)     def close(self):        """优雅关闭:清空会话状态。"""        self.connected = False        self.tools = {}  class MCPClientManager:    def __init__(self):        self.sessions = {}     def add_server(self, server_id, endpoint, server_meta):        """新增一个 Server 连接,并完成初始化握手。"""        session = MCPSession(server_id, endpoint)        result = session.initialize(server_meta)        self.sessions[server_id] = session        return result     def active_sessions(self):        """返回当前仍处于连接状态的会话列表。"""        return [sid for sid, s in self.sessions.items() if s.connected]     def call_with_retry(self, server_id, tool, args, max_retries=3, base_delay=0.2, max_delay=2.0):        """带指数退避的工具调用。"""        session = self.sessions.get(server_id)        if session is None:            raise KeyError("no such server: " + server_id)        last_exc = None        for attempt in range(max_retries + 1):            try:                return session.call_tool(tool, args)            except Exception as exc:                last_exc = exc                if attempt == max_retries:                    break                delay = min(base_delay * (2 ** attempt), max_delay)                time.sleep(delay)        raise last_exc     def close(self):        """关闭所有会话。"""        for session in self.sessions.values():            session.close()        self.sessions.clear()  def flaky_tool(args):    raise RuntimeError("service busy")  def main():    calc_meta = {        "tools": {            "add": lambda args: args["a"] + args["b"],            "mul": lambda args: args["a"] * args["b"],            "flaky": flaky_tool,        }    }    weather_meta = {        "tools": {            "query": lambda args: {"city": args["city"], "temp": 22},        }    }     manager = MCPClientManager()    print(manager.add_server("calc", "stdio://calc", calc_meta))    print(manager.add_server("weather", "http://weather", weather_meta))     print(manager.call_with_retry("calc", "add", {"a": 3, "b": 4}))    print(manager.call_with_retry("weather", "query", {"city": "Beijing"}))     try:        manager.call_with_retry("calc", "flaky", {}, max_retries=1, base_delay=0.01)    except RuntimeError as exc:        print("retry failed:", exc)     print("active:", manager.active_sessions())    manager.close()  if __name__ == "__main__":    main()
目的:演示 MCP Client 最核心的三件事——按注册表管理多 Server 连接、完成 initialize 握手、在失败时按指数退避重试工具调用。
  1. session.initialize(server_meta)模拟 MCP 初始化握手:登记工具表,把 connected 置为 True,对应现实中的 initialize 请求。
  2. self.sessions[server_id] = session用字典作为会话注册表,这就是文章里“多 Server 管理”的落点。
  3. session.call_tool(tool, args)根据名字动态分发到具体工具函数,对应 JSON-RPC 的 tools/call。
  4. delay = min(base_delay * (2 ** attempt), max_delay)指数退避公式的代码实现,等待时间按翻倍增长并封顶。
  5. manager.call_with_retry("calc", "add", {"a": 3, "b": 4})对外暴露的容错调用入口,失败会自动重试。
  6. manager.close()遍历所有会话执行优雅关闭,对应生命周期里的 close 阶段。

常见误区

误区我以为 MCP Client 是一个需要单独部署的微服务,就像启动一个 HTTP 服务器那样。实际上它只是一个 SDK 库里的对象,我找遍整个项目也没找到可以启动的入口。

正解MCP Client 通常以库对象的形式存在于 Host 进程中,由代码创建和销毁。你可以把它想成总机设备——它不是一间独立的机构,而是秘书处桌上的一台机器。它的生命周期依附于 Host,进程退出时所有连接也随之消亡。

为何常见:因为日常开发中习惯了“服务 = 独立进程 + 端口”的心智模型,遇到协议客户端时也自然往服务方向联想。

误区我觉得只要创建一个 MCP Client,然后把多个 Server 的地址都传进去,它自然能帮我维护好多条连接。这样代码最省事。

正解一个 Client 对象对应一条到特定 Server 的连接,它有独立的握手状态、能力协商结果和消息 ID 空间。多 Server 需要多个独立会话,再用注册表统一管理——就像一条电话线不能同时接通气象局和交易所,每打一个电话都是独立的线路。

为何常见:网络编程里常有“一个客户端类连多个服务端”的模式(比如数据库连接池),容易把这种经验套用到 MCP 上。

误区工具调用失败了,最自然的想法是马上再调一次。如果还失败就再调一次,总共试十次。结果服务反而更卡了。

正解没有退避的密集重试会让过载的 Server 雪上加霜。正确做法是有限次重试 + 指数退避 + 抖动,让等待间隔从 0.2s、0.4s、0.8s 逐渐拉长。接线员不会疯狂拨同一个占线号码,他会等对方缓过来再拨。

为何常见:人面对失败的第一反应是“再来一次”,线性思维让人忽略了分布式系统里的相互影响。

误区MCP 有三个角色:Host、Client、Server,我觉得 Host 就是 Client 的另一个名字,反正都是 Agent 侧的东西,写代码时混着用。

正解Host 是 Agent 应用的宿主环境,负责业务编排和生命周期决策;Client 是协议会话的实现者,负责消息收发和状态维护。用秘书处和总机的比喻来想:Host 决定打给谁,Client 负责把电话打通。混用会导致职责混乱、测试困难。

为何常见:在官方文档里 Host 和 Client 都出现在“Agent 侧”,初读时容易把整个 Agent 侧视为一个整体,忽略内部边界。

误区反正都是实现同一个协议,用 Python 或 TypeScript 写出来的 Client 应该完全等价,选一个顺手的就行。

正解协议消息是互通的,但 SDK 在传输层支持、异步模型、自动重连等能力上差异很大。比如某些 SDK 对 Streamable HTTP 支持还不完整,某些 SDK 的 stdio 传输更稳定。选型要看成色,而不是只看语言名。

为何常见:初学者容易把“协议统一”误解为“实现统一”,就像认为所有路由器都按同一标准造就能随便买一台,忽略了吞吐量和稳定性差异。

面试题

Q1. MCP Client 在 MCP 架构中最核心的职责是什么?

单选题 · easy

  • A.负责决策并编排整个任务流程
  • B.负责与远端 Server 建立协议会话并收发消息← 正确答案
  • C.负责存储大模型的权重与训练参数
  • D.负责渲染前端界面并处理用户点击

答案:B。MCP Client 的本质是协议通道,负责握手、消息发送和响应配对。决策编排是 Host 的职责,存储权重和渲染界面更是与 MCP 无关。就像接线员只负责接通和传话,不替老板决定该找谁。

Q2. MCP Client 与 Server 建立连接时,第一步发生的动作是?

单选题 · easy

  • A.客户端直接调用 tools/list 获取工具列表
  • B.客户端发送 initialize 请求进行握手← 正确答案
  • C.服务端主动推送工具到所有已注册客户端
  • D.客户端建立数据库连接并执行建表语句

答案:B。MCP 协议要求先发送 initialize 请求完成握手,确认协议版本和能力后再进入就绪状态。tools/list 必须等握手完成后才能使用,服务端也不会主动推送工具列表。就像打电话第一句要报身份,而不是直接开始念订单。

Q3. 下列哪个属于 MCP Client 运行期的消息处理职责?

单选题 · mid

  • A.向 Server 发送 tools/call 并校验响应 ID← 正确答案
  • B.训练一个新的工具选择模型
  • C.修改 Server 端工具的函数体实现
  • D.为 Agent 生成用户界面按钮文案

答案:A。运行期 Client 要做的事是构造请求、发送消息、按 ID 配对校验响应。训练模型是 Agent 侧的事,修改 Server 端代码和生成界面文案根本不发生在 Client 的职责范围内。总机员只负责传话和对账,不负责改对方答复内容。

Q4. 一个 Agent 需要连接多个 MCP Server,正确的做法包括?

多选题 · mid

  • A.为每个 Server 创建独立的 Client 会话实例← 正确答案
  • B.用会话注册表维护 Server 名称到会话的映射← 正确答案
  • C.让所有请求共享同一个会话并手动修改目标地址
  • D.隔离各会话状态和消息 ID 空间← 正确答案

答案:ABD。多 Server 管理的核心是“独立会话 + 注册表路由 + 状态隔离”。共享一个会话并手动改地址会导致消息 ID 冲突和工具清单混淆,是错误的做法。这就像一条电话线不能同时接通多个机构,每条线都要独立管理。

Q5. 重试机制中“指数退避”的核心思想是?

单选题 · mid

  • A.每次重试的等待时间按固定值递增
  • B.每次重试的等待时间按指数规律增大← 正确答案
  • C.所有重试都在同一次循环中立即完成
  • D.失败次数越多,重试间隔应当越短

答案:B。指数退避让等待时间按 0.2s、0.4s、0.8s 翻倍增长,既给服务恢复时间,又避免雪崩。固定值递增只是等距重试,立即完成和越挫越勇的间隔方向都与退避相反。这就像接线员发现占线后会等更久再拨,而不是越拨越勤。

Q6. 客户端重放一条 tools/call 请求时,如何避免 Server 端重复执行副作用?

单选题 · hard

  • A.使用不同的请求 ID 发送两条相同消息
  • B.复用原请求 ID 并配合 Server 端的幂等处理← 正确答案
  • C.直接关闭连接后重新初始化一个新会话
  • D.把请求方法从 tools/call 改成纯通知类型

答案:B。幂等重放的关键是让 Server 识别“这条消息我处理过”。复用原请求 ID,Server 就能返回上次结果而不重复执行。换新 ID 等于新请求,关闭连接和改成通知都解决不了重复执行问题。好比同一张转办单不能换编号再送一次。

Q7. 关于 Host 与 Client 的职责划分,正确的是?

多选题 · hard

  • A.Host 负责编排任务并决定调用哪些工具← 正确答案
  • B.Client 负责维护与 Server 的协议会话← 正确答案
  • C.Host 可以绕过 Client 直接与 Server 通信
  • D.Client 通过回调把结果交还给 Host← 正确答案

答案:ABD。Host 是决策层,Client 是传输层。Host 不能绕过 Client 直连 Server,否则就破坏了协议封装;Client 通过回调把结果和通知交给 Host,保持单向依赖。就像秘书处不能抢过电话线自己喊话,总机员也不能替秘书处做决定。

Q8. 不同语言 MCP SDK 的主要差异体现在?

单选题 · easy

  • A.异步模型与内置传输协议的支持度不同← 正确答案
  • B.协议消息格式完全不同导致无法互通
  • C.只有 Python SDK 能连接远程 HTTP Server
  • D.所有 SDK 的 API 形态完全一致

答案:A。SDK 差异集中在异步模型、传输层支持(stdio、HTTP+SSE、Streamable HTTP)和能力完善度。协议消息格式是统一的,否则无法互操作;API 形态也各不相同。就像不同品牌总机按键布局不同,但电话线标准是一致的。

Q9. MCP 协议中一次完整工具调用涉及哪些要素?

多选题 · mid

  • A.唯一的请求 ID 与工具名称和参数← 正确答案
  • B.请求与响应的 ID 配对校验← 正确答案
  • C.调用结果以及是否出错的错误标记← 正确答案
  • D.工具内部训练数据集的版本号

答案:ABC。工具调用三要素:请求结构(id、方法名、参数)、响应配对(同 id 返回)、结果标记(isError 区分业务错误)。训练数据集的版本号与协议调用无关。这就像一张转办单必须有编号、内容、回执,才能算一次完整通联。

Q10. 关闭 MCP Client 连接时的正确做法是?

单选题 · hard

  • A.直接让程序退出,靠操作系统回收资源
  • B.先结束会话消息再关闭传输并清理状态← 正确答案
  • C.只在连接池空闲时关闭否则会造成死锁
  • D.断网后立刻丢弃本地保存的全部历史记录

答案:B。优雅关闭要按顺序执行:停止接收新消息、关闭底层传输、清理本地会话状态和未完成请求。直接退出虽然进程结束会回收内存,但依赖宿主环境不优雅;MCP 没有“连接池空闲才关闭”的约束。下班前挂断电话、合上记录本,才算完整收尾。