想象你是一名刚到陌生城市的美食博主。你没有提前收藏任何餐厅,手机里只有一个外卖 App。你打开“附近商家”,整座城市的美食瞬间铺展在眼前:每家店叫什么、招牌菜是什么、怎么下单,一目了然。更妙的是,当新店开业或菜单调整时,你只需轻轻一拉刷新,列表立刻更新。你不需要把全城餐厅背下来——只要知道怎么问,App 就会告诉你此刻这里有什么。 这正是 AI 智能体(Agent)接入各种工具服务时应该有的姿态。过去的做法是“背菜单”:把每个功能的名称和用法写死在程序里,换一家店就得改代码。而 MCP 协议让 Agent 学会了“看附近商家”——它能在运行过程中主动询问服务端:“你现在提供了哪些工具?每个工具怎么用?”然后根据回答现场决定行动。这种本领,就是工具发现机制。

岗位关联

掌握 MCP 工具发现机制,是 Agent 开发者从“写死集成”走向“动态生态”的分水岭——你的 Agent 将能如鱼得水地接入任何新增的 Server,无需改一行代码。

目录
  1. 可视化演示
  2. 发现机制概述
  3. 工具列表请求
  4. 工具元数据
  5. 版本兼容
  6. 客户端缓存
  7. 同名工具冲突
  8. AI 应用场景
  9. Python 演示
  10. 常见误区
  11. 面试题

可视化演示

动态演示MCP工具发现的核心机制,通过总览/传输/转换/输出四种模式展示关键概念与交互流程。

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

MCP工具发现:MCP工 MCP工具发现机制总览与动态感 tools/list协议流程与 核心机制可视化 MCP工具发现整体架构与关键组件

发现机制概述

美食博主打开外卖 App 的“附近商家”,看到的不是一堵写满店名的墙,而是一份会呼吸、会生长的活菜单:新店开业会冒出来,售罄的菜品会消失。对 AI Agent 来说,MCP 的工具发现机制就是那枚“附近商家”按钮——它让 Agent 在运行时向 Server 发出询问:“你此刻提供了哪些工具?”而不是在出生前就把所有工具背下来。

在没有发现机制的传统集成方式里,开发者必须在代码里写死每个工具的调用方式:工具叫什么、需要传什么参数、返回什么格式。一旦服务端新增功能,客户端就得跟着改代码、发版、升级,像极了把整本电话簿背下来却只为了找一家餐厅。MCP 的发现机制把这种“静态契约”变成了“动态问答”:客户端随时可以重问,服务端随时能更新答案。

在协议层面,发现机制的核心是一条请求-响应消息流。Agent 与 Server 之间通过 JSON-RPC 格式对话,Agent 发出 tools/list,Server 返回一个工具数组。这个数组里的每个元素就是一件工具的完整“说明书”,包含工具的名称、用途和输入参数。整个过程发生在 Agent 完成初始化握手之后、首次决策调用哪个工具之前——就像一个旅人先站在街口扫一圈招牌,再决定迈进哪家店。

Agent→ tools/list Server,Server→ tools 数组响应 Agent\displaystyle \text{Agent} \xrightarrow{\ \text{tools/list}\ } \text{Server}, \quad \text{Server} \xrightarrow{\ \text{tools 数组响应}\ } \text{Agent}

1. 客户端发起请求:Agent 作为客户端,主动发出名为 tools/list 的方法请求,询问服务端可用工具。

method=tools/list\text{method} = \text{tools/list}

2. 服务端返回数组:Server 响应一个 tools 数组,数组里每一项是一份完整工具定义。

tools:[Tool1,Tool2,…]\text{tools}: [\text{Tool}_1, \text{Tool}_2, \ldots]

3. 一次往返即一次发现:请求与响应构成完整的发现过程,工具列表随服务端状态实时变化。

roundtrip=request+response\text{roundtrip} = \text{request} + \text{response}

像外卖 App 的列表永远不会忘记刷新一样,MCP 的发现机制让 Agent 始终握着最新鲜的“菜单”。硬编码的工具清单是博物馆里的展品,而发现机制拉取的工具列表,是窗明几净的今日菜单——Agent 的每一次行动,都从这一刻的“附近商家”开始。

