Skip to main content

Command Palette

Search for a command to run...

續談 MCP Server︰Resource、Server Inspector

Updated
6 min read
續談 MCP Server︰Resource、Server Inspector

繼上一篇 了解 MCP Go 只談到 Tools,以及 MCP client 與 server 之間的交互過程。這次我們來看看另外兩個功能(CapabilityResourcePrompt


Resource Capability

Resource 在 MCP Server中,用來表示 Client 能夠存取該 Server 哪些內部的資源或者提供訂製化的請求,並且把這些內容當作跟 LLM 交互的上下文來使用,使得 AI 給我們更加精準的回應。所以不同的 MCP Client 可能有不同的方式決定何時(When)以及如何(How)使用這些 Resource。

官方舉例這些都是 Resource︰

  • File contents

  • Database records

  • API responses

  • Live system data

  • Screenshots and images

  • Log files

  • And more

Each resource is identified by a unique URI and can contain either text or binary data.

但如果只是要索取資料跟內容,感覺上 Tools 也是能作到,也是沒錯的!只是 Tools 能做到去修改狀態(額外給予 LLM R/W 權限)。而 Resource 不行(額外給予 LLM ReadOnly 權限),且 Resource 語意上主要是提供結構化資料以及所在的位子(URI)提供存取。

所以在 Tools 的說明有這段。

Like resources, tools are identified by unique names and can include descriptions to guide their usage. However, unlike resources, tools represent dynamic operations that can modify state or interact with external systems.

所以你可以理解成 Resoruce 能回傳有哪些 databases 以及 tables。而Tools則是允許你去操作(CRUD+計算)。

讓我們來看看 MCP Postgres Server 的這段程式碼。它就把所有 public 下的 table 給組成 resources,並且每張 table 都有給上 URI,例如 postgres://database/customers/schema

server.setRequestHandler(ListResourcesRequestSchema, async () => {
  const client = await pool.connect();
  try {
    const result = await client.query(
      "SELECT table_name FROM information_schema.tables WHERE table_schema = 'public'",
    );
    return {
      resources: result.rows.map((row) => ({
        uri: new URL(`${row.table_name}/${SCHEMA_PATH}`, resourceBaseUrl).href,
        mimeType: "application/json",
        name: `"${row.table_name}" database schema`,
      })),
    };
  } finally {
    client.release();
  }
});

透過 resources/list 請求,就能取得所有的 Resource,目的是讓 Client 知道有哪些 Resource 可以使用。且 list 請求也支援 pagination 功能,晚點在說明。

Request︰

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "resources/list",
  "params": {
    "cursor": "optional"
  }
}

Response:

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "resources": [
      {
        "uri": "file:///project/src/main.rs",
        "name": "main.rs",
        "description": "Primary application entry point",
        "mimeType": "text/x-rust"
      }
    ],
    "nextCursor": "next-page-cursor"
  }
}

而描述 Resoruce 清單 的 metadata 樣貌如下,其中 URI 格式則是[protocol]://[host]/[path]

其實這跟 HTTP 去請求資源的格式沒什麼不同。HTTP 請求也是有 Request URLContent-Type這些。

{
  uri: string;           // Unique identifier for the resource
  name: string;          // Human-readable name
  description?: string;  // Optional description
  mimeType?: string;     // Optional MIME type
}

MCP Client 使用 resources/read 方法,指定一個資源的 URI,向 MCP Server 請求該資源的具體內容。

Request︰需要指定 URI

{
  "jsonrpc": "2.0",
  "id": 2,
  "method": "resources/read",
  "params": {
    "uri": "file:///project/src/main.rs"
  }
}

Response︰

如果資源是文字檔,則使用 text 來表示。但如果是二進制檔案則使用 blob 以base64 編碼的形式來表示。

{
  "jsonrpc": "2.0",
  "id": 2,
  "result": {
    "contents": [
      {
        "uri": "file:///project/src/main.rs",
        "mimeType": "text/x-rust",
        "text": "fn main() {\n    println!(\"Hello world!\");\n}"
      }
    ]
  }
}

從官方網站提供的 Flow,也不難理解上述兩個的用途。提供 Resource 清單給 LLM 理解,並且在需要時,透過 read 來取得資源的細節內容。像是取得圖來進行繪畫,取得有哪些資料庫,或 table 的 schema來方便理解需求要哪些欄位與關聯做處理。

