向 LLM 公开服务器上的数据和内容

模型上下文协议(MCP) 中,资源(Resources)是一种核心机制,允许服务器向客户端公开可读取的数据和内容,并将其用作 LLM 交互的上下文。

资源的设计原则是 由应用控制,意味着客户端应用可以自行决定何时以及如何使用资源。不同的 MCP 客户端可能会有不同的资源处理方式,例如:

  • Claude Desktop 目前要求用户显式选择资源后才能使用
  • 其他客户端可能会基于启发式自动选择资源
  • 某些实现甚至可能允许AI 模型自行决定使用哪些资源

服务器开发者在实现资源支持时,需要做好准备以应对各种交互模式。如果希望模型自动获取数据,服务器应使用 模型控制(Model-Controlled)的方式,例如 工具(Tools)


概述

资源可以表示 MCP 服务器希望提供给客户端的任何数据,例如:

  • 文件内容
  • 数据库记录
  • API 响应数据
  • 实时系统数据
  • 屏幕截图和图像
  • 日志文件
  • 其他各种数据

每个资源都由唯一的 URI 进行标识,并且可以包含 文本数据二进制数据


资源 URI

资源的唯一标识符采用 URI 形式,格式如下:

  1. [协议]://[主机]/[路径]

例如:

  • 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 端点公开具体资源列表,每个资源包含:

  1. {
  2. uri: string; // 资源的唯一 URI
  3. name: string; // 可读的资源名称
  4. description?: string; // 可选的资源描述
  5. mimeType?: string; // 可选的 MIME 类型
  6. }

资源模板(Resource Templates)

对于动态资源,服务器可以公开 URI 模板,客户端可用其构造有效的资源 URI:

  1. {
  2. uriTemplate: string; // 符合 RFC 6570 的 URI 模板
  3. name: string; // 该类型资源的可读名称
  4. description?: string; // 可选的描述
  5. mimeType?: string; // 可选的 MIME 类型(适用于所有匹配资源)
  6. }

读取资源

客户端可以通过 resources/read 请求指定资源的 URI,以读取资源内容。

服务器响应示例如下:

  1. {
  2. contents: [
  3. {
  4. uri: string; // 资源的 URI
  5. mimeType?: string; // 可选的 MIME 类型
  6. // 资源内容(只能二选一):
  7. text?: string; // 文本资源的内容
  8. blob?: string; // 二进制资源的 Base64 编码内容
  9. }
  10. ]
  11. }

服务器可以在一个 resources/read 响应中返回多个资源。例如,当读取目录时,服务器可以返回该目录下的所有文件。


资源更新

MCP 允许通过两种机制对资源进行实时更新

列表变更

当可用资源列表发生变化时,服务器可以通过 notifications/resources/list_changed 通知客户端。

内容变更

客户端可以订阅某个资源的更新:

  1. 客户端发送 resources/subscribe 请求指定资源 URI
  2. 资源发生变化时,服务器发送 notifications/resources/updated 通知
  3. 客户端可通过 resources/read 获取最新内容
  4. 客户端可通过 resources/unsubscribe 取消订阅

示例实现

以下是一个简单的 MCP 服务器 资源支持 实现示例:

TypeScript 代码示例:

  1. const server = new Server({
  2. name: "example-server",
  3. version: "1.0.0"
  4. }, {
  5. capabilities: {
  6. resources: {}
  7. }
  8. });
  9. // 列出可用资源
  10. server.setRequestHandler(ListResourcesRequestSchema, async () => {
  11. return {
  12. resources: [
  13. {
  14. uri: "file:///logs/app.log",
  15. name: "应用日志",
  16. mimeType: "text/plain"
  17. }
  18. ]
  19. };
  20. });
  21. // 读取资源内容
  22. server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
  23. const uri = request.params.uri;
  24. if (uri === "file:///logs/app.log") {
  25. const logContents = await readLogFile();
  26. return {
  27. contents: [
  28. {
  29. uri,
  30. mimeType: "text/plain",
  31. text: logContents
  32. }
  33. ]
  34. };
  35. }
  36. throw new Error("资源未找到");
  37. });

最佳实践

在实现资源支持时,建议遵循以下最佳实践:

  1. 使用清晰的 URI 结构和描述性名称
  2. 提供有帮助的描述信息,以便 LLM 理解
  3. 设置正确的 MIME 类型(如 application/jsontext/plain
  4. 使用 URI 模板来支持动态资源
  5. 对频繁变化的资源使用订阅机制
  6. 提供清晰的错误消息,处理异常情况
  7. 对大型资源列表进行分页
  8. 在适当的情况下缓存资源内容
  9. 验证传入的资源 URI,避免安全问题
  10. 文档化自定义 URI 方案,确保易用性

安全性考虑

在公开资源时,应注意以下安全问题:

  • 验证所有资源 URI,防止恶意输入
  • 实施适当的访问控制,确保权限管理
  • 防止目录遍历攻击,避免文件路径被操控
  • 谨慎处理二进制数据,防止恶意内容执行
  • 对资源读取请求实施速率限制(Rate Limiting)
  • 记录资源访问日志,进行安全审计
  • 传输敏感数据时加密,避免泄露
  • 验证 MIME 类型,防止内容欺骗
  • 对长时间运行的请求设置超时机制
  • 适当清理资源,避免内存和存储泄漏