工具列表请求

外卖 App 的“附近商家”背后,其实是一次次精心设计的网络请求。MCP 的 tools/list 就是那个请求协议:它是一则 JSON-RPC 2.0 消息,向服务端明确“把你的工具清单交给我”。工具特别多的时候,和外卖列表一样需要“滑动加载更多”——这就是分页。

tools/list 的请求体结构并不复杂:method 固定为 tools/list,params 里可以携带一个可选的 cursor 游标。游标是什么?它是上一页返回的“书签”:当清单太长、一次响应装不完时,服务端会返回一个 nextCursor,客户端带上它请求下一页,就像下拉刷新后继续滑动页面,直到看完所有商家。

响应体的核心是 tools 数组,每个元素是一个工具定义;如果还有更多工具未返回,响应中会带上 nextCursor。这种设计保证即使一个 Server 暴露了几百个工具,客户端也能分批拉完,不丢项、不卡顿,让 Agent 面对“挑花眼”的大列表依然从容。

Request={method:tools/list, params:{cursor?}};Response={tools:[Tool], nextCursor?}\displaystyle \text{Request} = \{\text{method}: \text{tools/list},\ \text{params}: \{\text{cursor}?\}\}; \quad \text{Response} = \{\text{tools}: [\text{Tool}],\ \text{nextCursor}?\}

1. 方法名路由:请求体必须写明 method 为 tools/list,服务端据此把消息路由到“枚举工具”的处理逻辑。

method: tools/list\text{method}: \ \text{tools/list}

2. 可选游标参数:params 里的 cursor 是上一次响应返回的翻页凭据,第一页请求时可以省略。

params={cursor?}\text{params} = \{\text{cursor}?\}

3. 工具数组响应:响应中的 tools 数组是发现成果的主体,每个元素都是一份完整工具元数据。

tools:[Tool,…]\text{tools}: [\text{Tool}, \ldots]

4. 游标续页:nextCursor 非空表示还有更多工具,客户端需要携带它继续请求,直到返回空。

nextCursor≠null⇒再次请求翻页\text{nextCursor} \neq \text{null} \Rightarrow \text{再次请求翻页}

点菜的流程因此变得有序:先点开“附近商家”,一页一页翻完整个工具宇宙,不漏掉任何一家藏在深处的宝藏小店。分页机制让 Agent 的“眼睛”再大也不会看花。

工具元数据

外卖商家列表里,每家店都挂着一块信息牌:店名告诉你它是谁,图文简介告诉你它值不值得吃,点单表单告诉你该怎么下单。MCP 的每个工具也有一块“信息牌”,由三个字段构成:name、description、inputSchema。

name 是工具在 Server 内的唯一标识,也是后续实际调用(tools/call)时指代这个工具的名字。description 是写给大模型看的“简介”——模型在几十个工具里做选择时,主要靠 description 判断哪个匹配当前任务。inputSchema 用 JSON Schema 描述参数结构:有哪些字段、哪些必填、每个字段什么类型。它相当于“点单表单”,Agent 依据它生成合法入参。

这三个字段缺一不可:名字写错,调用找不到工具;描述写糊,模型选错工具;参数模式写漏,调用直接出错。它们共同构成 Agent 对一件工具的完整认知:是什么、何时用、怎么用——这也是“工具发现”真正发现的东西。

Tool=⟨name, description, inputSchema⟩\displaystyle \text{Tool} = \langle \text{name},\ \text{description},\ \text{inputSchema} \rangle

1. name 唯一标识:工具名在 Server 作用域内唯一,调用时通过它路由到具体实现。

name∈String, Server 内唯一\text{name} \in \text{String}, \ \text{Server 内唯一}

2. description 语义匹配:用自然语言描述工具用途,是大模型做工具选择的依据。

description:说明使用场景与功能\text{description}: \text{说明使用场景与功能}

3. inputSchema 参数模板:以 JSON Schema 描述入参结构,Agent 据此生成合法参数。

inputSchema:JSON Schema 对象\text{inputSchema}: \text{JSON Schema 对象}