當資源內容發生變更時,MCP Server 通過 notifications/resources/updated 通知 MCP Client,RPC 與 HTTP API 的不同就是可以雙向通知。MCP Client 可再次使用 resources/read 方法讀取更新後的資源內容。

Prompt Capability

Prompt 跟 Tools 作用一樣,就是預先設置好的工作流程跟 Prompt 模板,客製化一些場景下可提供給 AI 進行採納的 Prompt,如果需要 AI 客製化回傳某些格式化內容時,可以提供自定義的 Prompt。

Prompt 的 Metadata 如下,主要的有 name,在同一個 server 內必須要是唯一的。然後就是一堆可選的 arguments,每個 argument 內都是一個物件,其中的 name在每個 prompt 中也必須唯一,也可搭配 required 屬性表明是否 Client 端需要提供。最重要的還是 description 要描述的清晰。

{
  name: string;              // Unique identifier for the prompt
  description?: string;      // Human-readable description
  arguments?: [              // Optional list of arguments
    {
      name: string;          // Argument identifier
      description?: string;  // Argument description
      required?: boolean;    // Whether argument is required
    }
  ]
}

跟其他兩個功能類似,一樣要提供 prompts/list 取得所有可用的 prompt 清單與 prompts/get 取得指定prompt的模板內容,兩個主要的endpoint,提供給 MCP client 存取。這裡就不做重複的說明。

而在 Prompt 模板中,我們可以設定 2 種角色(Role)給LLM使用,分別是 userassistant

User role 這個角色代表與系統交互的使用者,也就是提問的人所發出的訊息。需要提供上下文或是問題給 LLM 來產生回應。而 Assistant 則代表 LLM 本身,負責處理使用者的請求並產生回應。主要是根據內部邏輯和 context 進行推理,提供建議或解決方案,能維持這整串對話的連貫性與對上下文的理解。

官方網站給出一個除錯過程的範例。剛好有用到這兩種角色的 Prompt做範本。

debugWorkflow 的物件,主要用於處理錯誤調試的對話流程,輸入參數只有 error ,所以第一個訊息代表使用者(user)發出的,該物件會把 error 整合進去 text 內容中。說明: 用戶報告他們遇到的錯誤,${error} 會被具體的錯誤訊息替換。

接著的訊息則是 assistant,提示 LLM 願意幫助分析錯誤,並詢問用戶已嘗試過的解決方法。最後的對話又回來 user,使用者回應表示他們已經嘗試重新啟動服務,但錯誤仍然存在。這將生成一系列的對話訊息,幫助用戶和助手之間進行有效的溝通,以便更好地診斷和解決問題。這樣的工作流程範本是能預先根據需求定義在 MCP server中的。

const debugWorkflow = {
  name: "debug-error",
  async getMessages(error: string) {
    return [
      {
        role: "user",
        content: {
          type: "text",
          text: `Here's an error I'm seeing: ${error}`
        }
      },
      {
        role: "assistant",
        content: {
          type: "text",
          text: "I'll help analyze this error. What have you tried so far?"
        }
      },
      {
        role: "user",
        content: {
          type: "text",
          text: "I've tried restarting the service, but the error persists."
        }
      }
    ];
  }
};

Server Inspector

MCP 官方有提供 Server Inspector 來協助 MCP 開發者來對 Server 進行簡單的除錯調用。作用類似於 PostMan 或是 BloomRPC 這類的工具。

當我們把 Inspector 啟動後會看到以下畫面。我這裡啟用的是 MCP Grafana sse mode,而 Bearer token 就是 Grafana 中申請到的 Service account 用的 Token。然後點擊 connect 按鈕就能連結至對應的 MCP Sever 了。

此時若 Resource, Prompts, Tools 都有提供 list RPC method,這裡就能使用。

點擊 List Resources 就能看見一些關於能使用的 resource 相關的結構化資訊。或者配置好的 Prompts 與 Tools 也都能在這做簡單的功能測試,確認與 Grafana 之間的資料存取是否正常以及格式正確性。

