前幾篇把 Claude API 的工具使用、RAG 和提示快取一路接起來。到了 MCP,問題換成了另一個角度:如果每一個外部服務都要自己寫 schema、函式和維護流程,應用程式很快就會長成一團整合程式碼,這些工具能不能被整理成可重複使用的介面?
這次打開 Claude Academy 的 Building with the Claude API,也回到 Claude Platform 對照 API 的位置。本篇涵蓋官方 Model Context Protocol 的第 47–56 堂,共 10 堂課與 Quiz 6;課程用同一個 CLI 聊天機器人專案,同時建立 MCP server 和 client,走過 tools、resources、prompts 三條路徑。
章節課程地圖
Model Context Protocol 的第 47–56 堂,沿著同一個 CLI 聊天機器人專案,同時修改 mcp_server.py、mcp_client.py 和 main.py,把三種 MCP primitives 逐步接起來。
| 課程 | 加進 mcp_server.py | 加進 mcp_client.py/main.py |
|---|---|---|
| 47–49 | MCP 架構、server/client 分工與專案骨架 | CLI 聊天機器人與連線準備 |
| 50 | docs 字典、read_doc_contents、edit_document 兩個工具 | — |
| 51 | 不改程式碼,使用 mcp dev 測試工具 | — |
| 52 | — | MCPClient、list_tools()、call_tool(),接上 tool-use 迴圈 |
| 53 | list_docs、fetch_doc 兩個 resources | — |
| 54 | — | read_resource(),加入 @文件名 自動帶入文件內容 |
| 55 | format prompt:預寫的 Markdown 格式化指令 | — |
| 56 | — | list_prompts()、get_prompt() 與斜線指令選單 |
Model Context Protocol
47. Introducing MCP(介紹 MCP)
MCP(Model Context Protocol)是一層通訊協定,讓 Claude 取得外部工具與上下文。它處理的重點,是把工具的定義與執行責任交給專門的 MCP server,應用程式可以透過 client 使用這些能力。
官方用 GitHub 做了一個很容易理解的例子:使用者想問「我所有 repository 裡有哪些開放中的 pull request?」。如果每一項 GitHub 功能都直接塞進聊天應用程式,儲存庫、PR、issue、project 都要各自寫 schema、函式、測試和維護邏輯。MCP 把這些能力放在專用 server 裡,應用程式透過標準介面使用它們。
課程把三個常見問題整理成這張表:
| 問題 | 課程中的回答 |
|---|---|
| 誰寫 MCP server? | 任何人都可以,通常由服務提供商製作官方實作,例如 AWS 為自己的服務發布 MCP server |
| 跟直接呼叫 API 有什麼差別? | MCP server 已經定義好 schema 與函式;直接呼叫 API 時,這些定義由你的應用程式負責 |
| MCP 和 tool use 是同一件事嗎? | 兩者互補;MCP 關注的是工具由誰建立、維護與提供 |
我會把它記成「工具的供應鏈重組」:Claude 仍然可以提出工具請求,但工具怎麼被定義、放在哪裡執行,以及由誰長期維護,被拆到另一個可以獨立演進的 server。
48. MCP clients(MCP 客戶端)
MCP client 是你的應用程式和 MCP server 之間的橋。它負責處理訊息傳遞與協定細節,讓主程式不必自己拼裝每個外部服務的通訊格式。
MCP 的傳輸層與應用程式邏輯分開。client 和 server 可以透過不同方式溝通;最常見的設定是兩者在同一台機器上,透過標準輸入/輸出(stdio)通訊,也可以使用 HTTP、WebSockets 或其他網路協定。
最基本的兩組訊息是:
| 訊息 | 作用 |
|---|---|
ListToolsRequest / ListToolsResult | client 詢問 server 提供哪些工具,取得工具清單 |
CallToolRequest / CallToolResult | client 要求 server 用指定參數執行某個工具,取得執行結果 |
完整流程可以先畫成這樣:
使用者 → 你的伺服器 → MCP client → MCP server(真的打 GitHub)→ 原路送回 ↑ Claude 只跟你的伺服器講話假設使用者問「我有哪些 repository?」:你的伺服器先從 MCP client 取得工具清單,再把問題和工具送給 Claude。Claude 判斷要用哪個工具後,你的伺服器把請求交給 client,client 再交給 MCP server。GitHub 的結果沿著原路回來,最後由你的伺服器把結果交給 Claude 整理。
這張圖的重點是責任邊界:Claude 不直接碰 GitHub;它只和你的伺服器溝通,伺服器再透過 MCP client 使用外部能力。
49. Project setup(專案設定)
課程接著建立一個 CLI 聊天機器人,讓使用者透過命令列和一組文件互動。專案裡有兩個元件:處理使用者互動的 MCP client,以及管理文件操作的自訂 MCP server。文件先放在記憶體裡,不使用資料庫。
這個範例同時實作 client 和 server,主要是為了把完整流程走過一次。實際專案通常會依角色選擇其中一邊:做 server,是把自己的服務公開給其他開發者;做 client,則是連到已經存在的 MCP server。
設定完成後,課程提供兩種啟動方式:
uv run main.py # 建議python main.py # 標準 Python這堂最值得留下來的習慣,出現在真正開始加工具之前。先問一個你能立刻驗證的問題,例如「1+1 等於多少?」確認回覆確實是 2,再問「文件裡寫了什麼?」。第二個問題在文件工具還沒建立以前,不可能真的讀到文件;模型回什麼,都是之後加入工具前可以對照的基準。
這個步驟很小,卻把驗證成本和出錯代價接在一起:如果金鑰、相依套件或聊天迴圈一開始就有問題,後面每一堂都會被錯誤拖著走。先用已知答案建立基準,後續每加一層就有東西可以比較。
50. Defining tools with MCP(使用 MCP 定義工具)
Python SDK 把定義工具的工作縮短到 decorator、type hints 和 Field。你不必手寫完整的 JSON schema,SDK 會從函式簽名和參數描述產生 Claude 需要的結構。
初始化 MCP server:
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("DocumentMCP", log_level="ERROR")文件先用一個 dictionary 放在記憶體裡,key 是文件 ID,value 是文件內容。
讀取工具的定義如下:
@mcp.tool( name="read_doc_contents", description="Read the contents of a document and return it as a string.")def read_document(doc_id: str = Field(description="Id of the document to read")): if doc_id not in docs: raise ValueError(f"Doc with id {doc_id} not found") return docs[doc_id]另一個 edit_document 工具則要求 doc_id、要尋找的文字,以及要替換成的新文字;完整實作可以直接看 第 50 堂的 commit。
這段設計有三個實用細節:函式名稱和 description 讓 Claude 知道工具用途,Field 描述參數的意義,ValueError 則把「找不到文件」這件事變成模型可以理解的錯誤訊息。相較於只回傳一個模糊的 server error,具體錯誤讓 Claude 有機會修正下一次呼叫的參數。
51. The server inspector(伺服器檢查工具)
MCP server 還沒接進完整應用程式前,可以先用 Python MCP SDK 內建的瀏覽器版 inspector 單獨測試:
mcp dev mcp_server.py它會啟動一個開發伺服器,預設使用 port 6277,並提供本機 URL 開啟 MCP Inspector。操作流程是先按 Connect 啟動 server,再到 Tools → List Tools,選取工具、填入參數,最後按 Run Tool 看回傳結果。
Inspector 也能把操作串起來驗證:先用 edit_document 修改文件,再用 read_doc_contents 讀回來,確認兩個工具之間真的共享同一份資料。
官方提醒 Inspector 仍在積極開發,畫面可能和課程截圖不同。這個提醒很重要:可依賴的是它提供獨立測試 tools、resources、prompts 的能力,畫面配置和細節則要以目前版本為準。
52. Implementing a client(實作客戶端)
MCP client 可以拆成兩層:
| 元件 | 來源 |
|---|---|
| MCP Client | 自己建立的類別,讓 session 的使用方式更容易管理 |
| Client Session | MCP Python SDK 提供的實際連線 |
多包一層自訂類別的理由,是把 session 的資源清理集中管理。CLI 對 MCP server 的核心工作只有兩件事:取得工具清單,以及在 Claude 要求時呼叫工具。
async def list_tools(self) -> list[types.Tool]: result = await self.session().list_tools() return result.tools
async def call_tool(self, tool_name: str, tool_input: dict) -> types.CallToolResult | None: return await self.session().call_tool(tool_name, tool_input)把完整流程接起來後,資料會依序經過:取工具清單、把使用者問題送給 Claude、Claude 決定呼叫 read_doc_contents、client 執行工具、結果回到 Claude,最後才產生回覆。這個 client 的程式碼比我原本預期的小:兩個 async 方法,加上一個負責清理的 context manager,連線與協定的複雜度大多由 SDK 處理。
53. Defining resources(定義資源)
工具適合執行動作;資源則適合向 client 公開資料。課程用 HTTP 的 GET handler 來類比 resources:你想取得資訊時讀取資源,需要改變狀態時才呼叫工具。
文件提及功能就是一個好例子。使用者輸入 @ 時,需要先拿到所有可用文件供自動完成;選定文件後,再依 URI 取得特定文件內容。資源的 URI 就像資料的位址。
MCP 有兩種資源:
| 類型 | 特徵 | 例子 |
|---|---|---|
| 直接資源 | 固定、不帶參數的 URI | docs://documents |
| 範本化資源 | URI 裡帶參數 | docs://documents/{doc_id} |
Python SDK 會從範本化 URI 解析參數,再以關鍵字引數傳給函式:
@mcp.resource("docs://documents", mime_type="application/json")def list_docs() -> list[str]: return list(docs.keys())
@mcp.resource("docs://documents/{doc_id}", mime_type="text/plain")def fetch_doc(doc_id: str) -> str: if doc_id not in docs: raise ValueError(f"Doc with id {doc_id} not found") return docs[doc_id]mime_type 是給 client 的提示,告訴它回傳內容應該如何理解。SDK 會依回傳型別自動序列化,程式碼不用自己把資料轉成 JSON 字串。
54. Accessing resources(存取資源)
client 這一側要加上 read_resource:
import jsonfrom pydantic import AnyUrl
async def read_resource(self, uri: str) -> Any: result = await self.session().read_resource(AnyUrl(uri)) resource = result.contents[0]回應裡有一個 contents 清單,通常先取第一個元素,再依 MIME type 決定解析方式:
if isinstance(resource, types.TextResourceContents): if resource.mimeType == "application/json": return json.loads(resource.text) return resource.text使用者輸入 @report.pdf 這份文件在講什麼? 時,client 會先完成文件選擇,再把資源內容放進送給 Claude 的提示。這和讓 Claude 另外呼叫一個讀檔工具的流程不同:文件已經在提示裡,模型收到上下文後就能直接回答。
一次提及多份文件時,也能把比較資料一併注入:
You: @report.pdf 這份文件在講什麼? @financials.docx 跟 @outlook.pdf 有什麼差異?多份文件注入後,文件內容會直接進入提示,Claude 不需要再透過另一輪工具呼叫取回它們。
55. Defining prompts(定義提示)
MCP 的 prompts 是 server 作者預先寫好、測試過的高品質指令。使用者仍然可以自己輸入「把 report.pdf 轉成 Markdown」,但如果 server 已經把格式、結構和輸出要求整理成一份可重複呼叫的 prompt,結果比較容易維持一致。
這個 prompt 會回傳一組 user/assistant 訊息,client 取得後可以直接送給 Claude。完整的 format prompt 與 edit_document 呼叫方式放在 第 55 堂的 commit,文章保留它的設計重點就好:資源提供內容,prompt 提供經過測試的任務指令,工具負責真的修改文件。
這段的關鍵在於 prompt 會明確告訴 Claude 下一步可以使用 edit_document。提示、工具和資源因此形成一組設計。
Inspector 的 Prompts 區段可以選擇 prompt、填入參數,直接查看展開後要送給 Claude 的訊息。測試不同輸入時,這個畫面能確認變數真的插入指令,也能確認 prompt 和 server 的工具是否配得起來。
56. Prompts in the client(客戶端中的提示)
client 需要實作兩個方法,分別列出可用的 prompts,以及依名稱和參數取得某一個 prompt:
async def list_prompts(self) -> list[types.Prompt]: result = await self.session().list_prompts() return result.prompts
async def get_prompt(self, prompt_name, args: dict[str, str]): result = await self.session().get_prompt(prompt_name, args) return result.messages伺服器端 prompt 函式的參數,會對應到 client 呼叫 get_prompt 時傳入的 dictionary key。CLI 裡的使用者體驗則像 slash command:輸入斜線,看到可用指令,選取 prompt,再填入文件 ID,完整 prompt 就會送給 Claude。
以 /format financials.docx 為例,使用者只選一個指令和文件,剩下的工具順序由 Claude 依 prompt 內容完成。
走到這裡,我會把 /指令 記成 get_prompt:server 把一段測過的對話開場白交給 client,Claude 再從這個起點繼續使用工具。
Course Quiz 6(課程測驗)
有 6 題。官方頁面本身是繁中,以下保留實際題目與正確答案。
1. 您想為您的 MCP 伺服器建立一個讀取文件內容的工具。使用 Python SDK,定義此工具最簡單的方法是什麼?
答案:在函式上使用 @mcp.tool 裝飾器
2. 您正在建立一個文件系統,使用者可以輸入 @document_name 來引用檔案。哪個 MCP 功能最適合用於公開文件內容?
答案:Resources
3. 您想為使用者提供一個高品質、經過預先測試的文件格式化指令。您應該使用哪個 MCP 功能?
答案:Prompts
4. 您正在建立一個需要存取 GitHub 資料的聊天機器人。使用 MCP 而不是自行編寫 GitHub 整合的主要好處是什麼?
答案:MCP 會為您處理工具定義和執行
5. 您已經建立了一個 MCP 伺服器,並想在將工具連接到 Claude 之前測試您的工具。最好的測試方式是什麼?
答案:在瀏覽器中使用 MCP Inspector
6. 您的 MCP 伺服器和客戶端需要進行通訊。在開發過程中,它們最常見的連接方式是什麼?
答案:透過同一台機器上的標準輸入/輸出
實作地圖:每堂課一個 commit
完整程式碼放在 claude-academy-api-app。
| 課程 | 主題 | 實作內容 | Commit |
|---|---|---|---|
| 47 | Introducing MCP | MCP 架構與工具供應鏈概念 | — |
| 48 | MCP clients | client、server 與訊息流程 | — |
| 49 | Project setup | 建立 CLI 聊天機器人骨架(REPL 迴圈) | df2b5c7 |
| 50 | Defining tools with MCP | docs dictionary,以及 read_doc_contents、edit_document 兩個工具 | b1b4d4a |
| 51 | The server inspector | 以 mcp dev 測試第 50 堂加入的工具 | — |
| 52 | Implementing a client | MCPClient 類別、list_tools()、call_tool(),main.py 接上 tool-use 迴圈 | 2e8e584 |
| 53 | Defining resources | list_docs、fetch_doc,以及兩種 docs:// URI | f94219c |
| 54 | Accessing resources | read_resource() 與 @文件名 自動注入 | c895bc0 |
| 55 | Defining prompts | format prompt:預寫的 Markdown 格式化指令 | 6fa7e9f |
| 56 | Prompts in the client | list_prompts()、get_prompt() 與斜線指令選單 | 90a9f65 |
小結
這堂課最像在看一個 MCP 專案慢慢長出來:先把 client 和 server 的骨架接起來,再一個功能一個功能往上疊,從 tools 到 resources、prompts,讓我看懂 MCP 接進 CLI 之後,兩邊的訊息怎麼流動。
我覺得更值得留下來的是課程的開發順序。它採用交叉開發:先在 server 加上一個能力,再用 MCP Inspector 單獨測試,確認可以運作後,回到 client 把它串起來,接著才進下一個功能。一次只走完一條完整路徑,出了問題比較容易知道是哪一段造成的,也能理解為什麼這一堂先改 server,下一堂再回 client。
上到前面幾堂時,我一度覺得這些內容好像已經學過了。回頭和第八天的〈Model Context Protocol 簡介:從零打造 MCP 客戶端與伺服器〉比對,我粗略估計重疊程度大概有 90%。但跟著課程章節一路開發,把整個流程實際跑通,直到 client 和 server 真的串通,理解會深很多,也更有感。
所以我會建議至少跟著這門 MCP 課程完整走過一次。它適合邊看邊寫;把每一段實際接起來之後,才會比較清楚每個元件各自負責什麼。