菜单写得好不好吃,直接决定美食博主会不会点这道菜——工具的“信息牌”质量,直接决定 Agent 选得对不对、调得顺不顺。给工具写 description,就像给招牌菜写诱人的介绍,多写一句“适合深夜加班的人”,就多一次被选中的机会。

版本兼容

老版本外卖 App 遇到新开业的餐厅,会出现什么情况?新店招牌上有“AR 实景选座”这类新玩法,老 App 里根本不显示这个入口,但基础的点餐功能照常能用。MCP 的版本兼容处理,就是这个道理的协议版。

MCP 会话开始时,双方先通过 initialize 消息握手:客户端和服务端各自报上支持的协议版本(protocolVersion)和能力集(capabilities)。能力集就像各自支持的“功能开关列表”。协商的结果是取交集:两边都懂的功能才启用,有一边不懂的就先不用。这种“向下兼容、能力裁剪”的策略,避免了“版本不一致就崩溃”的尴尬。

更难得的是,MCP 遵循 JSON-RPC 的“忽略未知字段”惯例:老版本客户端收到新 Server 返回的、自己不认识的新字段时,会选择忽略而不是报错。这意味着新老版本可以共存——老客户端不会因为多看了几个新字段而当场“晕倒”,新 Server 也不必拒绝服务老客户。

{protocolVersion, capabilities}C↔{protocolVersion, capabilities}S\displaystyle \{\text{protocolVersion},\ \text{capabilities}\}_C \leftrightarrow \{\text{protocolVersion},\ \text{capabilities}\}_S

1. 版本号互换:双方在 initialize 中交换各自支持的协议版本,像交换名片一样互相摸底。

protocolVersionC vs protocolVersionS\text{protocolVersion}_C \ vs \ \text{protocolVersion}_S

2. 能力求交集:capabilities 声明支持的特性集合,协商后取交集作为实际可用范围。

可用能力=capabilitiesC∩capabilitiesS\text{可用能力} = \text{capabilities}_C \cap \text{capabilities}_S

3. 未知字段忽略:低版本端遇到不认识的新字段直接跳过,保证会话不中断。

未知字段↦忽略并继续\text{未知字段} \mapsto \text{忽略并继续}

版本兼容就像两个口音不同的人用普通话交流:各自收起对方听不懂的方言词,把音量让给彼此能理解的部分。只要“普通话”(协议核心)还在,饭局就不会散。

客户端缓存

外卖 App 当然不会每次打开都重新下载整座城市的商家列表——它会缓存在本地,只有地点变化、用户手动下拉或商家营业状态变化时才重新拉取。MCP 客户端对工具列表的处理,遵循同样的智慧。

一次完整的 tools/list 请求-响应是有网络开销的,如果 Agent 每做一个决策都要重新问一遍服务端“你有哪些工具”,那就成了无意义的重复劳动。所以客户端会在一次发现后把工具列表缓存起来,会话内直接复用。这就是“缓存命中”——零请求,直接用。

但缓存必须知道什么时候失效。MCP 提供了 notifications/tools/list_changed 通知:Server 侧工具列表变化时,主动推送一条通知给客户端,客户端收到后立即重新发送 tools/list。这是事件驱动的刷新,比定时轮询更快、更省。如果通知和调用之间存在竞争(比如通知刚发出、调用就已到达),客户端还能用“调用失败后兜底重发现”的策略自愈。

命中↦直接调用;list_changed↦重新 tools/list;调用失败↦兜底重发现\displaystyle \text{命中} \mapsto \text{直接调用}; \quad \text{list\_changed} \mapsto \text{重新 tools/list}; \quad \text{调用失败} \mapsto \text{兜底重发现}

1. 缓存命中零请求:会话内已缓存的工具列表直接复用,减少网络往返。

cache hit:无网络请求\text{cache hit}: \text{无网络请求}

2. 通知驱动刷新:Server 通过 list_changed 通知工具变化,客户端收到后重发 tools/list。

list_changed⇒tools/list\text{list\_changed} \Rightarrow \text{tools/list}

3. 失败兜底:tools/call 遇到“工具不存在”时,客户端可回退到重新发现再重试。