這是我簡單寫的 Prometheus Resource 的描述。其實主要的還是 uri 和 text 的描述,才能讓 LLM 知道這 resource 的作用。其中 uri datasource:prometheus:prometheus-demo 只是說這是 Grafana datasource,然後 type 是 Prometheus, 而物件名稱則是 prometheus-demo。對應到下圖在 Grafana 中的 data sources 配置就清楚了。更完整點應該是要給出 Grafana Host 但我懶得改Code了。

{
  "contents": [
    {
      "uri": "datasource:prometheus:prometheus-demo",
      "mimeType": "application/json",
      "text": "{\"isDefault\":false,\"name\":\"Prometheus Demo\",\"type\":\"prometheus\",\"uid\":\"prometheus-demo\",\"url\":\"https://prometheus.demo.prometheus.io\"}"
    }
  ]
}

但 Inspector 本身沒有 LLM 的功能,所以整個完整的 end-to-end testing,還是得回到是上一篇提到的 VsCode + Cline。

VsCode with Cline

SSE mode

上一篇我們用的是 stdio 這樣的方式跟 MCP server 做交互,但在企業內部還是會用 sse 的形式加入驗證與授權,且 Cline 的 stdio 並不會去取得 Resources,而 sse mode 會去取得。透過 Add Remote Server 的方式就能加入 mcp server。

加入完成後,就能在 installed 頁籤看見這 mcp server 提供的 Tools 和 Resources。

只是目前有支援 Prompts 的 MCP Client 並不多,Cline 目前只支援 Tools 和 Resources。

OpenRouter

這時免費黨的我,沒錢設備也不夠力跑 local LLM,就想有沒有免費的線上 LLM model 能用,發現了 OpenRouter。今天當下它提供了 56 種免費的 model能使用。

這裡我選擇 Google Gemini 2.5 Pro Exp(Free) ,點擊複製按鈕就能複製模型名稱。

來到 Profile 頁面找到 API Keys 建立一組。

來到 VsCode Cline 照著以下做設定,Provider 選擇 OpenAI Compatible,Base URL 填入 https://openrouter.ai/api/v1,這個在 OpenRouter 的Doc 中有。填入OpenRouter 申請好的 API KEY 以及模型名稱就完成了。

就能在 Cline 中透過 LLM 與 MCP server 做交互了,想說學習 Cline 組出來的 Prompt Request 做學習。找到 OpenRouter 能查看 Activity,但這個其實沒 log 功能,看不到request 內容。

Cloudflare

Cloudflare 剛好有提供 AI Gateway 功能,能看見request 與response。先來建立一組專案與 gateway,這裡選擇 AI Provider 平台是 OpenRoter,此時會產生一組 Endpoint。

來到 Cline 的設定頁面替換掉 Base URL,從 OpenRouter 指向 CloudFlare。

此時,我們的環境之間成為這樣的關係。Cloudflare 會幫我們紀錄 log 之外還能處理驗證以及限流這些的非功能性需求。

對話幾次後,就能在 Cloudflare 看見 Log了。

從以下 Log 能清楚知道,User 就是我們發出去的訊息,包含了使用者送出的文字,以及 MCP Tools / Resources 回傳的資料。而 assistant 則是 AI 提供的推理或對話內容。而 Response 中就都是 assistant 了。

