登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  科技周边 >  人工智能

MCP 客户端怎样区分工具、资源和提示模板

来源:17golang原创

时间:2026-11-02 10:08:04 142浏览 收藏

MCP 客户端区分工具、资源和提示模板,关键不是看名称里有没有“tool”或“prompt”,而是按协议完成两次判断:先看 initialize 响应里的能力声明,再把协议方法映射到对应对象。tools 是可被模型请求执行的函数能力,resources 是由 URI 标识的上下文数据,prompts 是可由用户选择并带参数展开的消息模板。

官方规范地址:https://modelcontextprotocol.io/specification/2025-06-18

本文把客户端设计拆成“能力覆盖率、列表完整度、调用确认率”三个可记录指标。它们是工程上的观测口径,不是某个真实服务端的实测性能数据:客户端至少要知道自己发现了哪些能力、列表是否读完、哪些工具调用经过了用户确认。

先看初始化响应里的能力声明

MCP 的生命周期要求客户端先发起 initialize,与服务端协商协议版本和能力;服务端返回自己的 capabilities 后,客户端再发送 notifications/initialized 进入正常操作阶段。因此,客户端目录的第一条记录应来自初始化响应,而不是来自某个列表接口的偶然返回。

MCP initialize 响应中 tools、resources、prompts 能力边界的静态说明图
图1:MCP 能力边界说明图,展示客户端如何从初始化响应识别工具、资源和提示模板。

服务端可能只声明其中一类,也可能同时声明三类。客户端可以把能力覆盖率记成一个集合,而不是把缺失能力当成错误。例如:

{
  "capabilities": {
    "tools": { "listChanged": true },
    "resources": { "subscribe": true, "listChanged": true },
    "prompts": { "listChanged": false }
  }
}

上面的 JSON 是协议字段示例,字段旁边的布尔值只表示服务端是否支持相应的变更通知或订阅能力。客户端拿到响应后可以得到三条不同的结论:

  • 存在 tools,才去发现和调用工具;
  • 存在 resources,才去读取 URI 资源,并根据 subscribe 决定是否建立资源订阅;
  • 存在 prompts,才把提示模板暴露给用户选择。

这里最容易犯的错是“列表为空就认为能力不存在”。能力声明回答的是“服务端是否提供这一类接口”,列表响应回答的是“当前能列出哪些对象”,两者必须分别记录。建议至少保存 capabilityDeclared、listCompleted 和 lastChangedAt 三个指标字段。

用方法名判断对象类型

初始化能力只能决定客户端应该打开哪扇门,真正的对象类型由协议方法决定。可以用下面这张对照表作为客户端路由规则:

能力发现方法取单项或执行方法核心数据主要驱动方
toolstools/listtools/callinputSchema、调用结果模型
resourcesresources/list、resources/templates/listresources/readuri、MIME 类型、内容应用
promptsprompts/listprompts/get参数、消息列表用户
MCP 工具资源提示模板与协议方法、核心数据的静态映射说明图
图2:MCP 方法与数据对象映射说明图,帮助客户端把协议入口路由到正确的内部模型。

这张表也解释了为什么不能把三类对象塞进同一个“可执行项”数组:工具的关键是输入 JSON Schema 和调用结果,资源的关键是 URI 与内容,提示模板的关键是参数展开后的消息。它们都能有 name 和 description,但同名字段不等于同一种语义。

列表接口支持分页,客户端不能只读取第一页就把“列表完整度”记为 100%。当响应带有 nextCursor 时,应继续请求下一页;没有游标时才可以把本轮列表标记为完成。这个指标尤其适合排查“客户端明明支持工具,但模型看不到某个工具”的问题。

区分模型驱动、应用驱动和用户驱动

MCP 规范对三类能力的交互模型有明确区分。工具通常是模型根据上下文发现并请求调用的函数;资源是应用决定如何让用户或模型选择、搜索或自动纳入上下文的数据;提示模板则面向用户显式选择,常见呈现方式是命令或可选模板列表。

因此,客户端 UI 或代理层可以按下面的决策方式组织:

  1. 遇到 tools:展示名称、描述、输入参数和风险提示;调用前保留用户拒绝入口。
  2. 遇到 resources:展示资源 URI、名称、描述和 MIME 类型;读取结果后由应用决定放入哪个上下文槽位。
  3. 遇到 prompts:展示可选模板及其参数;用户确认后调用 prompts/get,再把返回的消息交给后续模型流程。