call error⇒discover 后重试\text{call error} \Rightarrow \text{discover 后重试}

缓存是聪明食客的记忆力:记得住常去的店,但店铺改菜单时会收到店家来信(list_changed),点菜时如果发现菜已下架(method not found),也懂得重新翻开菜单再点一遍——既省力气,又不至于吃闭门羹。

同名工具冲突

你所在的城市可能有两家“老王饺子馆”:一家在中山路,一家在建设路。外卖平台不会让它们混为一谈——每个店的条目后面都跟着位置后缀,把“老王饺子馆(中山路店)”与“老王饺子馆(建设路店)”区分开。MCP 生态里的工具名冲突,也需要这种“加后缀”的智慧。

工具 name 只在各自 Server 内唯一。当 Agent 同时连接多个 Server,恰好有两个 Server 都提供了同名工具时,比如文件 Server 和网盘 Server 都有一个 upload,直接按名字调用就会产生歧义。这是所有走向“多 Server 生态”的 Agent 都会撞上的墙。

实践中主流的解决思路有三种:其一是命名空间隔离,客户端维护一张“Server 名 → 工具表”的映射,用 Server 标识加斜杠拼成全局唯一限定名;其二是约定式命名,Server 开发者按领域前缀给工具命名,比如文件服务叫 file_upload、网盘服务叫 drive_upload,从源头规避重名;其三是在日志与提示词中展示完整限定路径,让开发者一眼看出工具来自哪个 Server,排查问题时不会张冠李戴。

qualified_name=server_prefix / tool_name\displaystyle \text{qualified\_name} = \text{server\_prefix} \ \text{/} \ \text{tool\_name}

1. 冲突根源:工具名仅 Server 内唯一,跨 Server 时天然存在重名概率。

ServerA,ServerB⇒nameA=nameB 可能\text{Server}_A, \text{Server}_B \Rightarrow \text{name}_A = \text{name}_B \ \text{可能}

2. 前缀限定隔离:客户端用 Server 标识拼上工具名,构成全局唯一键。

qualified=server+/+name\text{qualified} = \text{server} + \text{/} + \text{name}

3. 约定去重:开发者遵循“领域前缀 + 动作”的命名规范,降低冲突概率。

name=domain+_+action\text{name} = \text{domain} + \text{\_} + \text{action}

有了限定名,Agent 就像拥有精准地址的食客:它要的是“中山路的老王饺子馆”,而不是“建设路的”——同名不再可怕,前缀分隔了世界,工具调用从此精确无误。

AI 应用场景

IDE 插件的即插即用工具市场

阶段:工具接入与编排

解决的问题:传统 IDE 插件把工具集写死,新增一个功能就要用户升级整个插件,体验割裂、迭代缓慢。基于 MCP 发现机制,Agent 首次启动时向所有已配置 Server 发 tools/list,之后每一次工具变化都能被及时感知。

具体案例:Claude Desktop 或 Cursor 中配置了文件系统、数据库、浏览器控制等多个 MCP Server。用户往配置目录里放进一个新的 Server 后,无需重启客户端,Agent 在新会话中就能看到并调用新工具,如同美食博主打开 App 时发现街角新开了一家甜品店。

企业内部服务台 Agent 的系统集成

阶段:运行时动态集成

解决的问题:企业内 HR、IT、财务系统众多,API 频繁迭代,硬编码接入会导致维护地狱。用 MCP 发现机制,业务系统只需实现 Server 并注册,Agent 自动发现其暴露的请假审批、资产申领、报销提交等工具。

具体案例:某公司的内部服务台 Agent 通过 MCP 接入十多个业务系统。新员工系统上线时,运维只需启动对应的 MCP Server;旧系统下架时,客户端收到 list_changed 通知自动刷新列表,员工永远面对最新可用的服务菜单。

自动化测试 Agent 的动态环境适配

阶段:动态编排与容错

解决的问题:测试环境里的工具不稳定,随时可能增减。测试 Agent 在进入环境后先 tools/list 扫描可用操作(创建测试数据、读取日志、清理资源),测试中途若工具被移除,依靠“调用失败兜底重发现”自动切换方案。

