1. 从静态资源到资源模板
基础 MCP 资源是「固定的 URI → 固定内容」。但真实世界的资源是动态的:配置文件随环境变、文档按 ID 取、用户数据按参数查。
Resource Template(资源模板) 用 URI 模板表达「一类可寻址资源」,让客户端按需取任意子资源。
一句话:静态资源是「文件名」,资源模板是「路径模式」——模板让资源的寻址从枚举升级为参数化。
1.1 两种资源形态
| 形态 | URI 示例 | 说明 |
|---|---|---|
| 静态资源 | config://app/settings | 一个固定资源 |
| 资源模板 | doc://{id} | 一类资源,id 可变 |
1.2 模板定义(服务器端)
server.registerResourceTemplate(
{
uriTemplate: "doc://{id}.{ext}",
name: "文档资源",
description: "按 id 访问文档",
},
async (uri) => {
// uri = doc://42.md
const doc = await loadDocument(uri);
return {
contents: [{
uri: uri,
mimeType: "text/markdown",
text: doc,
}],
};
}
);
1.3 客户端读取
const res = await client.readResource({ uri: "doc://42.md" });
// 服务器按模板匹配 doc://{id}.{ext} → 解析 id=42, ext=md
一句话:模板的语法是 RFC 6570 URI 模板(
{var}),服务器解析匹配后动态生成内容——资源从「枚举」变成「按需计算」。
2. 资源模板语法与匹配
2.1 常用模板模式
| 模式 | 示例 | 说明 |
|---|---|---|
| 路径参数 | doc://{id} | 单参数 |
| 多参数 | org://{org}/user/{user} | 复合路径 |
| 扩展名 | doc://{id}.{ext} | 后缀分离 |
| 前缀通配 | db://{env}/* | 子资源 |
2.2 服务器匹配流程
客户端读 doc://42.md
服务器匹配模板 doc://{id}.{ext}
→ 解析变量:id=42, ext=md
→ 调用 handler(可用变量拼 SQL/路径/API 参数)
→ 返回资源内容
2.3 变量校验
// 解析后校验参数合法性
const match = template.match(uri);
if (!match) return { contents: [] };
const id = match.id;
if (!isValidId(id)) {
return { contents: [], isError: true, error: "invalid id" };
}
一句话:模板匹配 = 解析变量 → 校验 → 按变量取数——参数的合法性与安全性由服务器 handler 负责,不要信任 URI 中的任意值。
3. 订阅:ListChanged 通知
静态内容可以「客户端自己刷新」,动态资源需要服务器主动通知。
3.1 订阅机制
客户端请求订阅某资源列表
→ 服务器在资源集合变化时发 resources/listChanged 通知
→ 客户端收到通知 → 重新 listResources
→ 更新本地展示/上下文
3.2 服务器端实现
// 服务器:资源变化时推送通知
server.sendNotification(ResourceListChangedNotificationSchema, {
_meta: { version: ++resourceVersion },
});
3.3 客户端实现
// 客户端:注册 listChanged 处理
client.setRequestHandler(ResourceListChangedNotificationSchema, async () => {
const res = await client.listResources();
refreshContext(res.resources); // 更新上下文
});
3.4 订阅 vs 轮询
| 方式 | 实时性 | 成本 | 适用 |
|---|---|---|---|
| 轮询 | 有延迟 | 持续请求 | 低频变化 |
| 订阅通知 | 即时 | 事件推送 | 高频/事件驱动 |
一句话:订阅把「客户端问有没有新东西」变成「服务器有变化就告诉你」——这是资源从静态快照走向实时数据的关键一跳。
4. 内容协商与读取细节
4.1 读取响应结构
// readResource 响应
{
contents: [
{
uri: "doc://42.md",
mimeType: "text/markdown", // 内容类型
text: "...", // 文本内容
// 或 binary + encoding: "base64"
}
]
}
4.2 二进制资源
- 图片/音频等二进制资源用 base64 编码传输
- 客户端按 mimeType 决定如何呈现
- 超大资源需考虑「是否值得进 LLM 上下文」
4.3 资源注入上下文
资源的用途:
1. 直接注入 → 让 LLM 阅读该文件/数据
2. 作为参考 → 工具调用时的参数依据
3. 触发操作 → 资源的获取/变更驱动工作流
注入时注意 token 预算:只注入相关的部分
一句话:读取的语义由 mimeType 决定,注入的价值由「相关性」决定——资源进上下文前,先问「LLM 真的需要看全部吗」。
5. 与 Tools / Prompts 的协同
5.1 三种能力的分工
| 能力 | 语义 | 场景 |
|---|---|---|
| Resources | 读数据(只读) | 配置、文档、快照 |
| Tools | 执行操作(读写/副作用) | 查询、写入、计算 |
| Prompts | 模板化指令 | 结构化引导、复用流程 |
5.2 协同模式
例:代码分析场景
Resources:read 代码文件(src://main.ts)
Prompts :按「代码评审模板」引导
Tools :执行格式化/lint/搜索
例:运维场景
Resources:read 服务状态(svc://api/status)
Tools :执行重启/扩容
5.3 何时用哪种
只读数据 → Resources(甚至用模板按需取)
需要动作 → Tools(副作用明确)
需要引导 → Prompts(流程复用)
一句话:三者不是并列的「三选一」,而是一套协作语言——读用 Resources、动用 Tools、引导用 Prompts,组合起来才覆盖完整场景。
6. 缓存与失效策略
6.1 为什么需要缓存
LLM 上下文昂贵 → 重复读同一资源不应反复注入
→ 客户端缓存资源内容(按 URI)
→ 结合订阅:变化时失效缓存
6.2 缓存策略
- 静态资源:长 TTL / 不失效
- 动态资源:短 TTL + listChanged 主动失效
- 大资源:缓存但摘要后注入
- 秘密/敏感:缓存不进日志
失效优先级:
通知 > TTL > 客户端主动刷新
6.3 示例
const cache = new Map<string, { content, expiresAt }>();
async function readResourceCached(client, uri, ttlMs) {
const hit = cache.get(uri);
if (hit && hit.expiresAt > Date.now()) return hit.content;
const res = await client.readResource({ uri });
cache.set(uri, { content: res, expiresAt: Date.now() + ttlMs });
return res;
}
一句话:缓存是「资源进上下文」的成本控制器——订阅通知失效最及时,TTL 兜底,敏感资源特殊处理。
7. 常见陷阱
| 陷阱 | 症状 | 解决 |
|---|---|---|
| 模板与 URI 不匹配 | 返回空 | 仔细核对模板语法 |
| 未校验模板参数 | 注入攻击/错误 | handler 内严格校验 |
| 无订阅就等通知 | 资源不更新 | 需先订阅 + 注册 handler |
| 全量注入大资源 | 上下文爆炸 | 摘要 / 只注入相关部分 |
| 缓存无失效 | 读到旧数据 | TTL + listChanged 失效 |
| 二进制当文本 | 乱码 | 按 mimeType + base64 |
8. 总结
MCP 资源进阶机制可以概括为「模板寻址、订阅联动、注入克制」:
| 层面 | 要点 |
|---|---|
| 资源模板 | URI 模板让资源按需参数化 |
| 订阅通知 | listChanged 让资源实时联动 |
| 内容协商 | mimeType 决定读取与呈现 |
| 三能力协同 | 读用 Resource、动用 Tool、引导用 Prompt |
| 缓存失效 | 通知 > TTL > 手动刷新 |
Resources 从静态走向动态,靠模板与订阅两件事;从「能读」走向「有用」,靠上下文注入的克制与三能力协同。把模板、订阅、注入三件事做对,MCP 的资源能力才真正为 Agent 所用。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。