当我们讨论大型语言模型(LLM)和 AI 应用时,通常关注的是模型本身的能力。但要构建功能丰富的 AI 应用,模型与工具、服务之间的高效协作同样至关重要。模型上下文协议(Model Context Protocol, MCP)正是一种为此设计的标准,而 MCP 客户端则是实现这种协作的关键一环。
什么是 MCP 客户端?
一个 MCP 客户端是由宿主应用程序(host application)实例化,用于和某个特定的 MCP 服务器进行通信的组件。这里的宿主应用程序,好比我们日常使用的 Claude.ai 或是一个集成开发环境(IDE),它负责管理整体的用户体验,并可能协调多个不同的客户端。
每个客户端只处理与一个服务器之间的直接通信。理解宿主和客户端的区别非常重要:宿主是我们用户直接交互的应用程序,而客户端是在协议层面实现与服务器连接的组件。
举个例子,当在一个 IDE(宿主)中使用一个 AI 代码分析工具时,IDE 可能会启动一个 MCP 客户端,专门用于和这个代码分析工具的服务器进行对话。同时,如果还使用了另一个 AI 辅助文档写作的工具,IDE 会为它启动另一个独立的 MCP 客户端。IDE 作为宿主,统一管理这些工具的界面展示和交互流程,而各个客户端则在后台默默地处理着与各自服务器的数据交换。
客户端的核心特性
MCP 客户端不仅能利用服务器提供的上下文信息,它本身也可以向服务器提供多种功能。这些客户端特性使得服务器的开发者能够构建出交互性更强的应用。
| 特性 | 说明 | 示例 |
|---|---|---|
| Elicitation | 允许服务器在交互过程中向用户请求特定信息,提供了一种结构化的按需信息收集方式。 | 一个旅行预订服务器可能会询问用户的座位偏好、房型或联系电话,以完成预订。 |
| Roots | 允许客户端指定服务器应当关注的文件目录,通过协调机制来传达预期的工作范围。 | 一个旅行预订服务器可以被授予访问特定目录的权限,以便读取用户的日程安排。 |
| Sampling | 允许服务器通过客户端请求 LLM 进行文本生成,从而实现类似 Agent 的工作流。这种方式将用户权限和安全措施的控制权完全交给了客户端。 | 一个旅行预订服务器可以将航班列表发送给 LLM,并请求 LLM 为用户挑选最佳航班。 |
下面,我们来逐一深入了解这些特性。
Elicitation:让服务器主动提问
Elicitation 机制使得服务器可以在交互过程中向用户请求特定信息,从而创造出更动态、响应更迅速的工作流程。
它为服务器提供了一种结构化的按需信息收集方式。当服务器缺少必要数据时,它不必直接失败或要求用户一次性提供所有信息,而是可以暂停当前操作,向用户发起一个具体的信息请求。这种方式让交互变得更加灵活,服务器能够适应用户的需求,而不是遵循死板的流程。
整个交互流程大致如下:服务器需要信息时,会向客户端发起一个 elicitation/create 请求。客户端收到后,会以合适的用户界面(UI)将问题呈现给用户。用户填写信息并提交后,客户端再将用户的响应返回给服务器。拿到新信息后,服务器便可继续执行后续的操作。
一个 Elicitation 请求通常包含一段说明信息和一个 schema,用以定义所需信息的结构。例如,一个旅行预订服务器在最后确认阶段,可能会发起如下请求:
{
"method": "elicitation/requestInput",
"params": {
"message": "请确认您的巴塞罗那假期预订详情:",
"schema": {
"type": "object",
"properties": {
"confirmBooking": {
"type": "boolean",
"description": "确认预订 (机票 + 酒店 = $3,000)"
},
"seatPreference": {
"type": "string",
"enum": ["window", "aisle", "no preference"],
"description": "航班座位偏好"
},
"roomType": {
"type": "string",
"enum": ["sea view", "city view", "garden view"],
"description": "酒店房间类型偏好"
},
"travelInsurance": {
"type": "boolean",
"default": false,
"description": "添加旅行保险 ($150)"
}
},
"required": ["confirmBooking"]
}
}
}
在这个例子中,message 清楚地告诉用户请求的目的。schema 则详细定义了需要用户提供或确认的信息:是否确认预订 (confirmBooking) 是一个必须回答的布尔值;座位偏好 (seatPreference) 和房间类型 (roomType) 是从给定选项中选择的字符串;旅行保险 (travelInsurance) 是一个默认为 false 的可选项。
客户端会根据这个 schema 生成相应的 UI 控件,如复选框、下拉菜单等,方便用户操作。同时,Elicitation 的设计也充分考虑了用户体验和隐私。客户端会清楚地展示是哪个服务器在请求信息、为什么需要这些信息,并允许用户选择提供、拒绝或取消整个操作。像密码或 API 密钥这类敏感信息是绝不会通过 Elicitation 请求的。
Roots:划定工作范围
Roots 的作用是为服务器的操作定义文件系统的边界,允许客户端明确告知服务器应该将注意力集中在哪些目录上。
当一个服务器需要与本地文件系统交互时,它如何知道哪些文件是相关的呢?Roots 就是为此设计的协调机制。它由一组文件 URI 组成,这些 URI 指向服务器可以操作的目录。
需要强调的是,Roots 传达的是预期的工作范围,它本身并不强制执行安全限制。真正的文件系统安全必须在操作系统层面通过文件权限或沙箱技术来保障。
一个 Root 的结构非常简单,通常包含 URI 和一个名称:
{
"uri": "file:///Users/agent/travel-planning",
"name": "Travel Planning Workspace"
}
这里的 uri 总是使用 file:// 协议,明确指向一个本地文件系统路径。name 则是一个便于人类理解的别名。
以一位需要处理多个客户旅行计划的旅行代理为例。其工作目录可能包含不同用途的文件夹。客户端可以向旅行规划服务器提供以下 Roots:
file:///Users/agent/travel-planning- 包含所有旅行计划文件的主工作区。file:///Users/agent/travel-templates- 存放可复用的行程模板。file:///Users/agent/client-documents- 存放客户的护照等文件。
当代理开始为客户创建一份新的巴塞罗那行程时,一个行为良好的服务器会尊重这些 Roots 边界。它会在指定的目录内查找模板、保存新文件、引用客户文档,而不会随意访问文件系统中的其他位置。
Roots 的列表是动态的。如果用户在 IDE 中打开了一个新的项目文件夹,比如 file:///Users/agent/archive/2023-trips,客户端会自动更新 Roots 列表,并通过 roots/list_changed 通知服务器工作范围发生了变化。
这种设计哲学将 Roots 定位为客户端与服务器之间的“君子协定”,而非强制性的安全沙箱。协议规范要求服务器“应该(SHOULD)”尊重 Roots 边界,而不是“必须(MUST)”强制执行。这是因为客户端无法完全控制服务器上运行的代码。因此,Roots 在与受信任的服务器协作、防止误操作以及组织工作流程方面表现出色。
Sampling:在客户端的控制下调用 LLM
Sampling 允许服务器通过客户端请求语言模型生成内容,这为实现更智能的 Agent 行为提供了可能,同时又将安全和控制权牢牢掌握在客户端手中。
设想一个场景:一个服务器(比如一个简单的航班分析工具)本身不具备调用 LLM 的能力,也没有相关的 API 密钥和计费账户,但它在执行任务时又需要 LLM 的分析能力。Sampling 机制就是为了解决这个问题。
服务器可以向客户端发起一个请求,让客户端代为调用 LLM。这种方式将客户端置于中心控制地位。因为客户端通常已经配置好了对各种 AI 模型的访问权限,它可以全权负责处理权限、安全和成本控制。
Sampling 的流程设计中包含了“人在环路”(human-in-the-loop)的关键节点,以确保安全。服务器发起请求后,客户端可以先将请求内容展示给用户审批。用户可以审核甚至修改将要发送给 LLM 的提示词。LLM 返回结果后,客户端同样可以先让用户审核,再决定是否将结果返回给服务器。
一个 Sampling 请求的参数可以非常丰富,允许服务器精细地表达其意图:
{
"messages": [
{
"role": "user",
"content": "分析这些航班选项并推荐最佳选择:\n" +
"[包含价格、时间、航司、中转信息的47个航班]\n" +
"用户偏好:早班机,最多1次中转"
}
],
"modelPreferences": {
"hints": [{
"name": "claude-sonnet-4-20250514"
}],
"costPriority": 0.3,
"speedPriority": 0.2,
"intelligencePriority": 0.9
},
"systemPrompt": "你是一位旅行专家,帮助用户根据偏好寻找最佳航班",
"maxTokens": 1500
}
在这个例子中,服务器不仅提供了完整的对话历史(messages),还通过 modelPreferences 表达了对模型的偏好:它建议使用 claude-sonnet-4-20250514 模型,并且更看重智能(intelligencePriority: 0.9),不太在意成本和速度。
使用我们之前提到的旅行预订场景,一个名为 findBestFlight 的工具在收集了 47 个航班选项后,就可以利用 Sampling 将这些数据连同用户偏好一起打包,请求客户端的 LLM 进行分析。LLM 会权衡价格、时间和中转次数等复杂因素,给出一个综合性的建议。该工具拿到这个分析结果后,再向用户展示最终的推荐。
通过将 LLM 调用能力集中在客户端,服务器开发者可以专注于自身的核心业务逻辑,而不必为集成和管理 LLM 费心。同时,对于用户而言,所有由服务器发起的 AI 调用都在其掌控之下,确保了透明度和安全性。
开发必备:API 全流程管理神器 Apifox
介绍完上文的内容,我想额外介绍一个对开发者同样重要的效率工具 —— Apifox。作为一个集 API 文档、API 调试、API 设计、API 测试、API Mock、自动化测试等功能于一体的 API 管理工具,Apifox 可以说是开发者提升效率的必备工具之一。
如果你正在开发项目需要进行接口调试,不妨试试 Apifox。注册过程非常简单,你可以直接在这里注册使用。

注册成功后可以先看看官方提供的示例项目,这些案例都是经过精心设计的,能帮助你快速了解 Apifox 的主要功能。
使用 Apifox 的一大优势是它完全兼容 Postman 和 Swagger 数据格式,如果你之前使用过这些工具,数据导入会非常方便。而且它的界面设计非常友好,即使是第一次接触的新手也能很快上手,快去试试吧!