具体案例:一个回归测试 Agent 在临时测试环境中运行,前一轮还在用的“清空缓存”工具这一轮已被环境回收。Agent 调用失败后不是直接崩溃,而是重新发现工具列表,找到替代的“重置数据库”工具完成清理,用例照常通过。

Python 演示

python72 行
import jsonimport time  class MockMCPClient:    """模拟 MCP 客户端:动态发现、缓存、冲突处理"""     def __init__(self):        self.cache = {}   # server_url -> {"tools": [...], "ts": 时间戳}        self.version = "2024-11-05"     def send_request(self, server_url, method, params=None):        """模拟 JSON-RPC 请求;真实场景这里会走网络层"""        if method == "tools/list":            return self._fetch_tools(server_url)        raise ValueError("未知方法: " + method)     def _fetch_tools(self, server_url):        """模拟服务端返回的工具清单(含分页游标逻辑)"""        catalog = {            "file://svc": ["file_read", "file_write", "fulltext_search"],            "mail://svc": ["send_mail", "read_inbox"],            "weather://svc": ["get_weather"],        }        names = catalog.get(server_url, [])        tools = []        for name in names:            tools.append({                "name": name,                "description": "提供 " + name + " 能力",                "inputSchema": {                    "type": "object",                    "properties": {"q": {"type": "string"}},                    "required": ["q"],                },            })        return {"tools": tools, "nextCursor": None}     def discover(self, server_url, force=False):        """带缓存的发现:命中 60 秒内的缓存就不发请求"""        now = time.time()        if not force and server_url in self.cache:            entry = self.cache[server_url]            if now - entry["ts"] < 60:                return entry["tools"]        resp = self.send_request(server_url, "tools/list")        self.cache[server_url] = {"tools": resp["tools"], "ts": now}        return resp["tools"]     def on_list_changed(self, server_url):        """服务端通知工具变化:强制刷新缓存"""        return self.discover(server_url, force=True)     def qualified_names(self):        """解决跨 Server 同名冲突:server/tool 限定名"""        result = []        for server_url in ["file://svc", "mail://svc", "weather://svc"]:            prefix = server_url.split("://")[0]            for tool in self.discover(server_url):                result.append(prefix + "/" + tool["name"])        return result  if __name__ == "__main__":    client = MockMCPClient()    first = client.discover("file://svc")    print("首次发现工具数:", len(first))    again = client.discover("file://svc")    print("缓存命中(同一对象):", again is first)    print("list_changed 后刷新:", client.on_list_changed("file://svc"))    for q in client.qualified_names():        print("限定名:", q)
目的:演示 MCP 客户端如何带缓存地动态发现工具,并在多个 Server 提供同名工具时用“服务端前缀/工具名”构造全局限定名。
  1. self.cache = {}缓存字典,以 Server 地址为键,存放工具列表与时间戳。
  2. if method == "tools/list":模拟协议路由,真实场景这里会通过 JSON-RPC 发送网络请求。
  3. resp = self.send_request(server_url, "tools/list")核心的发现动作:向服务端请求工具清单。
  4. if now - entry["ts"] < 60:缓存命中判断,60 秒内的工具列表直接复用,避免重复网络开销。
  5. self.cache[server_url] = {"tools": resp["tools"], "ts": now}把最新工具列表写回缓存。
  6. return self.discover(server_url, force=True)收到 list_changed 通知后强制刷新,这是事件驱动的缓存失效。
  7. prefix = server_url.split("://")[0]提取服务端前缀(file/mail/weather),用于构造限定名。
  8. result.append(prefix + "/" + tool["name"])用“服务端前缀/工具名”解决跨 Server 重名问题。

常见误区

误区“我们团队的做法是 Agent 启动时调一次 tools/list 拿到工具列表,之后就一直用这份列表,Server 一般不会变,没必要反复刷新。”

