Appearance
深入 MCP:MCP Server 是怎么描述自己能力的?
Tools、Resources、Prompts 为什么要分成三种 Primitive?
MCP Server 最核心的三类能力是:
Tools、Resources 和 Prompts。
官方把它们称为 Primitive(协议原语),可以简单理解为:MCP 协议预先定义好的三种基础能力模型。
它们表面上看区别很大,但其实很多东西都可以全部做成 Tool。
比如读取一个项目文件,可以定义:
read_file
获取数据库表结构,可以定义:
get_database_schema
获取一段预先设计好的代码审查提示词,也可以定义:
get_code_review_prompt
既然都可以通过 Tool 实现,MCP 为什么还要再单独设计 Resource 和 Prompt?
因为协议真正需要表达的不只是:
text
Server 能不能返回这段数据?还需要告诉 Client:
text
这段能力应该以什么方式被发现、被选择和被使用。官方给三种 Primitive 定义了不同的控制模型:
- Tools:Model-controlled(模型控制)
- Resources:Application-controlled(应用控制)
- Prompts:User-controlled(用户控制)
这里的“控制”不是强制 UI 规则,而是在表达:
text
谁通常负责决定什么时候使用这项能力。Tool 更适合让模型自己决定什么时候调用。
比如模型发现用户问:
text
帮我查一下这个仓库最近有没有新的 Issue。它可以根据当前上下文选择:
list_issues
Resource 不一样。
Resource 更像 Server 暴露给 Host 的一份可寻址上下文数据。
例如:
file:///project/README.md
或者:
git://repository/history
Host 可以把这些资源做成文件树、上下文选择器,也可以根据自己的策略自动加入模型上下文。
决定:
text
哪些 Resource 应该进入当前上下文?通常不完全交给模型,而是由应用自己管理。
Prompt 又不同。
一个 Server 暴露:
code_review
Prompt,通常是希望用户主动选择:
text
我要执行“代码审查”这个预定义工作流。它可能最终在界面里表现成:
/code-review
或者一个菜单按钮。
所以 MCP 把 Prompt 设计成更偏向用户主动触发的能力。
这三个控制模型背后,其实对应三种完全不同的交互语义:
Tool:执行一个操作。
Resource:访问一份具有身份的数据。
Prompt:获取一个可以直接进入对话的预定义交互模板。
这也是为什么 MCP 没有设计一个万能对象:
text
Capability然后所有东西都塞进去。
MCP 选择在协议层直接把这些语义拆开。
这不仅让 Server 更清楚自己在暴露什么,也让 Host 能够针对不同类型采用完全不同的处理方式。
当然,这种划分并不是说现实世界中的能力永远只有唯一答案。
例如:
“读取 GitHub Issue”
既可以设计成:get_issue
Tool,也可以把某个 Issue 暴露成:github://issues/123 Resource。
两种方式都能返回 Issue 内容,但它们表达的协议意图不同。
Tool 在表达:
text
执行一次“获取 Issue”操作。Resource 在表达:
text
这里存在一份可以被定位和读取的 Issue 资源。tools/list 返回的到底是什么?
Server 声明支持:
json
{
"capabilities": {
"tools": {}
}
}以后,就意味着它实现了 MCP Tools 这套协议能力。
Client 接下来可以通过:
tools/list
获取当前可用的 Tool。
但 tools/list 返回的并不是一堆函数地址,也不是 Server 内部真实代码。
它返回的是:
text
Tool Definition(工具定义)。例如:
json
{
"name": "get_weather",
"title": "天气查询",
"description": "查询指定城市当前的天气信息",
"inputSchema": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "城市名称"
}
},
"required": ["location"]
}
}这里真正重要的是:
name
description
inputSchema
以及可选的:
outputSchema
annotations
title
icons
等信息。
这些字段共同回答的是:
text
这是什么能力?
怎样调用?
应该传什么?
可能返回什么?Tool 的 name 是协议调用时真正使用的标识。
后面执行:
text
tools/call时,Client 就是通过 name 指定要调用哪个 Tool。
Tool Name 应该在单个 Server 内保持唯一。
注意是:
text
单个 Server 内。不是整个 Host 全局唯一。
假设 Host 同时连接 GitHub Server 和 Google Drive Server,它们完全可能都暴露:
text
search对于各自 Server 来说都没有任何问题。
真正把多个 Server Tool 聚合到一起以后,才会发生:
text
GitHub.search
GoogleDrive.search这类命名冲突。
官方规范也明确提醒 Client 或 Proxy(代理层):如果要聚合多个 Server 的 Tool,应该自己设计消歧策略,例如给 Tool Name 加上 Server 前缀。
但不能简单使用:
serverInfo.name
作为全局唯一标识,因为 Server 自己报告的 name 本身也不保证唯一。
这其实再次对应了我们第一篇讲过的架构边界:
text
Server 只需要保证自己的能力空间内部一致,多 Server 聚合问题属于 Host。title 和 name 也不是一个东西。
name 是协议标识,例如:
text
create_issuetitle 更偏向给用户看的显示名称:
text
创建 GitHub Issuedescription 也非常关键。
当 Host 后面把 MCP Tool 转换成模型真正能够看到的 Tool Schema 时,这段 Description 很可能直接成为模型判断 “什么时候应该使用这个 Tool?”的重要依据。
所以 Tool Definition 实际上同时服务了两个消费者:
一边是程序:
text
name、Schema、协议字段。另一边是模型:
text
description、参数说明以及能力语义。这也是为什么 Tool Description 写得差,会直接影响模型的 Tool Selection(工具选择)。
Tool 还有一组:annotations
可以描述一些行为特征,例如:
readOnlyHint 表示它是否只是读取数据;
destructiveHint 表示修改操作是否可能具有破坏性;
idempotentHint 表示同样参数重复执行是否具有幂等性,也就是重复执行是否产生相同效果;
openWorldHint 则帮助 Client 判断这个 Tool 是否可能和开放的外部世界发生交互。
但这里一定要注意:
text
Annotation 是 Hint(提示),不是安全保证。官方 Schema 明确强调这些字段不能被认为一定真实。
一个恶意 Server 完全可以给:
delete_database
写上:
text
readOnlyHint = true所以 Client 可以用这些信息改善 UI 和默认交互,但不能仅凭 Server 自己声明的 Annotation 就跳过安全确认。
这和前面讲 serverInfo 时的原则其实一样:
text
自我声明的信息可以辅助理解,但不能自动升级成可信安全事实。为什么 Tool 的输入和输出都要用 JSON Schema?
如果 Tool 只有:
text
name
description模型可能知道“这个工具是干什么的”,但仍然不知道:
text
到底应该怎样构造参数?例如:
create_issue
可能需要:
json
{
"owner": "itkdm",
"repo": "aiagentguide",
"title": "xxx",
"body": "xxx"
}但如果协议只是告诉模型:
text
请传几个 JSON 参数。问题马上就来了:
owner 是 string 还是 number?
title 必填吗?
有没有枚举值?
数组内部是什么结构?
对象还允许哪些字段?
MCP 没有重新发明一套参数类型描述语言,而是直接采用 JSON Schema(JSON 数据结构约束规则)。
当前 Tool 可以通过:
inputSchema
描述输入。
如果没有显式声明 $schema,当前规范默认按照:
JSON Schema 2020-12
解释。
例如:
json
{
"type": "object",
"properties": {
"query": {
"type": "string"
},
"limit": {
"type": "integer",
"minimum": 1,
"maximum": 100
}
},
"required": ["query"]
}这已经不仅仅是在告诉模型:
text
有 query 和 limit 两个参数。它同时给出了:
类型、必填关系、取值范围
这些可以被程序直接验证的约束。
这就是 JSON Schema 最大的价值:
text
它既可以作为机器能够理解的类型契约,又足够通用,不需要 MCP 自己维护一套类型系统。当前 2026-07-28 还进一步放宽了 Tool Schema。
早期实现容易把 inputSchema / outputSchema 理解成比较简单的 Object Schema,但新版明确允许使用 JSON Schema 2020-12 中更加完整的关键字,包括 $ref、组合 Schema 等能力。
也就是说,一个复杂 Tool 不必把所有结构重复展开:
text
User
Address
Permission
...完全可以通过标准 JSON Schema 的引用和组合机制表达。
对于完全没有参数的 Tool,规范也没有建议直接把inputSchema省掉。
因为 Tool Definition 仍然需要明确表达:
text
这个 Tool 接受怎样的参数结构。官方推荐的无参数形式是:
json
{
"type": "object",
"additionalProperties": false
}它明确表达:
text
参数是一个空对象,而且不能随便再塞其他字段。相比:
json
{
"type": "object"
}前者约束更加准确。
Tool 的输出同样可以通过:outputSchema 描述。
为什么结果也需要 Schema?
因为只约束输入还不够。
假设:
get_user
声称返回:
json
{
"id": "123",
"name": "小明",
"email": "[email protected]"
}但 Server 某次突然返回:
json
{
"username": 123
}如果后续程序还要把这个结果交给另外一个 Tool,或者写入数据库,那么输出结构的不稳定同样会造成问题。
因此:
inputSchema
约束:
text
调用者应该传什么。outputSchema
描述:
text
Server 应该返回什么结构化结果。为什么 Tool Result 同时需要 content 和 structuredContent?
一个 Tool 执行完成以后,最简单的结果可以直接是一段文字:
json
{
"content": [
{
"type": "text",
"text": "东京当前温度为 26°C"
}
]
}content 并不限于纯文本。
它还可以包含:
Text、Image、Audio、Resource Link、Embedded Resource
等不同内容块。
也就是说,content 的目标更偏向:
text
给模型或者用户直接消费。例如一个图片分析工具可以直接返回图片;
一个文件生成工具可以返回 Resource Link(资源链接);
一个搜索 Tool 可以返回多段文本。
但如果 Tool Result 还要被程序继续处理,只返回自然语言就会很麻烦。
假设 get_weather 返回:
text
东京现在 26°C,天气晴朗,湿度 62%。对于 LLM 来说完全够用。
但如果下一个 Tool 需要读取:
text
temperature
humidity程序就不得不重新从自然语言里解析。
这显然不可靠。
所以 MCP 同时支持:structuredContent
也就是结构化结果。
例如:
json
{
"content": [
{
"type": "text",
"text": "东京当前 26°C,天气晴朗,湿度 62%。"
}
],
"structuredContent": {
"temperature": 26,
"condition": "sunny",
"humidity": 62
}
}这两个字段并不是重复设计。
content 更适合人和模型理解。
structuredContent 更适合程序继续处理。
如果 Tool 定义了 outputSchema,
那么 structuredContent 就应该符合这份 Schema。
例如:
json
{
"type": "object",
"properties": {
"temperature": {
"type": "number"
},
"condition": {
"type": "string"
},
"humidity": {
"type": "number"
}
},
"required": [
"temperature",
"condition"
]
}需要注意一点:
MCP 的 structuredContent 和我们平时说的大模型 Structured Output(结构化生成)不是一个东西。
Structured Output 通常是:
text
约束 LLM 必须按照某个 Schema 生成结果。MCP structuredContent 则是:
text
Tool Server 已经执行完成以后,返回给 Client 的结构化数据。数据生产者不同。
一个是模型。
一个是 Tool。
当前协议允许 structuredContent 是任意合法 JSON 值,而不只是 Object:
Array、String、Number、Boolean、Null
都可以。
如果 Tool 定义了 outputSchema,那么结果只需要满足对应 Schema 即可。
这对于:
text
list_users这类天然返回数组的 Tool 就很合理。
没有必要为了“顶层必须是 Object”,强行包装成:
json
{
"users": [...]
}还有一个兼容性问题。
目前规范建议:
text
如果 Tool 返回了 structuredContent,同时最好把序列化后的 JSON 也放进 Text Content。原因是旧版 Client 或者一些只认识 content 的上层集成,可能根本不会读取:
structuredContent
所以一个结果可以同时保留:
给旧 Client / LLM 看的文本版本
和:
给程序消费的结构化版本
Resource 为什么要用 URI,而不是也设计成一个 Tool?
Tool 的核心身份是:
text
nameResource 的核心身份却是:
URI(统一资源标识符)。
例如:
text
file:///project/src/main.tsResource 为什么不也设计成:
text
name = project_main_file因为 Resource 要表达的并不只是:
“我有一个读取文件的能力。”
它更重要的是:
text
这里存在一个可以被稳定定位的数据对象。这和函数调用完全不同。
一个:
read_file
Tool 表达的是操作:
text
给我一个 path,我帮你执行读取。而:
text
file:///project/src/main.ts表达的是身份:
text
这是一个具体资源。一旦数据具有稳定身份,很多能力就自然出现了。
Client 可以 resources/list 发现它;
通过 resources/read 读取它;
可以把这个 URI 存下来;
可以在 Prompt 或 Tool Result 里返回这个 URI;
甚至可以订阅它的变化。
所以 URI 其实给 Resource 建立了一种统一的:
text
寻址模型。这也是为什么 Resource 不要求一定对应真实文件。
规范里的 file:// 只是在表达:
text
这份 Resource 的行为类似文件系统资源。它并不要求背后真的存在一块物理磁盘。
一个 Server 完全可以把数据库里的虚拟目录暴露成:
text
file:///reports/2026/september.md同样,自定义 Server 也可以设计:
text
github://repo/itkdm/aiagentguide/issues/123只要符合 URI 规范即可。
当前 MCP 还定义了一些常见 Scheme(URI 方案),例如:
https://
file://
git://
也允许 Server 自己定义 Scheme。
其中 https:// 有一个很重要的语义。
如果 Resource 使用:
text
https://example.com/report.pdf规范建议只有在 Client 可以直接通过 Web 获取这个资源时才这样使用。
如果真正读取内容仍然必须经过 MCP Server:
resources/read
那么最好使用其他 URI Scheme。
原因也很好理解。
看到:
https://
Client 会自然认为:
text
这个 URI 本身就是一个可以直接访问的 Web 资源。URI Scheme 不只是字符串前缀,它实际上会影响 Client 对资源寻址方式的理解。
Resource 的内容也不一定只返回一段数据。
一次:
resources/read
可以返回多个 Resource Content。
比如读取:
text
file:///project/src/Server 完全可以把这个目录对应的多个文件一起返回。
文本 Resource 使用:
text
Binary Resource(二进制资源)则可以使用 Base64 编码后的:
blob
并配合:
mimeType
告诉 Client内容是什么媒体类型。
所以 Resource 真正建立的是:
text
身份 → 寻址 → 读取 → 内容类型 → 更新这样一条完整的数据模型。
如果数据根本没有必要被 Host 独立发现、选择、引用或者订阅,那么专门设计 Resource 反而会增加复杂度。
Resource Template 为什么还要单独存在?
Resource 有一个明显问题。
假设 GitHub Server 可以读取:
text
github://repos/{owner}/{repo}/issues/{issueNumber}全世界可能有几百万甚至几亿个符合这个结构的 Resource。
难道:
resources/list
要把所有可能的 URI 全部列出来吗?
当然不可能。
这时候就需要:
Resource Template(资源 URI 模板)。
例如:
text
github://repos/{owner}/{repo}/issues/{issueNumber}这不是一个具体 Resource。
因为:
owner
repo
issueNumber
都还没有确定。
它表达的是:
text
符合这个 URI 规则的一整类 Resource。客户端可以通过:
resources/templates/list
获取这些模板。
真正填入:
text
owner = itkdm
repo = aiagentguide
issueNumber = 123以后,才能得到:
text
github://repos/itkdm/aiagentguide/issues/123这样的具体 Resource URI。
Resource Template 使用的是标准的 URI Template 机制,而不是 MCP 自己设计:
text
{xxx}替换语法。
当前规范使用 RFC 6570 的 URI Template。
Resource Template 真正解决的是:
text
Resource Space(资源空间)太大,无法枚举。例如:
数据库表:
text
database://tables/{table}Git Commit:
text
git://commits/{sha}用户文档:
text
docs://users/{userId}/{documentId}这些资源都不是一个有限、固定的小列表。
如果只能依赖:
resources/list
要么列表大到不可接受,要么 Server只能暴露很小一部分资源。
Template 则让 Server 直接描述:
“我能访问哪一类地址。”
另外,Resource Template 参数还可以和 MCP Completion API(补全接口)结合。
例如用户输入:
text
github://repos/itkdm/Host 可以让 Server帮助补全 Repo Name。
这让 Resource Template 不只是一份协议描述,也可以进一步参与真正的 UI 交互。
不过它仍然不是 Tool。
因为 Resource Template 最终生成的结果是:
URI
而不是:
执行一个函数。
Prompt 为什么会成为 MCP 的一等能力?
很多人第一反应都会是:
text
Prompt 不就是一段字符串吗?如果只是固定写一句:
text
请帮我审查下面这段代码。确实没有必要专门设计一套协议。
但 MCP Prompt 真正表达的不是一段静态文字,而是:
text
Server 可以向 Host 提供的一组可发现、可参数化、结构化的对话模板。例如 Server 可以声明:
json
{
"name": "code_review",
"title": "代码审查",
"description": "检查代码质量并给出改进建议",
"arguments": [
{
"name": "code",
"description": "需要审查的代码",
"required": true
}
]
}Client 可以先通过:
prompts/list
发现它。
用户真正选择:
code_review
以后,Client 再调用:
prompts/get
并传入对应参数。
Server 最终返回的也不是单纯:
text
string而是:
text
messages例如:
json
{
"messages": [
{
"role": "user",
"content": {
"type": "text",
"text": "请审查下面这段代码:..."
}
}
]
}这里最大的区别就在于:
text
Prompt 是对话结构,而不只是文本。Prompt Message 可以有:
user
或者:
assistant
Role(角色)。
Content 也不只支持 Text。
还可以包含:
Image、Audio、Resource Link、Embedded Resource
等内容。
这意味着 Server 完全可以定义一套更完整的交互上下文。
例如:
text
先给模型一段用户问题,再附带一个 Resource,再附带图片。如果 Prompt 只是一个字符串字段,这些结构都会丢失。
那为什么这件事不能由 Tool 完成?
当然可以写一个:
text
generate_code_review_promptTool。
但这样以后,从协议层看,它只是一个:
text
模型可以调用的函数。Host 不知道:
text
这是一个更适合展示给用户主动选择的 Prompt Template(提示词模板)。所以 Prompt 成为独立 Primitive 的价值并不主要是:
“MCP 能帮你存 Prompt。”
真正的价值是:
text
MCP 给预定义交互模板建立了一个标准发现和调用协议。Host 可以把它做成:
Slash Command(斜杠命令)
菜单项
模板选择器
工作流入口
而不需要知道 Server 内部怎样生成 Prompt。
官方把 Prompt 定义为 User-controlled,也是这个原因:
text
用户通常主动决定什么时候使用这个模板。这里要注意,User-controlled 指的是:
text
谁决定使用它。不是:
text
谁编写 Prompt 内容。真正的 Prompt 内容仍然由 Server 定义。
例如 GitHub Server 完全可以提供:
text
summarize_pull_requestPrompt。
用户只是主动选择:
text
我要使用这个 Prompt。具体里面如何组织消息、引用哪些 Resource,仍然由 Server 决定。
所以三种 Primitive 的边界到这里其实已经很清楚了:
Tool 定义“可以做什么”。
Resource 定义“有什么数据可以访问”。
Prompt 定义“可以怎样开始一次预定义交互”。
总结
MCP Server 描述自己的能力,并不是简单返回一张“接口列表”,而是把能力拆成 Tools、Resources 和 Prompts 三种不同的 Primitive。三者解决的问题不同,也对应不同的控制模型:
- Tools:Model-controlled,更适合让模型根据当前上下文决定是否调用。
- Resources:Application-controlled,更适合作为 Host 可以发现、读取、引用和管理的上下文数据。
- Prompts:User-controlled,更适合作为用户主动选择的预定义交互模板。
Tool 的核心是 Tool Definition。tools/list 返回的不是 Server 内部函数本身,而是 name、description、inputSchema、可选的 outputSchema、annotations 等描述信息。name 用于协议调用,description 和参数说明会影响模型对 Tool 的理解,而 annotations 只是行为提示,不能被当作可信的安全保证。
Tool 使用 JSON Schema 描述参数和结构化结果,使调用双方能够共享一份机器可理解的类型契约。inputSchema 用来约束调用参数,outputSchema 可以描述结构化结果。Tool 执行完成以后,content 更适合模型和用户直接理解,而 structuredContent 更适合程序继续消费;它和大模型的 Structured Output 并不是同一个概念。
Resource 和 Tool 最大的区别在于:Tool 的核心身份是操作,Resource 的核心身份是 URI。 Resource 通过 URI 建立稳定的寻址模型,Client 可以发现、读取、引用甚至监听它的变化。对于无法枚举的大规模资源空间,MCP 又提供 Resource Template,通过 RFC 6570 URI Template 描述“一类资源”,而不是提前列出所有具体 URI。
Prompt 则解决另一类问题。它不是简单保存一段字符串,而是让 Server 向 Client 暴露 可发现、可参数化、结构化的对话模板。Client 可以通过 prompts/list 发现 Prompt,再通过 prompts/get 获得真正的 Prompt Messages。Prompt Message 还可以包含文本、图片、音频、Resource Link 或 Embedded Resource,因此它表达的是完整的对话结构,而不仅仅是一段文本。
所以理解这三种 Primitive 时,可以抓住三个最核心的区别:
- Tool 定义“可以做什么”。
- Resource 定义“有什么数据可以访问”。
- Prompt 定义“可以怎样开始一次预定义交互”。
它们并不是三种互相替代的实现方式,而是 MCP 为不同交互语义设计的三种标准能力模型。
相关面试题
- MCP Server 是怎么描述自己能力的?Tools、Resources 和 Prompts 有什么区别?
- 为什么 MCP 不把所有能力都统一设计成 Tool?
tools/list返回的到底是什么?Tool Definition 中哪些字段最重要?- Tool 的
name、title、description和annotations分别有什么作用? - 为什么 MCP Tool 要使用 JSON Schema?
inputSchema和outputSchema分别解决什么问题? - Tool Result 中
content和structuredContent有什么区别? - MCP Resource 为什么使用 URI,而不是像 Tool 一样只使用
name? - Resource Template 是什么?它为什么需要使用 URI Template?
- MCP Prompt 为什么要成为独立的一等能力?它和普通 Prompt 字符串、Tool 有什么区别?
- MCP 中 Model-controlled、Application-controlled 和 User-controlled 分别是什么意思?