{
  "model": "google/gemini-2.5-pro-exp-03-25:free",
  "messages": [
    {
      "role": "user",
      "content": [
        {
          "type": "text",
          "text": "<task>\n目前有哪些log需要注意的嗎?\n</task>"
        },
        {
          "type": "text",
          "text": "<environment_details>\n# VSCode Visible Files\n../../Library/Application Support/Code/User/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json\n\n# VSCode Open Tabs\ncmd/mcp-grafana/main.go\n../../Library/Application Support/Code/User/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json\ntools/loki.go\n.vscode/launch.json\ntools/datasources.go\nresources/resource.go\nresources.go\nprompts.go_bak\nmcpgrafana.go\nprompts/prompt.go\n../../go/pkg/mod/github.com/mark3labs/mcp-go@v0.13.0/server/server.go\n../../.monica-code/config.json\n../../Library/Application Support/Claude/claude_desktop_config.json\n\n# Current Time\n4/5/2025, 10:14:18 PM (Asia/Taipei, UTC+8:00)\n\n# Current Working Directory (/Users/nathanlu/Projects/mcp-grafana) Files\n.dockerignore\n.gitignore\n.goreleaser.yaml\nCODEOWNERS\ndocker-compose.yaml\nDockerfile\ngo.mod\ngo.sum\ngo.tools.mod\ngo.tools.sum\nimage-tag\nLICENSE\nMakefile\nmcpgrafana_test.go\nmcpgrafana.go\nprompts.go_bak\nREADME.md\nresources.go\ntools_test.go\ntools.go\ncmd/\ncmd/mcp-grafana/\ncmd/mcp-grafana/__debug_bin509465229\ncmd/mcp-grafana/main.go\nprompts/\nprompts/prompt.go\nresources/\nresources/resource.go\ntestdata/\ntestdata/loki-config.yml\ntestdata/prometheus.yml\ntestdata/promtail-config.yml\ntestdata/dashboards/\ntestdata/dashboards/demo.json\ntestdata/provisioning/\ntestdata/provisioning/alerting/\ntestdata/provisioning/alerting/alert_rules.yaml\ntestdata/provisioning/dashboards/\ntestdata/provisioning/dashboards/dashboards.yaml\ntestdata/provisioning/datasources/\ntestdata/provisioning/datasources/datasources.yaml\ntools/\ntools/alerting_test.go\ntools/alerting.go\ntools/dashboard_test.go\ntools/dashboard.go\ntools/datasources_test.go\ntools/datasources.go\ntools/incident_integration_test.go\ntools/incident_test.go\ntools/incident.go\ntools/loki_test.go\ntools/loki.go\ntools/oncall_cloud_test.go\ntools/oncall.go\ntools/prometheus_test.go\ntools/prometheus.go\ntools/search_test.go\ntools/search.go\n\n# Current Mode\nACT MODE\n</environment_details>"
        }
      ]
    },
    {
      "role": "assistant",
      "content": "好的,我將檢查 Grafana 中的 Loki 資料來源,看看是否有需要注意的日誌。\n\n首先,我需要列出 Loki 資料來源以取得其 UID。\n\n<use_mcp_tool>\n<server_name>mcp-grafana</server_name>\n<tool_name>list_datasources</tool_name>\n<arguments>\n{\n  \"type\": \"loki\"\n}\n</arguments>\n</use_mcp_tool>"
    },
    {
      "role": "user",
      "content": [
        {
          "type": "text",
          "text": "[use_mcp_tool for 'mcp-grafana'] Result:"
        },
        {
          "type": "text",
          "text": "[{\"id\":3,\"uid\":\"loki\",\"name\":\"Loki\",\"type\":\"loki\",\"isDefault\":false},{\"id\":1,\"uid\":\"prometheus\",\"name\":\"Prometheus\",\"type\":\"prometheus\",\"isDefault\":true},{\"id\":2,\"uid\":\"prometheus-demo\",\"name\":\"Prometheus Demo\",\"type\":\"prometheus\",\"isDefault\":false}]"
        },
        {
          "type": "text",
          "text": "<environment_details>\n# VSCode Visible Files\ntools/datasources.go\n\n# VSCode Open Tabs\ncmd/mcp-grafana/main.go\n../../Library/Application Support/Code/User/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json\ntools/loki.go\n.vscode/launch.json\ntools/datasources.go\nmcpgrafana.go\n../../go/pkg/mod/github.com/grafana/grafana-openapi-client-go@v0.0.0-20250108132429-8d7e1f158f65/client/datasources/datasources_client.go\nresources/resource.go\nresources.go\nprompts.go_bak\nprompts/prompt.go\n../../go/pkg/mod/github.com/mark3labs/mcp-go@v0.13.0/server/server.go\n../../.monica-code/config.json\n../../Library/Application Support/Claude/claude_desktop_config.json\n\n# Current Time\n4/5/2025, 10:45:15 PM (Asia/Taipei, UTC+8:00)\n\n# Current Mode\nACT MODE\n</environment_details>"
        }
      ]
    },
    {
      "role": "assistant",
      "content": "好的,我已經成功獲取了 Grafana 中的資料來源列表。目前有以下資料來源:\n\n1.  **Loki** (uid: `loki`, type: `loki`) - 用於儲存和查詢日誌。\n2.  **Prometheus** (uid: `prometheus`, type: `prometheus`, 預設) - 用於儲存和查詢指標。\n3.  **Prometheus Demo** (uid: `prometheus-demo`, type: `prometheus`) - 另一個 Prometheus 資料來源,可能是用於演示。\n\n由於您關心的是「log」,我將使用 Loki 資料來源 (uid: `loki`) 來查詢最近的日誌,看看是否有需要注意的內容。我將查詢最近的 50 條日誌記錄。\n\n<use_mcp_tool>\n<server_name>mcp-grafana</server_name>\n<tool_name>query_loki_logs</tool_name>\n<arguments>\n{\n  \"datasourceUid\": \"loki\",\n  \"logql\": \"{}\",\n  \"limit\": 50,\n  \"direction\": \"backward\"\n}\n</arguments>\n</use_mcp_tool>"
    }
  ]
}