正解MCP Server 完全可能在运行中增删工具:一个“邮件工具包”可能在用户授权后才加载发送功能,一个“插件型 Server”会热插拔子工具。如果客户端不监听 notifications/tools/list_changed,就会在工具已经消失或新增后,仍照着过期清单行事——要么调用失败,要么错失新能力。正确做法是:初始化时发现一次建立基线,之后通过通知做事件驱动刷新,并在调用失败时兜底重发现。这就像你记住了一家店的菜单,却没看到它贴出的“本周起不再供应这道菜”告示。

为何常见:很多人从“API 网关一般不会变”的惯性出发,把“动态发现”理解成“启动时的一次性枚举”,忽略了发现机制真正的价值在于持续感知变化。

误区“只要 description 写得清楚,两个 Server 有同名工具也没关系,大模型能根据描述区分该调哪个。”

正解在单个 Server 内工具 name 必须唯一,它是 tools/call 的调用标识,重复会让协议层的路由直接产生歧义。跨 Server 的同名冲突也不该靠大模型的语义猜测来解决——模型可能选错或困惑。正确做法是在客户端用 Server 前缀组成限定名,让每个工具在 Agent 视野里都拥有唯一地址,就像两家“老王饺子馆”用“中山路店”“建设路店”来区分。

为何常见:人们容易把 name 当成“给人看的标签”,而忽略了它是机器寻址的 ID;描述文字再丰富,也无法替代标识符的唯一性。

误区“工具描述写清楚用途就够了,inputSchema 随便给个空对象,反正大模型能自己猜参数。”

正解inputSchema 是 Agent 生成参数的唯一结构依据:字段名、类型、必填性、枚举值全部从它读出。空对象等于告诉模型“这个工具不需要参数”,类型写错会让调用直接失败,必填项漏写会让模型生成残缺参数。严谨的 inputSchema 不仅提升调用成功率,还能让客户端做参数预校验和界面表单生成,它绝不是可有可无的装饰。

为何常见:很多 demo 和教程为了精简只展示 name 和 description,让人误以为 inputSchema 是可选附件;而模型“偶尔能蒙对”的假象又掩盖了它的必要性。

误区“客户端和服务端协议版本如果不一致,肯定得报错,或者干脆退回最古老版本的功能,否则没法保证安全。”

正解MCP 采用协商加兼容的策略:双方通过 initialize 交换 protocolVersion 与 capabilities,然后取能力交集继续会话。老客户端遇到新 Server 返回的未知字段,按 JSON-RPC 惯例忽略即可;新客户端遇到老 Server 缺少某些能力,就主动少用那些功能。版本不一致不是异常,而是日常状态。

为何常见:HTTP 等协议的严格版本管理经验很容易迁移过来,但 JSON-RPC 的“未知字段忽略”哲学不一样——它相信向前兼容可以通过克制实现。

误区“我们给工具列表缓存设置了 30 秒的定时刷新任务,每半分钟拉一次最新的工具列表,这样一定不会用过期工具。”

正解定时轮询在 MCP 场景里是最差解:30 秒窗口内工具变了无法及时感知,而高频轮询又会白白消耗网络资源。协议本就提供了 notifications/tools/list_changed 事件通知,工具变化的瞬间 Server 就会推送,客户端收到后再刷新——这才是事件驱动的正确姿势。定时轮询只适合“Server 不支持通知”的兜底场景,或者把频率调得极低作为安全网。

为何常见:后端开发者的肌肉记忆里,“定时同步”是解决数据一致性的通用手段;但事件驱动才是分布式系统里更优雅的答案,而 MCP 已经把这个轮子造好了。

面试题

Q1. MCP 中客户端查看某服务端当前提供哪些工具,应调用什么方法?

单选题 · easy

  • A.tools/call 执行工具调用
  • B.tools/list 查询工具清单← 正确答案
  • C.initialize 完成握手连接
  • D.prompts/list 获取提示语

答案:B。tools/list 是发现工具的入口,相当于点开“附近商家”;initialize 是握手,tools/call 才是实际调用,D 是干扰项。

Q2. tools/list 成功响应中,存放工具清单的字段是?

单选题 · easy

  • A.result 结果字段
  • B.tools 工具数组← 正确答案
  • C.items 列表元素
  • D.data 数据主体

答案:B。协议规定响应体的核心字段是 tools,它是个数组,每个元素是一个工具的完整元数据。其他选项都是常见框架里的通用命名,不是 MCP 的约定。

