資源

資源是模型上下文協議(MCP)中的一個核心原語,允許服務器暴露數據和內容,這些內容可以被客戶端讀取並用作 LLM 交互的上下文。

🌐

資源設計為應用程序控制,這意味著客戶端應用程序可以決定如何以及何時使用它們。 不同的 MCP 客戶端可能會以不同方式處理資源。例如:

  • Claude Desktop 目前要求用戶在使用資源之前明確選擇它們
  • 其他客戶端可能會基於啟發式方法自動選擇資源
  • 某些實現甚至可能允許 AI 模型自己決定使用哪些資源

服務器作者在實現資源支持時應該準備好處理這些任何一種交互模式。為了自動向模型暴露數據,服務器作者應該使用模型控制的原語,如工具

概述

資源代表 MCP 服務器想要提供給客戶端的任何類型的數據。這可以包括:

  • 文件內容
  • 數據庫記錄
  • API 響應
  • 實時系統數據
  • 截圖和圖片
  • 日誌文件
  • 等等

每個資源都由唯一的 URI 標識,可以包含文本或二進制數據。

資源 URI

資源使用以下格式的 URI 進行標識:

[協議]://[主機]/[路徑]

例如:

  • file:///home/user/documents/report.pdf
  • postgres://database/customers/schema
  • screen://localhost/display1

協議和路徑結構由 MCP 服務器實現定義。服務器可以定義自己的自定義 URI 方案。

資源類型

資源可以包含兩種類型的內容:

文本資源

文本資源包含 UTF-8 編碼的文本數據。這些適用於:

  • 源代碼
  • 配置文件
  • 日誌文件
  • JSON/XML 數據
  • 純文本

二進制資源

二進制資源包含以 base64 編碼的原始二進制數據。這些適用於:

  • 圖片
  • PDF
  • 音頻文件
  • 視頻文件
  • 其他非文本格式

資源發現

客戶端可以通過兩種主要方法發現可用資源:

直接資源

服務器通過 resources/list 端點暴露具體資源列表。每個資源包括:

{
  uri: string;           // 資源的唯一標識符
  name: string;          // 人類可讀的名稱
  description?: string;  // 可選描述
  mimeType?: string;     // 可選 MIME 類型
}

資源模板

對於動態資源,服務器可以暴露 URI 模板,客戶端可以用它來構造有效的資源 URI:

{
  uriTemplate: string;   // 遵循 RFC 6570 的 URI 模板
  name: string;          // 此類型的人類可讀名稱
  description?: string;  // 可選描述
  mimeType?: string;     // 所有匹配資源的可選 MIME 類型
}

讀取資源

要讀取資源,客戶端需要發送帶有資源 URI 的 resources/read 請求。

服務器響應包含資源內容列表:

{
  contents: [
    {
      uri: string;        // 資源的 URI
      mimeType?: string;  // 可選 MIME 類型

      // 以下二選一:
      text?: string;      // 用於文本資源
      blob?: string;      // 用於二進制資源(base64 編碼)
    }
  ]
}
服務器可以在響應一個 resources/read 請求時返回多個資源。例如,當讀取目錄時,可以返回目錄中的文件列表。

資源更新

MCP 通過兩種機制支持資源的實時更新:

列表變更

服務器可以通過 notifications/resources/list_changed 通知來通知客戶端其可用資源列表發生了變化。

內容變更

客戶端可以訂閱特定資源的更新:

  1. 客戶端發送帶有資源 URI 的 resources/subscribe
  2. 當資源變化時服務器發送 notifications/resources/updated
  3. 客戶端可以用 resources/read 獲取最新內容
  4. 客戶端可以用 resources/unsubscribe 取消訂閱

實現示例

這是一個在 MCP 服務器中實現資源支持的簡單示例:

    const server = new Server({
      name: "example-server",
      version: "1.0.0"
    }, {
      capabilities: {
        resources: {}
      }
    });

    // List available resources
    server.setRequestHandler(ListResourcesRequestSchema, async () => {
      return {
        resources: [
          {
            uri: "file:///logs/app.log",
            name: "Application Logs",
            mimeType: "text/plain"
          }
        ]
      };
    });

    // Read resource contents
    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("Resource not found");
    });
    app = Server("example-server")

    @app.list_resources()
    async def list_resources() -> list[types.Resource]:
        return [
            types.Resource(
                uri="file:///logs/app.log",
                name="Application Logs",
                mimeType="text/plain"
            )
        ]

    @app.read_resource()
    async def read_resource(uri: AnyUrl) -> str:
        if str(uri) == "file:///logs/app.log":
            log_contents = await read_log_file()
            return log_contents

        raise ValueError("Resource not found")

    # Start server
    async with stdio_server() as streams:
        await app.run(
            streams[0],
            streams[1],
            app.create_initialization_options()
        )

最佳實踐

在實現資源支持時:

  1. 使用清晰、描述性的資源名稱和 URI
  2. 包含有助於 LLM 理解的幫助描述
  3. 在已知時設置適當的 MIME 類型
  4. 為動態內容實現資源模板
  5. 對頻繁變化的資源使用訂閱
  6. 使用清晰的錯誤消息優雅地處理錯誤
  7. 考慮大型資源列表的分頁
  8. 適當時緩存資源內容
  9. 在處理前驗證 URI
  10. 記錄你的自定義 URI 方案

安全考慮

在暴露資源時:

  • 驗證所有資源 URI
  • 實現適當的訪問控制
  • 淨化文件路徑以防止目錄遍歷
  • 謹慎處理二進制數據
  • 考慮資源讀取的速率限制
  • 審計資源訪問
  • 加密傳輸中的敏感數據
  • 驗證 MIME 類型
  • 為長時間運行的讀取實現超時
  • 適當處理資源清理