總結

目前 Cline 還沒支援 Prompt,所以更實用的 multi-step work flow 還無法在 Cline 上操作。但有了 Cloudflare + OpenRouter 的免費組合,能讓小弟我省去建置本地 LLM 那超高的進入門檻。我會持續關注 Cline,MCP-Grafana 的後續版本。希望有機會能 demo 把 mcp server 跑在 cloudflare 上以及演示 multi-step work flow。

1.7K views

More from this blog

Claude Code 監控秘錄:OpenTelemetry(OTel/OTLP)實戰指南

稟告主公:此乃司馬懿進呈之兵書,詳解如何以 OpenTelemetry 陣法,令臥龍神算之一舉一動盡在掌握,知糧草消耗、察兵器效能、辨戰報異常,使主公運籌帷幄於大帳之中。 為何需要斥候情報? 司馬懿稟告主公: 臥龍神算(Claude Code)乃當世利器,然若無斥候回報,主公便如蒙眼行軍——兵器耗損幾何、糧草消費幾許、哪路斥候出了差錯,一概不知。臣以為,此乃兵家大忌。 無情報之弊,有四: 軍

Feb 19, 202610 min read173
Claude Code 監控秘錄:OpenTelemetry(OTel/OTLP)實戰指南

工程師的 Claude Code 實戰指南:從零開始到高效開發

工程師的 Claude Code 實戰指南:從零開始到高效開發 本文整合 Anthropic 官方 Best Practices 與社群實戰 Tips,帶你由淺入深掌握 Claude Code。 什麼是 Claude Code?為什麼值得學? 如果你還在用「複製程式碼貼到 ChatGPT,再複製答案貼回去」的工作流程,Claude Code 會讓你大開眼界。 Claude Code 是 Anthropic 推出的命令列工具,它直接活在你的 terminal 裡,能夠讀懂你的整個 codeb...

Feb 18, 20265 min read76
工程師的 Claude Code 實戰指南:從零開始到高效開發

System Design Interview Ch 12 Digital Wallet

確立問題與設計範疇 角色對話內容 面試者我們應該只關注兩個數位錢包之間的餘額轉帳操作嗎?我們是否需要擔心其他功能? 面試官讓我們只關注餘額轉帳操作。 面試者該系統需要支援多少 TPS(每秒交易次數)? 面試官讓我們假設是 1,000,000 TPS (每秒 100 萬次交易)。 面試者數位錢包對正確性有嚴格的要求。我們可以假設事務保證 就足夠了嗎? 面試官聽起來不錯。 面試者我們需要證明正確性嗎? 面試官這是一個很好的問題。正確性(Correctness)通常只有在交...

Feb 2, 202610 min read191
System Design Interview Ch 12 Digital Wallet

Claude Code 利用 Event-Driven Hooks 打造自動化開發大腦

在現代 AI 輔助開發中,我們不僅需要 AI 寫程式,更需要它懂規則、記性好,並且能自動處理那些繁瑣的雜事。透過 Claude Code Hooks 機制,我們可以介入 AI 的思考與執行迴圈,實現真正的「人機協作自動化」。 一、 動機與痛點:為什麼你需要介入 AI 的生命週期? 在預設狀態下,Claude Code 雖然強大,但它是「被動」且「無狀態」的,這導致了開發者常遇到以下痛點: 記憶重置 (Session Amnesia): 痛點:每次重啟終端機,AI 就像失憶一樣。 解法:你...

Jan 24, 20266 min read481
Claude Code 利用 Event-Driven Hooks 打造自動化開發大腦
M

MicroFIRE

71 posts