Q3. 实际执行一个工具时使用的 MCP 方法是?

单选题 · easy

  • A.tools/list 查询工具清单
  • B.tools/call 执行工具调用← 正确答案
  • C.tools/run 启动一次任务
  • D.tools/invoke 触发工具逻辑

答案:B。发现用 tools/list,执行用 tools/call,这是 MCP 中“看菜单”和“点菜”的两个动作。run/invoke 都是编造的干扰项。

Q4. 大语言模型 Agent 决定“是否调用某工具”,直接影响判断的字段是?

单选题 · mid

  • A.工具的列表排序位置
  • B.工具名称字符数量
  • C.description 语义匹配度← 正确答案
  • D.服务端的硬件性能

答案:C。模型通过 description 理解工具用途并匹配当前任务,就像食客看招牌菜介绍决定下不下单。列表顺序、名称长度和硬件性能都不是语义判断的依据。

Q5. 两个不同 Server 都暴露 send_message 工具,客户端应该怎么做?

单选题 · mid

  • A.直接报错并拒绝本次连接
  • B.随机挑选其中一个来调用
  • C.用服务端前缀组合限定名← 正确答案
  • D.保留最后发现的同名工具

答案:C。跨 Server 同名冲突的正确解法是用“服务端前缀/工具名”构造全局唯一限定名,就像给两家同名的店加上位置后缀。报错、随机、保留最后一个都是错误或脆弱的策略。

Q6. 关于工具列表缓存刷新时机,以下哪些说法正确?

多选题 · mid

  • A.收到 list_changed 通知立即刷新← 正确答案
  • B.每次调用工具前都重发 tools/list
  • C.调用失败时可回退重新发现← 正确答案
  • D.初始化之后永不刷新工具列表

答案:AC。list_changed 是服务端主动推送的刷新信号;调用失败兜底重发现是自愈策略。B 每次都重新发现代价太大,D 忽略了工具列表会动态变化。

Q7. 工具元数据中的 description 写得好,可以带来哪些好处?

多选题 · mid

  • A.提升大模型选对工具的概率← 正确答案
  • B.让参数校验自动通过
  • C.减少无用工具的调用次数← 正确答案
  • D.确保工具名称全局唯一

答案:AC。description 是模型的语义依据:描述精准,选择就准、误调就少。B 是 inputSchema 的职责,D 是 name 的职责——菜单介绍写得再好,也不能替代店名和点单格式。

Q8. MCP 版本兼容中,若客户端协议版本低于服务端版本,正确做法是?

单选题 · hard

  • A.客户端立即拒绝建立连接
  • B.服务端主动关闭当前会话
  • C.协商版本并忽略未知字段← 正确答案
  • D.强制客户端升级后才能使用

答案:C。MCP 通过 initialize 交换版本与能力,取交集使用;老客户端遇到未知字段按约定忽略,而不是报错。这就像两个方言不同的人互相迁就,用普通话继续聊天。

Q9. 关于 tools/list 的分页机制,正确的说法有?

多选题 · hard

  • A.nextCursor 存在表示列表可续页← 正确答案
  • B.请求可携带 cursor 取下一页← 正确答案
  • C.分页仅用于超大工具列表
  • D.列表小则无需分页返回← 正确答案

答案:ABD。nextCursor 非空说明还有下一页,客户端携带它继续请求;工具少时服务端可以一次全量返回。C 错在分页不是超大列表的专属,任何大响应都可能用到它。

Q10. 客户端缓存工具列表后收到 list_changed 通知,tools/call 仍报 method not found,下一步最优操作是?

单选题 · extreme

  • A.忽略错误重试原调用
  • B.重新发现后再发起调用← 正确答案
  • C.断开并重建整个会话
  • D.标记工具永久不可用

答案:B。通知与调用之间可能存在竞争条件——列表刚变、本地缓存还没刷新,或变更通知后又发生了二次变化。最稳妥是立即重新 tools/list 拿到最新清单再重试。忽略错误会一直失败,断开重连代价大,永久屏蔽会错失恢复后的工具。