測驗:用 FastMCP 打造你的第一個 MCP Server
共 5 題,點選答案後會立即顯示結果
1. 在 FastMCP 裡,@mcp.tool 這個裝飾器的主要作用是什麼?
2. 以下這段工具程式碼,FastMCP 會拿 docstring """把兩個整數相加""" 來做什麼?
3. 文章說「型別註解在 FastMCP 裡是真的驗證規則」。這句話的意思最接近下列哪一個?
4. 你執行 python my_server.py(server 內是純 mcp.run()),畫面停住、沒有印出網址。這代表什麼?
5. 想「不開子程序、不走網路」在同一支程式裡驗收 server 有沒有正確暴露工具,最適合用哪種方式?
系列第 2 篇,共 4 篇|難度:L2-進階
前置:已讀本系列 #01(MCP 核心概念與原語)、看得懂基本 Python 型別註解。
上一篇你搞懂了 MCP 的核心原語:Tool(工具)/Resource(資源)/Prompt(提示),以及 client 與 server 怎麼透過協定溝通。
這一篇要動手了。不過對 Vibe Coder 來說,重點不是「背 API」,而是:AI 幫你生了一個 MCP Server,你要看得懂它在幹嘛、判斷它有沒有寫對、能不能收。
我們用的框架是 FastMCP——目前寫 MCP Server 最主流的 Python 框架(官方文件 gofastmcp.com,套件 fastmcp)。
一句話說明
FastMCP 讓你「把一個普通 Python 函式,變成 AI 能呼叫的工具」,schema、驗證、文件全自動生成。
30 秒範例:一個能跑的 Server
先安裝(建議用 uv):
uv add fastmcp
然後這就是一個完整可運作的 MCP Server,存成 my_server.py:
from fastmcp import FastMCP
mcp = FastMCP("My MCP Server")
@mcp.tool
def add(a: int, b: int) -> int:
"""把兩個整數相加"""
return a + b
if __name__ == "__main__":
mcp.run()
Code language: JavaScript (javascript)這段代碼做了什麼:
- 建立一個叫
My MCP Server的 server 實例 - 用
@mcp.tool把add這個普通函式「註冊」成一個 AI 能呼叫的工具 mcp.run()啟動 server,等 client 來連
跑起來:
python my_server.py
Code language: CSS (css)看起來什麼都沒發生(游標停在那)——這是對的。因為預設是 stdio 傳輸(下面會解釋),它在等別人透過標準輸入輸出跟它講話,不是一個網頁伺服器。按 Ctrl+C 結束。
逐行翻譯
from fastmcp import FastMCP # 從 fastmcp 套件拿出 FastMCP 這個類別
mcp = FastMCP("My MCP Server") # 建一台 server,取名 My MCP Server
@mcp.tool # 「把下面這個函式登記成一個工具」
def add(a: int, b: int) -> int: # 工具叫 add,收兩個整數,回傳一個整數
"""把兩個整數相加""" # 這句話會變成工具的「說明」給 AI 看
return a + b # 實際邏輯:相加
if __name__ == "__main__": # 「這個檔案被直接執行時才跑」
mcp.run() # 啟動 server(預設 stdio 傳輸)
Code language: PHP (php)核心:@mcp.tool 到底做了什麼
這是整篇最重要的一段。當 AI 幫你寫 MCP Server,99% 的代碼長得就是「一個函式 + @mcp.tool」。你要看懂函式的長相怎麼決定工具的長相。
FastMCP 在你註冊工具時,自動做四件事:
| 你寫的東西 | FastMCP 自動生成 |
|---|---|
函式名稱 add |
工具名稱 add |
docstring """把兩個整數相加""" |
工具的說明(description) |
參數 + 型別註解 a: int, b: int |
輸入參數的 schema(叫什麼、什麼型別、必填與否) |
回傳註解 -> int |
輸出的 schema |
換句話說,你不用手寫任何 JSON schema。你把函式簽章寫清楚,schema 就生出來了。這就是「FastMCP」快的原因。
型別註解不是裝飾,是合約
對一般 Python,型別註解 a: int 只是給人看的提示,寫錯了也照跑。但在 FastMCP 裡,型別註解會變成真正的驗證規則。
@mcp.tool
def add(a: int, b: int) -> int:
return a + b
Code language: CSS (css)翻譯:「這個工具叫 add,一定要給我兩個整數 a 和 b。」如果 AI client 傳了個文字 "abc" 進來,FastMCP 會擋下來、回報錯誤,而不是讓你的函式炸掉。
所以看到 AI 生的工具時,先看型別註解有沒有寫齊——這是它有沒有認真做的第一個訊號。
常見變化:AI 可能這樣寫
變化 1:裝飾器帶參數 @mcp.tool(...)
@mcp.tool(
name="find_products",
description="搜尋商品目錄,可依分類過濾",
tags={"catalog", "search"},
)
def search(query: str, category: str | None = None) -> list[dict]:
...
Code language: PHP (php)翻譯:不想用函式名稱和 docstring 當工具名/說明時,可以在裝飾器裡「手動指定」。@mcp.tool(不加括號)和 @mcp.tool(...)(加括號帶參數)兩種都對,不是誰新誰舊,看它要不要客製化而已。
變化 2:選填參數=有預設值
@mcp.tool
def search(query: str, max_results: int = 10) -> list[dict]:
...
Code language: CSS (css)翻譯:query 沒有預設值 → 必填;max_results = 10 有預設值 → 選填,不給就用 10。這跟一般 Python 函式規則一模一樣,FastMCP 直接沿用。
變化 3:用 Pydantic model 收複雜參數
當一個工具要收「一包結構化資料」,AI 常會定義一個 Pydantic model:
from pydantic import BaseModel
class User(BaseModel):
name: str
age: int = 18
@mcp.tool
def create_user(user: User) -> str:
return f"建立了 {user.name},年齡 {user.age}"
翻譯:「這個工具收一個 user,裡面一定要有 name(文字),可以有 age(數字,預設 18)。」FastMCP 會把這個 model 展開成巢狀的 schema,client 要傳 {"user": {"name": "Alice"}} 這種 JSON 物件進來。
認得就好(舊寫法):
Optional[float]= 現在的float | None;List[dict]= 現在的list[dict]。AI 維護舊專案時可能出現,意思一樣。
變化 4:換傳輸方式(stdio vs http)
if __name__ == "__main__":
mcp.run() # 預設:stdio(本機、給桌面 client 用)
# 或
mcp.run(transport="http", port=8000) # http:可被遠端連
Code language: PHP (php)翻譯:
- stdio:server 當成一個子程序被啟動,透過標準輸入輸出溝通。Claude Desktop、Cursor 這類桌面工具用的就是這種。本機開發預設用這個。
- http:把 server 掛成一個網路服務,別人用網址連。要遠端部署才需要。
本篇我們只用 stdio。看到 AI 沒指定 transport(純 mcp.run()),就是 stdio,正常。
回傳值會被怎麼處理
工具回傳的東西 FastMCP 會幫你「序列化」成 client 收得到的格式:
- 回傳
int/str/bool→ 直接變成對應的值 - 回傳
dict/list→ 變成 JSON 結構 - 回傳 Pydantic model → 攤平成 JSON 物件
你不用自己 json.dumps()。看到 AI 在工具裡手動把結果轉成 JSON 字串再回傳,通常是多此一舉(見紅旗)。
🚩 紅旗:看到這些要警覺
🚩 1:工具沒有型別註解
@mcp.tool
def add(a, b): # 🚩 沒型別!AI client 根本不知道該傳什麼
return a + b
Code language: PHP (php)為什麼危險:沒有型別,生出來的 schema 就是「隨便什麼都行」,FastMCP 幫不了你驗證,AI 也更容易亂傳參數。
跟 AI 這樣說:「幫每個工具的參數和回傳值都補上型別註解,讓 FastMCP 能生出正確的 schema。」
🚩 2:工具沒有 docstring(或說明很爛)
@mcp.tool
def proc(x: str) -> str: # 🚩 工具叫 proc、沒說明,AI 看不懂這是幹嘛的
...
Code language: CSS (css)為什麼危險:MCP 工具是給 AI 讀的。名字含糊、沒說明,AI 就不知道什麼時候該用這個工具,等於白做。
跟 AI 這樣說:「每個工具都要有清楚的名稱和 docstring,說明它做什麼、什麼時候該用、參數是什麼意思。」
🚩 3:危險操作沒有任何防護
@mcp.tool
def run_shell(cmd: str) -> str: # 🚩 讓 AI 直接下任意 shell 指令
import subprocess
return subprocess.run(cmd, shell=True, capture_output=True).stdout.decode()
Code language: PHP (php)為什麼危險:你把一把「能執行任意指令」的槍交給 AI。一旦這個 server 接上會亂呼叫工具的 client,等於門戶大開。MCP 工具是 AI 能主動觸發的能力,破壞性操作要特別小心。
跟 AI 這樣說:「這個工具會執行任意指令,風險太高。改成只允許白名單內的操作,或加上明確的參數限制。」
🚩 4:工具把錯誤默默吞掉
@mcp.tool
def fetch(url: str) -> str:
try:
return download(url)
except Exception:
return "" # 🚩 失敗了卻回空字串,AI 以為成功
Code language: CSS (css)為什麼危險:工具失敗時回一個看起來正常的空值,AI(和你)都不會知道出事了,只會拿到一堆莫名其妙的結果。
跟 AI 這樣說:「工具失敗時應該讓錯誤往上拋或回傳清楚的錯誤訊息,不要用空字串掩蓋失敗。」
Vibe Coder 驗收檢查點
AI 交給你一個 MCP Server,別只是「看起來對」。用下面的方法實際驗它有沒有正確暴露工具。
方法 A:寫個 5 行 client 自己呼叫(最推薦)
FastMCP 內建 client,可以「在同一個程式裡」直接連上你的 server,不用開子程序、不用網路。這是最快、最穩的驗收方式。把 server 存成 my_server.py,另存一個 check.py:
import asyncio
from fastmcp import Client
from my_server import mcp # 直接把 server 物件拿進來
async def main():
async with Client(mcp) as client: # 開連線(in-memory,不用網路)
tools = await client.list_tools() # 列出所有工具
print("工具清單:", [t.name for t in tools])
result = await client.call_tool("add", {"a": 2, "b": 3}) # 實際呼叫
print("add(2, 3) =", result.data)
asyncio.run(main())
Code language: PHP (php)跑 python check.py,預期看到:
工具清單: ['add']
add(2, 3) = 5
Code language: JavaScript (javascript)- [ ]
list_tools()有列出你期待的每一個工具名稱 - [ ]
call_tool()回傳的結果正確
看到這兩個,就代表工具確實有被正確暴露。
方法 B:用 CLI 快速看 server 有什麼
不想寫 code,直接問 server「你有哪些工具」:
fastmcp inspect my_server.py
Code language: CSS (css)它會印出 server 名稱、工具清單、每個工具的參數 schema。
- [ ] 輸出裡的工具數量、名稱、參數型別,跟你要的一致
方法 C:用 MCP Inspector 視覺化點點看
MCP 官方有一個網頁版測試工具 Inspector,可以用表單填參數、按按鈕呼叫工具、看回傳:
fastmcp dev inspector my_server.py
Code language: CSS (css)它會啟動 server 並開一個瀏覽器介面(連線方式選 STDIO 後按 connect)。
註:不同 FastMCP 版本 CLI 略有差異,舊版可能是
fastmcp dev my_server.py。指令跑不動時,先fastmcp --help看你的版本支援哪個。
- [ ] 在 Inspector 裡看得到工具、能填參數呼叫、回傳符合預期
看不懂就這樣問 AI
「用白話一段一段解釋這個 FastMCP server 檔案在幹嘛,每個
@mcp.tool分別暴露了什麼工具、收什麼參數、回傳什麼,我是初學者。」
「這個 MCP Server 裡有沒有危險的工具(例如能執行任意指令、刪檔、打外部 API)?各自的風險是什麼?」
「幫我寫一個最小的 FastMCP client 腳本,連上這個 server、列出所有工具、並各呼叫一次,讓我驗收。」
小結
- FastMCP = 把普通 Python 函式變成 AI 工具,
@mcp.tool是核心。 - 函式簽章決定工具長相:名稱 → 工具名、docstring → 說明、型別註解 → 參數 schema、回傳註解 → 輸出 schema。
- 型別註解在這裡是真的驗證規則,不是裝飾——先看它有沒有寫齊。
- stdio 是本機開發預設,看到純
mcp.run()就是它。 - 驗收靠實際呼叫:
Client(mcp)寫 5 行自己列工具、呼叫工具,比「看起來對」可靠得多。
下一篇(#03)我們會讓工具真的做點有用的事,並把 server 接上真實的 MCP client。
進階測驗:用 FastMCP 打造你的第一個 MCP Server
共 5 題,包含情境題與錯誤診斷題。