向 LLM 公开服务器上的数据和内容
在 模型上下文协议(MCP) 中,资源(Resources)是一种核心机制,允许服务器向客户端公开可读取的数据和内容,并将其用作 LLM 交互的上下文。
资源的设计原则是 由应用控制,意味着客户端应用可以自行决定何时以及如何使用资源。不同的 MCP 客户端可能会有不同的资源处理方式,例如:
- Claude Desktop 目前要求用户显式选择资源后才能使用
- 其他客户端可能会基于启发式自动选择资源
- 某些实现甚至可能允许AI 模型自行决定使用哪些资源
服务器开发者在实现资源支持时,需要做好准备以应对各种交互模式。如果希望模型自动获取数据,服务器应使用 模型控制(Model-Controlled)的方式,例如 工具(Tools)。
概述
资源可以表示 MCP 服务器希望提供给客户端的任何数据,例如:
- 文件内容
- 数据库记录
- API 响应数据
- 实时系统数据
- 屏幕截图和图像
- 日志文件
- 其他各种数据
每个资源都由唯一的 URI 进行标识,并且可以包含 文本数据 或 二进制数据。
资源 URI
资源的唯一标识符采用 URI 形式,格式如下:
[协议]://[主机]/[路径]
例如:
file:///home/user/documents/report.pdf
postgres://database/customers/schema
screen://localhost/display1
协议(protocol)和路径结构由 MCP 服务器的实现定义,服务器可以自定义 URI 方案。
资源类型
资源可以包含以下两种类型的内容:
文本资源
文本资源包含 UTF-8 编码的文本数据,适用于:
- 源代码
- 配置文件
- 日志文件
- JSON/XML 数据
- 纯文本
二进制资源
二进制资源包含 Base64 编码的二进制数据,适用于:
- 图像
- PDF 文件
- 音频文件
- 视频文件
- 其他非文本格式
资源发现(Resource Discovery)
客户端可以通过两种方式发现可用的资源:
直接资源(Direct Resources)
服务器通过 resources/list
端点公开具体资源列表,每个资源包含:
{
uri: string; // 资源的唯一 URI
name: string; // 可读的资源名称
description?: string; // 可选的资源描述
mimeType?: string; // 可选的 MIME 类型
}
资源模板(Resource Templates)
对于动态资源,服务器可以公开 URI 模板,客户端可用其构造有效的资源 URI:
{
uriTemplate: string; // 符合 RFC 6570 的 URI 模板
name: string; // 该类型资源的可读名称
description?: string; // 可选的描述
mimeType?: string; // 可选的 MIME 类型(适用于所有匹配资源)
}
读取资源
客户端可以通过 resources/read
请求指定资源的 URI,以读取资源内容。
服务器响应示例如下:
{
contents: [
{
uri: string; // 资源的 URI
mimeType?: string; // 可选的 MIME 类型
// 资源内容(只能二选一):
text?: string; // 文本资源的内容
blob?: string; // 二进制资源的 Base64 编码内容
}
]
}
服务器可以在一个 resources/read
响应中返回多个资源。例如,当读取目录时,服务器可以返回该目录下的所有文件。
资源更新
MCP 允许通过两种机制对资源进行实时更新:
列表变更
当可用资源列表发生变化时,服务器可以通过 notifications/resources/list_changed
通知客户端。
内容变更
客户端可以订阅某个资源的更新:
- 客户端发送
resources/subscribe
请求指定资源 URI - 资源发生变化时,服务器发送
notifications/resources/updated
通知 - 客户端可通过
resources/read
获取最新内容 - 客户端可通过
resources/unsubscribe
取消订阅
示例实现
以下是一个简单的 MCP 服务器 资源支持 实现示例:
TypeScript 代码示例:
const server = new Server({
name: "example-server",
version: "1.0.0"
}, {
capabilities: {
resources: {}
}
});
// 列出可用资源
server.setRequestHandler(ListResourcesRequestSchema, async () => {
return {
resources: [
{
uri: "file:///logs/app.log",
name: "应用日志",
mimeType: "text/plain"
}
]
};
});
// 读取资源内容
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
const uri = request.params.uri;
if (uri === "file:///logs/app.log") {
const logContents = await readLogFile();
return {
contents: [
{
uri,
mimeType: "text/plain",
text: logContents
}
]
};
}
throw new Error("资源未找到");
});
最佳实践
在实现资源支持时,建议遵循以下最佳实践:
- 使用清晰的 URI 结构和描述性名称
- 提供有帮助的描述信息,以便 LLM 理解
- 设置正确的 MIME 类型(如
application/json
、text/plain
) - 使用 URI 模板来支持动态资源
- 对频繁变化的资源使用订阅机制
- 提供清晰的错误消息,处理异常情况
- 对大型资源列表进行分页
- 在适当的情况下缓存资源内容
- 验证传入的资源 URI,避免安全问题
- 文档化自定义 URI 方案,确保易用性
安全性考虑
在公开资源时,应注意以下安全问题:
- 验证所有资源 URI,防止恶意输入
- 实施适当的访问控制,确保权限管理
- 防止目录遍历攻击,避免文件路径被操控
- 谨慎处理二进制数据,防止恶意内容执行
- 对资源读取请求实施速率限制(Rate Limiting)
- 记录资源访问日志,进行安全审计
- 传输敏感数据时加密,避免泄露
- 验证 MIME 类型,防止内容欺骗
- 对长时间运行的请求设置超时机制
- 适当清理资源,避免内存和存储泄漏