注意“驱动方”是交互模型,不是安全豁免。工具描述和工具注解仍然可能来自不可信服务端,客户端应把它们当作待审查元数据;资源内容也不能在没有用户同意时被转发到其他服务。

统一保存元数据但分开执行入口

如果客户端需要做搜索、排序或权限展示,可以建立统一目录,但必须保留一个不可变的 kind 字段。下面的 TypeScript 只是客户端内部类型草图,不绑定任何 SDK;注释说明了分类判断和入口分离的目的。

type McpEntry =
  | { kind: "tool"; name: string; description?: string; inputSchema: unknown }
  | { kind: "resource"; uri: string; name?: string; mimeType?: string }
  | { kind: "prompt"; name: string; description?: string; arguments?: unknown[] };

function routeEntry(entry: McpEntry): string {
  // kind 是协议语义边界,不能只靠 name 或 description 猜测。
  switch (entry.kind) {
    case "tool":
      // 工具进入模型可调用目录,真正调用时还要经过授权确认。
      return "tools/call";
    case "resource":
      // 资源要按 URI 读取,不能被当成可执行函数。
      return "resources/read";
    case "prompt":
      // 提示模板先由用户选择,再按名称和参数获取消息。
      return "prompts/get";
  }
}

工程上可以把三类对象放在同一个搜索界面,但点击动作应该不同:工具按钮打开参数和确认面板,资源打开上下文预览,提示模板打开参数填写面板。这样既方便统一检索,也不会让用户误以为“看到的每一项都能直接执行”。

对于工具,inputSchema 是客户端生成参数表单或做请求前校验的重要依据;对于资源,URI 是唯一定位键,资源模板还可能需要先补全 URI 参数;对于提示模板,参数最终进入 messages,不能把模板名字直接当成用户消息文本。

处理分页、变更通知和安全边界

把分类逻辑写对之后,仍要处理三个动态问题。第一,列表可能分页;第二,服务端可能声明 listChanged,随后通过通知告诉客户端列表有变化;第三,资源和工具都可能触及敏感数据或外部副作用。

可以把一次同步的结果记录为下面四个状态,而不把它们混成一个“加载成功”:

状态含义排查重点
能力已声明初始化响应包含对应 capability是否真的注册了该类能力
列表已完成所有分页都已消费是否遗漏 nextCursor
变更已同步收到 list_changed 后重新拉取通知处理器是否按 kind 路由
调用已确认工具调用前完成用户确认是否把不可信描述当成授权依据

工具调用至少要在 UI 上说明工具名称、参数和可能的副作用,并给用户拒绝机会。资源读取要尊重用户的数据边界,不因为“资源是只读的”就自动把内容发送给任意模型或远端服务。提示模板虽然由用户选择,模板展开后的消息仍应在进入模型前展示关键参数。

如果服务端没有声明某个 capability,客户端不要凭空调用对应方法;如果能力已声明但列表请求失败,应保留“声明存在、列表未完成”的状态,让重试和诊断有依据。这个拆分比单纯显示“连接成功”更能解释客户端为何看不到对象。

一个可落地的分类核对清单

在接入新的 MCP 服务端时,可以用以下顺序核对:

  • 是否完成 initialize 和 notifications/initialized,并保存服务端返回的协议版本与 capabilities?
  • 是否按能力分别调用 tools/list、resources/list、prompts/list,并处理 nextCursor?
  • 是否用 kind 固定三类内部模型,而不是靠名称、图标或描述猜测?
  • 是否将工具调用、资源读取、提示模板展开设计成三种不同交互?
  • 是否在服务端列表变化后重新同步,并在工具调用前保留用户确认?

记住一个最短判断:能被模型请求执行的是工具;由 URI 表示、供上下文使用的是资源;由用户选择、返回消息模板的是提示。客户端只要把“能力声明—发现方法—执行入口—安全交互”四层分开,三类对象就不会在同一个目录里互相误用。

常见问题

MCP 的 resources 能不能像 tools 一样直接调用?

不能按同一语义处理。资源通过 URI 定位,通常使用 resources/read 获取内容;它可能被应用放进模型上下文,但不是带参数执行的函数。

prompts/list 返回的对象为什么不能直接交给模型?

列表通常只提供模板的名称、描述和参数定义。客户端还需要调用 prompts/get,提供所需参数,拿到消息列表后再按应用流程展示或提交。

服务端同时声明 tools 和 resources 时要合并吗?

可以合并到一个可搜索目录,但不要合并执行语义。保留 kind,让搜索结果点击后分别进入工具确认、资源预览或提示参数面板。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>