【MCP 與 FastMCP 實戰】#04 部署與整合:HTTP 傳輸、授權與接上 AI 客戶端

測驗:MCP 部署與整合

共 5 題,點選答案後會立即顯示結果

1. 為什麼 stdio 傳輸無法用來讓遠端機器連上你的 MCP server?

  • A. stdio 傳輸速度太慢,不適合網路
  • B. stdio 沒有加密,被防火牆擋掉
  • C. stdio 是靠客戶端在本機把 server 當子程序啟動,兩者必須在同一台機器
  • D. stdio 一次只能服務一個工具呼叫

2. 在 FastMCP 2.x 中,要把 server 切換成對外的 Streamable HTTP 傳輸,最正確的寫法是?

mcp.run(transport=____, host=”0.0.0.0″, port=8000, path=”/mcp”)
  • A. “stdio”
  • B. “http”
  • C. “websocket”
  • D. “tcp”

3. 根據文章,為什麼「對外的 HTTP MCP server 一定要加授權」,但 stdio 時代通常不用煩惱?

  • A. HTTP 傳輸本身有漏洞,stdio 沒有
  • B. HTTP 一定要付費才能用授權功能
  • C. stdio 會自動加密所有訊息
  • D. stdio 能啟動程序的人本來就是本機使用者;HTTP 掛上網後任何知道網址的人都能呼叫工具

4. 在 MCP 2026 授權規格中,你自己寫的 MCP server 扮演什麼角色?

  • A. Resource Server(資源伺服器),只負責驗票,不負責發 token
  • B. Authorization Server,負責讓使用者登入並發 token
  • C. 同時是登入頁面與資料庫
  • D. 憑證頒發機構(CA)

5. 用 Claude Code 註冊一個「遠端 HTTP」MCP server,正確的指令是?

  • A. claude mcp add my-server — python server.py
  • B. claude mcp add –transport http my-server https://mcp.yourcompany.com/mcp
  • C. claude mcp run –stdio server.py
  • D. pip install my-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"hostport/mcp 路徑、JWKSBearer 這些字,就是在講「把 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 裡試試看」,那太慢也看不到細節。三個好用的驗收手段:

  1. MCP Inspector(官方除錯 UI):
  2. FastMCP 內建 Client(寫成小腳本,可放進測試):
  3. curl 打端點:直接 curl server 的 /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 題,包含情境題與錯誤診斷題。

1. 你把 FastMCP server 部署到雲主機的 Docker 容器裡,本機 curl 得到回應,但同事的機器就是連不上。你在 code 裡看到下面這行。最該先改哪裡? 情境題

mcp.run(transport=”http”, host=”127.0.0.1″, port=8000, path=”/mcp”)
  • A. 把 port 改成 80
  • B. 把 host 改成 “0.0.0.0”,讓容器接受來自任何網卡的連線(同時記得配上 auth)
  • C. 把 transport 改回 “stdio”
  • D. 把 path 改成 “/”

2. 你要把對外的 MCP server 部署成負載平衡後的多台實例,希望任何一台都能處理任何請求以便水平擴展。最適合的設定是? 情境題

  • A. 改用 transport=”stdio”
  • B. 把 port 設成 0,讓系統隨機分配
  • C. 建立 server 時加 stateless_http=True,讓每個請求各自獨立、server 不記得上一個
  • D. 關掉授權以加快請求速度

3. Code review 時你看到 AI 幫某工具寫了下面這段。為什麼這在 MCP 2026 規格下是紅旗? 錯誤診斷

headers = {“Authorization”: request.headers[“Authorization”]} requests.get(“https://other-api.com”, headers=headers)
  • A. 這是 token passthrough/混淆代理:token 應綁定給「你這個 server」,不該原封轉手給下游
  • B. requests 套件太舊,應改用 httpx
  • C. headers 字典的鍵不該用大寫
  • D. 沒問題,重複利用 token 可以省一次登入

4. 你的 Streamable HTTP server 上線後,客戶端連上卻一直轉圈、串流回應遲遲不來。前面擺了一台 nginx 反向代理。最可能的原因與修法是? 錯誤診斷

location /mcp { proxy_pass http://127.0.0.1:8000; # proxy_buffering 沒設定,使用預設 }
  • A. port 打錯,改成 8080
  • B. 要把 transport 改成 sse 才支援串流
  • C. 代理預設會緩衝回應,破壞串流;在該 location 加 proxy_buffering off
  • D. server 需要改綁 0.0.0.0

5. 你想在部署後驗收「授權真的有生效」,而不是只確認連得上。下列哪個做法最能驗證這件事? 情境題

  • A. 帶著正確 token 呼叫,成功就代表安全
  • B. 看 server log 有沒有 error
  • C. 在 Claude 裡問它「你安全嗎」
  • D. 故意「不帶」token 呼叫一次,應該被擋(回 401)而不是照樣執行

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *