續談 MCP Server︰Resource、Server Inspector

繼上一篇 了解 MCP Go 只談到 Tools,以及 MCP client 與 server 之間的交互過程。這次我們來看看另外兩個功能(Capability) Resource 與 Prompt。
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 URL、Content-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使用,分別是 user 與 assistant。
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。






