5.1 资源与资源模板:URI 即接口 本节摘要:第 4 章讲了模型驱动的工具,本节讲应用驱动的资源。资源是 MCP 三大原语里的「只读数据提供者」,类比 Web API 的 GET——它不改世界,只把数据喂给模型上下文。本节的核心是讲清资源的两种形态:具体资源(URI 无参数,固定数据)与资源模板(URI 带 ,参数化数据)。资源的设计哲学是「URI 即接口」——URI 既是地址,也是参数来源。读完本节,你能用 注册两类资源,并说清它们的差别。 一、资源是什么:应用驱动的只读数据 回顾第 2.2 节的原语划分:资源是应用(宿主)决定加载的原语,类比 GET,只读。
本节摘要:第 4 章讲了模型驱动的工具,本节讲应用驱动的资源。资源是 MCP 三大原语里的「只读数据提供者」,类比 Web API 的 GET——它不改世界,只把数据喂给模型上下文。本节的核心是讲清资源的两种形态:具体资源(URI 无参数,固定数据)与资源模板(URI 带
{param},参数化数据)。资源的设计哲学是「URI 即接口」——URI 既是地址,也是参数来源。读完本节,你能用@mcp.resource(uri)注册两类资源,并说清它们的差别。
回顾第 2.2 节的原语划分:资源是应用(宿主)决定加载的原语,类比 GET,只读。
宿主准备调用模型前 │ ▼ 「这次对话需要项目配置上下文」 宿主决定加载 config://app 资源 │ ▼ 客户端发 resources/read 请求 服务端返回配置内容 │ ▼ 宿主把内容塞进模型上下文 模型带着这份上下文推理
资源的关键特征:
这几点与工具(模型驱动、有副作用、运行时调用)形成鲜明对比。
资源的设计哲学浓缩为一句话:URI 即接口。URI 既是资源的「地址」(客户端据此找到它),也是参数来源(URI 里的占位符就是参数):
URI: config://app │ │ │ └── 资源标识(名为 app 的配置) └── 方案(自定义,体现来源) URI: db://users/{id} │ │ │ │ │ └── 参数占位符(填用户 ID) │ └── 资源类型(用户) └── 方案(数据库)
把 URI 想象成 Web API 的路径,config://app 类比 GET /config/app,db://users/{id} 类比 GET /db/users/{id}。这个类比贯穿整个资源体系。
最简单的资源形态是具体资源——URI 里没有占位符,代表一份固定数据:
@mcp.resource("config://app") def get_config() -> dict: """Return app configuration.""" return {"theme": "dark", "lang": "zh", "timeout": 30}
具体资源的特点:
resources/list 的结果里客户端调 resources/list │ ▼ 服务端返回:[ {uri: "config://app", name: "get_config", description: "..."}, {uri: "status://health", ...}, ... ] ← 具体资源都在这里
另一种形态是资源模板——URI 里有 {param} 占位符,代表一类参数化数据:
@mcp.resource("db://users/{user_id}") def get_user(user_id: str) -> dict: """Get a user by ID.""" return fetch_user_from_db(user_id)
资源模板的特点:
resources/list,而在 resources/templates/list(单独的模板列表)客户端调 resources/templates/list │ ▼ 服务端返回:[ {uriTemplate: "db://users/{user_id}", ...}, {uriTemplate: "log://{date}", ...} ] 客户端要读时: resources/read db://users/u123 ← 填参数 user_id=u123 │ ▼ 服务端解析 URI,提取 user_id="u123" 调用 get_user("u123") 返回用户数据
把两种形态放一起对照,差异更清晰:
| 维度 | 具体资源 | 资源模板 |
|---|---|---|
| URI 形式 | 无参数(config://app) |
带 {param}(db://users/{id}) |
| 能否直接列举 | 能,在 resources/list |
不能,在 resources/templates/list |
| 读取方式 | 直接 resources/read <uri> |
先填参数实例化 URI,再 read |
| 数据特征 | 一份固定数据 | 一类参数化数据 |
| 类比 Web | GET /config(单数) |
GET /users/{id}(参数化路由) |
💡 技巧:判断该用哪种,问一句:「这个资源的数据是一份,还是一类?」 一份固定数据→具体资源;一类按参数变化的数据→资源模板。例:应用配置是一份(具体资源),用户数据是一类(资源模板)。
资源模板有个细节值得强调——URI 占位符的名字,必须与函数参数名一致:
@mcp.resource("db://users/{user_id}") # URI 占位符叫 user_id def get_user(user_id: str) -> dict: # 函数参数也叫 user_id ...
SDK 用这个对应关系,把 URI 里解析出的值,映射成函数调用:
读取请求:db://users/u123 │ ▼ SDK 解析 URI 匹配模板:db://users/{user_id} 提取参数:user_id = "u123" │ ▼ 调用函数 get_user(user_id="u123")
如果 URI 占位符叫 {user_id} 但函数参数叫 id,SDK 会对不上,导致调用失败。所以起名要一致——这是资源模板的约定。
资源返回的内容,会被宿主塞进模型上下文。返回类型灵活:
@mcp.resource("config://app") def get_config() -> dict: # 字典 → JSON 文本 return {"theme": "dark"} @mcp.resource("readme://project") def get_readme() -> str: # 字符串 → 原样 return "# 项目说明\n..." @mcp.resource("image://{name}") def get_image(name: str) -> bytes: # 字节 → 二进制内容 return load_image(name)
不同返回类型适合不同场景:文本/JSON 给模型读、字节给富内容(图片等)。SDK 会按类型包装成对应的内容格式发回客户端。
{param},参数化数据,需实例化才能读,适合一类数据。资源清楚了,下一节回答一个关键追问——「为什么不直接用工具」。