Ian Chou's Blog

MCP Tools 通知機制:與 Resources 的差異解析

MCP Tools 通知機制:與 Resources 的差異解析

深入理解 Tools 的 list_changed 通知與 Resources 訂閱機制的本質差異,以及如何設計跨元件的更新流程。


核心差異總覽

Tools 只有 list_changed,沒有「某個 tool updated」這種細粒度事件;而且這些通知都是「server 發給 client」,不是 Tool 自己直接發。

特性 Tools Resources
清單變更通知 notifications/tools/list_changed notifications/resources/list_changed
單一項目更新通知 ❌ 無 notifications/resources/updated
訂閱機制 ❌ 無 resources/subscribe
通知發送者 Server runtime Server runtime

Tools 可以發什麼通知?

notifications/tools/list_changed

這是 Tools 唯一支援的通知類型。

使用前提: Server 在 capabilities 宣告:

{
  "capabilities": {
    "tools": { "listChanged": true }
  }
}

表示「這個 server 會在 tools 清單有變時,對 client 發 list_changed 通知」。

觸發時機: 當工具集合有變化時(新增 / 移除 / 變更定義)

通知格式:

{ "jsonrpc": "2.0", "method": "notifications/tools/list_changed" }

Client 反應: 收到通知後,通常會再呼叫一次 tools/list 拿最新清單。

[!IMPORTANT]
這個通知是 「工具清單有變化」,而不是「某個工具被執行了一次」或「工具內部 state 改變」。


為什麼 Tools 沒有 updated 事件?

設計哲學的差異

概念 Resources Tools
本質 資料 / 上下文 動作 / 函式
狀態 有內容狀態,會變化 無狀態,只有定義
用途 被讀取、被消費 被執行、產生結果

Resources 代表的是「資料」,資料內容會隨時間變化,所以需要 updated 來通知「內容變了」。

Tools 代表的是「能力」,工具本身的定義(名稱、參數、描述)很少變動,變動時用 list_changed 足矣。

如果你想表達「Tool 執行後產生了新 data」

這是常見的需求,但 MCP 的設計是讓你透過 Resources 來處理

Tool 執行完
    ↓
寫入/更新一個 Resource
    ↓
Server 發 notifications/resources/updated
    ↓
Client 在 notification handler 決定要不要再叫一次 model

這種分離設計讓職責更清晰:Tools 負責做事,Resources 負責儲存結果。


「Tool 自己發通知」這件事怎麼理解?

在協議層,所有訊息都是「MCP server ↔ MCP client」之間的 JSON-RPC:

┌─────────────────────────────────────────────────────────────┐
│                    訊息流向                                  │
├─────────────────────────────────────────────────────────────┤
│                                                              │
│  Tool Handler (callback)                                     │
│       │                                                      │
│       │ 執行完畢,呼叫 sendToolListChanged()                 │
│       ↓                                                      │
│  Server Runtime                                              │
│       │                                                      │
│       │ 送出 JSON-RPC notification                           │
│       ↓                                                      │
│  MCP Client                                                  │
│                                                              │
└─────────────────────────────────────────────────────────────┘

重點:


實務設計 Pattern

Pattern 1:Tool 執行後觸發 Resource 更新

這是最推薦的做法,符合 MCP 的設計哲學。

// Tool handler
async function executeAnalysisTool(params) {
  const result = await runAnalysis(params);
  
  // 把結果寫入 resource
  await updateResource('analysis://latest-result', result);
  
  // 通知 client resource 已更新
  server.notifyResourceUpdated('analysis://latest-result');
  
  return { success: true };
}

Pattern 2:動態工具(需要 list_changed)

某些場景下,工具清單會動態變化:

// 使用者連接新的 database 後,新增對應的 query tools
async function onDatabaseConnected(dbName) {
  registerTool({
    name: `query_${dbName}`,
    description: `Query the ${dbName} database`,
    // ...
  });
  
  // 通知 client 工具清單變了
  server.notifyToolListChanged();
}

適用場景:


總結對照表

問題 答案
Tools 可以發通知嗎? ✅ 可以,但只有 list_changed
Tools 有 per-tool 的 updated 嗎? ❌ 沒有
Tool 執行後想推送更新怎麼辦? 把結果寫成 Resource,用 resources/updated
通知是誰發的? Server runtime,不是 Tool handler 自己

我的補充意見

為什麼這個設計是合理的

MCP 的設計遵循了一個清晰的關注點分離原則:

  1. Tools = 動詞(Actions)
    工具是「能做什麼」,本身是無狀態的。執行一次就結束,結果要嘛直接回傳,要嘛寫到某個地方。

  2. Resources = 名詞(Data)
    資源是「有什麼」,代表可被觀察的狀態。狀態會變,所以需要訂閱和更新通知。

  3. Prompts = 模板(Templates)
    給人類用的快捷方式,不參與運行時的事件流。

這種分離讓架構更容易理解:

想知道「發生了什麼變化」→ 訂閱 Resources
想「讓系統做點事」→ 呼叫 Tools

設計建議

如果你在設計 MCP Server,考慮以下原則:

原則 說明
Tool 產生的持久結果應寫入 Resource 讓 client 可以訂閱後續變化
只在真正需要時才用 list_changed 頻繁變動的工具清單會增加 client 負擔
善用 Resource Templates 搭配 Tools 使用,讓 LLM 可以先瀏覽再操作

這樣的設計不僅符合 MCP 規格,也讓你的 server 更容易被 AI 理解和使用。