測驗:MCP 部署與整合
共 5 題,點選答案後會立即顯示結果
1. 為什麼 stdio 傳輸無法用來讓遠端機器連上你的 MCP server?
2. 在 FastMCP 2.x 中,要把 server 切換成對外的 Streamable HTTP 傳輸,最正確的寫法是?
3. 根據文章,為什麼「對外的 HTTP MCP server 一定要加授權」,但 stdio 時代通常不用煩惱?
4. 在 MCP 2026 授權規格中,你自己寫的 MCP server 扮演什麼角色?
5. 用 Claude Code 註冊一個「遠端 HTTP」MCP server,正確的指令是?
系列第 4 篇(共 4 篇)|難度:L2-進階
前置:需先讀過第 1~3 篇(MCP 概念、用 FastMCP 寫 server、tools/resources/prompts),並具備基本 HTTP 與部署概念
一句話說明
前三篇你的 server 一直躲在本機、用 stdio 跟客戶端講話;這篇教你把它「搬上網」——切換成 HTTP 傳輸、加上基本的授權保護,最後真的接到 Claude Code / Claude Desktop 上跑通一輪。重點不是叫你會寫這些,而是看懂 AI 幫你改的部署 code,並知道哪裡最容易出包。
先對齊:這篇在解決什麼問題
第 2 篇我們寫的 server 結尾大概長這樣:
if __name__ == "__main__":
mcp.run() # 沒指定 transport,預設就是 stdio
Code language: PHP (php)stdio(standard input/output)的意思是:客戶端直接把你的 Python 程式當子程序啟動,用標準輸入輸出這條管子傳訊息。這在本機很爽——不用開埠、不用管網路——但它有個硬限制:客戶端必須跟 server 在同一台機器,因為它是靠「啟動一個本地程序」在溝通的。
一旦你想讓 server 跑在一台雲主機、讓多個人或多台機器連進來,stdio 就不夠了。這時要換成 HTTP 傳輸。
看到這篇的 code 裡出現
transport="http"、host、port、/mcp路徑、JWKS、Bearer這些字,就是在講「把 server 對外服務」這件事。
傳輸方式:stdio vs Streamable HTTP
FastMCP 2.x 支援兩種你會實際用到的傳輸:
# 1) 本機:預設 stdio
mcp.run() # 等同 mcp.run(transport="stdio")
# 2) 對外:Streamable HTTP
mcp.run(transport="http", host="127.0.0.1", port=8000, path="/mcp")
Code language: PHP (php)逐行翻譯第 2 種:
transport="http":改用 Streamable HTTP 傳輸(透過一般的 HTTP 請求+伺服器推送來傳訊息)。host="127.0.0.1":只綁在本機回環位址(等一下的紅旗會講這個坑)。port=8000:對外聽這個埠。path="/mcp":MCP 的端點掛在/mcp這條路徑,所以完整網址是http://127.0.0.1:8000/mcp。
一句話記住差別:
| stdio | Streamable HTTP | |
|---|---|---|
| 誰啟動 server | 客戶端把它當子程序 spawn | 你自己先把它跑起來當服務 |
| 位置 | 必須同一台機器 | 可以跨網路 |
| 要開埠嗎 | 不用 | 要(host/port/path) |
| 適合 | 本機工具、開發測試 | 正式對外、多人共用 |
✅ 必看懂:transport="http" 代表這 server 要對外了,接下來所有安全問題都跟著來。
🕰️ 認得就好(舊寫法):你可能會在舊 code 看到 transport="sse"(Server-Sent Events)或 transport="streamable-http"(完整名稱)。sse 是上一代、已被取代的傳輸;streamable-http 則是 http 的正式全名,兩者等價。AI 若幫你維護舊專案可能寫成這樣,看到不用慌,但新專案一律用 "http"。
部署考量:對外服務要多想三件事
把 mcp.run(transport="http", ...) 跑起來,它其實是啟動了一個 ASGI web 服務(底層是 Uvicorn/Starlette)。要真的「對外」,你會遇到三個經典問題。
1. 綁 127.0.0.1 還是 0.0.0.0?
# 只有本機連得到(外面連不進來)
mcp.run(transport="http", host="127.0.0.1", port=8000)
# 讓機器的網路介面都能連進來(雲主機/容器裡通常要這個)
mcp.run(transport="http", host="0.0.0.0", port=8000)
Code language: PHP (php)127.0.0.1 = 「只接受這台機器自己的連線」;0.0.0.0 = 「接受來自任何網卡的連線」。在容器或雲主機裡忘了改成 0.0.0.0,就會出現「本機 curl 得到、外面死活連不上」的鬼打牆。
2. 前面通常還有一層反向代理
正式環境很少讓 Python 服務直接曝露在公網,前面一般擺 nginx / Caddy / Traefik 這類反向代理,負責 HTTPS 憑證、綁網域、擋流量。你的 FastMCP 只需在內部聽一個埠(例如 127.0.0.1:8000),代理再把 https://mcp.yourcompany.com/mcp 轉進來。
反向代理有個 MCP 專屬的坑:Streamable HTTP 會用到伺服器持續推送(串流)。如果代理把回應緩衝起來(buffering)等收完才吐給客戶端,串流就壞了。nginx 記得對這條路徑關掉緩衝:
location /mcp {
proxy_pass http://127.0.0.1:8000;
proxy_buffering off; # 🚩 關鍵:不然串流會卡住
proxy_read_timeout 3600s; # 長連線別太快被切
}
Code language: PHP (php)3. 有狀態 vs 無狀態
FastMCP 的 HTTP 預設是有狀態的(server 記得每個連線的 session)。如果你要在多台機器後面做負載平衡、或想部署成 serverless,通常會改成無狀態模式:
mcp = FastMCP("my-server", stateless_http=True)
Code language: PHP (php)stateless_http=True 白話講就是「每個請求各自獨立、server 不記得上一個」,這樣任何一台機器都能處理任何請求,好水平擴展。知道有這選項就好,一開始不用碰。
授權與安全:對外的 server 一定要有門
這是整篇最重要的一段。stdio 時代不太需要煩惱授權,因為能啟動你程序的人本來就是本機使用者。一旦換成 HTTP、掛上公網,情況完全反過來:
沒加保護的 HTTP MCP server = 你把「能執行這些工具」的能力公開在網路上,任何知道網址的人都能呼叫。如果你的工具會查資料庫、寄信、動檔案,這就是災難。
MCP 2026 規格怎麼看授權
2026 版的 MCP 授權建立在 OAuth 2.1 上,核心角色分工是這樣:
- 你的 MCP server = Resource Server(資源伺服器):只負責「驗票」,不負責發票。
- Authorization Server(授權伺服器)= 另一個身分供應商(IdP):像 Auth0、WorkOS、GitHub、Google,負責登入使用者、發 token。
幾個你在 code 或設定裡會看到、值得認得的規格名詞:
- Protected Resource Metadata(RFC 9728):server 會提供一個
/.well-known/oauth-protected-resource端點,等於門口貼張告示:「要進來?去這個授權伺服器拿票」。客戶端沒帶票時,server 回401並附上這個資訊,客戶端就知道該去哪登入。 - Resource Indicators(RFC 8707):客戶端拿票時要註明「這張票是要用在哪個 server」,發出來的 token 就被綁定到你的 server(audience 綁定)。這是為了防止「拿去 A server 的票被偷渡到 B server 用」的混淆代理(confused deputy)攻擊。
你不需要背這些 RFC 號碼,但要能認得:授權伺服器和資源伺服器是分開的,token 要綁定到特定 server,這是新規格的兩個主旋律。
FastMCP 怎麼加驗證
最務實的入門作法是「驗 Bearer JWT」:客戶端每個請求帶一個 Authorization: Bearer <token>,你的 server 拿 IdP 公開的金鑰(JWKS)去驗這張 token 是不是真的、有沒有過期、audience 對不對。
from fastmcp import FastMCP
from fastmcp.server.auth.providers.jwt import JWTVerifier
verifier = JWTVerifier(
jwks_uri="https://your-idp.example.com/.well-known/jwks.json",
issuer="https://your-idp.example.com",
audience="https://mcp.yourcompany.com/mcp",
)
mcp = FastMCP("secure-server", auth=verifier)
Code language: JavaScript (javascript)逐行翻譯:
jwks_uri=...:IdP 公佈公鑰的地方,server 用它來驗證 token 簽章是真的。issuer=...:只接受這個發行者發的票。audience=...:只接受「指定給我這個 server」的票——這行就對應上面 Resource Indicators 的 audience 綁定。auth=verifier:把這個驗證器交給 FastMCP,之後每個 HTTP 請求都會先過安檢。
FastMCP 也有把常見 IdP 包好的整合(例如 GitHub、Google、WorkOS、Auth0 的 provider),讓你連「使用者登入 → 拿 token」整條 OAuth 流程都省事。初期不用全上,先有 JWTVerifier 這道門就贏過「裸奔」。
🕰️ 認得就好(舊命名):舊版 FastMCP 這個類別叫 BearerAuthProvider(在 fastmcp.server.auth.providers.bearer)。功能一樣,只是後來改名成 JWTVerifier。AI 從舊教學抄來時可能給你舊名字,import 報 ImportError 時就往這個方向問。
接上 AI 客戶端:端到端跑通一輪
server 上線了、有門了,最後一步是把它註冊給實際的 AI 客戶端。
Claude Code(CLI)
Claude Code 用 claude mcp add 註冊。遠端 HTTP server 這樣加:
claude mcp add --transport http my-server https://mcp.yourcompany.com/mcp
Code language: JavaScript (javascript)如果 server 要授權,帶上 header:
claude mcp add --transport http my-server https://mcp.yourcompany.com/mcp \
--header "Authorization: Bearer <你的token>"
Code language: JavaScript (javascript)本機 stdio server 則是把啟動指令接在 -- 後面:
claude mcp add my-server -- python server.py
Code language: CSS (css)加完用 claude mcp list 看有沒有連上。
Claude Desktop(設定檔)
Claude Desktop 讀一個 JSON 設定檔(claude_desktop_config.json)。本機 stdio server 長這樣:
{
"mcpServers": {
"my-server": {
"command": "python",
"args": ["/absolute/path/to/server.py"]
}
}
}
Code language: JSON / JSON with Comments (json)翻譯:「登記一個叫 my-server 的 MCP server,用 python /absolute/path/to/server.py 這個指令把它啟動起來。」注意路徑要用絕對路徑,這是最常見的踩雷點。
小提醒:FastMCP 有 CLI 幫你省掉手寫設定,例如
fastmcp install claude-desktop server.py,它會自動把上面那段 JSON 塞進去。
端到端驗收與除錯
上線後不要只靠「在 Claude 裡試試看」,那太慢也看不到細節。三個好用的驗收手段:
- MCP Inspector(官方除錯 UI):
- FastMCP 內建 Client(寫成小腳本,可放進測試):
- curl 打端點:直接
curlserver 的/mcp,用來分辨「是 server 掛了,還是客戶端設定錯了」。
🚩 紅旗:這些看到要立刻警覺
AI 幫你寫部署 code 時,最常在這幾個地方埋雷:
# 🚩 紅旗 1:對外 HTTP 卻完全沒 auth
mcp.run(transport="http", host="0.0.0.0", port=8000)
# 綁 0.0.0.0 曝露到公網、又沒 auth = 誰都能呼叫你的工具。
# 對 AI 說:「這個對外 server 沒有任何授權保護,幫我加上 JWT/Bearer 驗證」
Code language: PHP (php)# 🚩 紅旗 2:把收到的 token 原封不動轉手給下游
headers = {"Authorization": request.headers["Authorization"]}
requests.get("https://other-api.com", headers=headers)
# 這是 token passthrough / 混淆代理,MCP 2026 規格明文反對。
# token 應該是綁定給「你這個 server」的,不該拿去別處用。
Code language: PHP (php)- 🚩
audience沒設或設錯:token 驗證看似有做,但誰的票都收,等於門形同虛設。要確認audience對到你 server 的真實網址。 - 🚩 反向代理開著 buffering:串流會莫名卡住、客戶端一直轉圈。看到 nginx 設定沒
proxy_buffering off就要問。 - 🚩 公網服務沒 HTTPS:Bearer token 用明文 HTTP 傳等於把鑰匙貼在門上。正式對外一定走
https://。 - 🚩 host 綁
127.0.0.1卻抱怨外面連不上:這不是安全問題而是連不通,容器/雲主機記得改0.0.0.0(但同時務必配上 auth,別為了連通就裸奔)。
Vibe Coder 驗收檢查點
叫 AI 幫你把 server 上線後,照這張表逐項驗收:
- [ ] 傳輸對不對:
grep transport server.py,確認是"http"(對外)還是"stdio"(本機)符合你的意圖。 - [ ] 有沒有門:對外的 server,code 裡要找得到
auth=。沒有就是裸奔,退回去要 AI 加。 - [ ] 本機連得到嗎:
- [ ] 授權有效嗎:故意不帶 token 呼叫一次,應該被擋(回 401),而不是照樣執行。擋不下來代表 auth 沒真的生效。
- [ ] 客戶端看得到:
claude mcp list裡你的 server 狀態是連上的。 - [ ] 端到端:在 Claude 裡實際請它用一次你的工具,拿到預期結果。
看不懂就這樣問 AI
- 「這段
mcp.run(...)是用 stdio 還是 HTTP 傳輸?對外服務的話還缺哪些安全設定?」 - 「幫我檢查這個對外 MCP server 有沒有做授權,沒有的話用 JWTVerifier 幫我補上,並解釋
issuer/audience各要填什麼。」 - 「我照設定把 server 加到 Claude Code 但連不上,一步一步教我怎麼用 MCP Inspector 和 curl 找出是 server 還是客戶端的問題。」
- 「用白話解釋 MCP 2026 授權規格裡『資源伺服器和授權伺服器分開』還有『Resource Indicators / audience 綁定』在防什麼攻擊。」
系列回顧:你現在會的四件事
- #01:MCP 是什麼、為什麼需要它。
- #02:用 FastMCP 寫出第一個 server。
- #03:tools、resources、prompts 三種能力怎麼設計。
- #04(本篇):把 server 從 stdio 換成 HTTP、加上 OAuth/JWT 授權、接上真實 AI 客戶端並端到端驗收。
到這裡,你已經能看懂 AI 幫你寫的 MCP 部署 code,也知道對外服務時最該盯的那幾個安全紅旗——這正是 Vibe Coder 最需要的「看得懂、收得下、抓得到包」的能力。
進階測驗:MCP 部署與整合
共 5 題,包含情境題與錯誤診斷題。