Skip to content

MCP OAuth Well-Known Subpath 的 nginx Ingress 架構

在 nginx Ingress Controller 的 Subpath 部署中,因 OAuth 2.1 well-known 路徑與 FastMCP 內部路徑不一致,需要 3 個獨立 Ingress 分別處理不同的 rewrite 行為。

概述

將 MCP Server 部署在共享 domain 的子路徑下(如 /mcp-o365)時,會遇到一個根本性的衝突:

  1. nginx Ingress 的 rewrite-target 是 per-Ingress 的全局設定——同一個 Ingress 內所有路徑共用同一條 rewrite 規則,無法針對不同路徑使用不同 rewrite。

  2. FastMCP 對兩個 OAuth well-known 端點的路徑處理不一致

    • /.well-known/oauth-protected-resource 的路徑包含 MCP endpoint path(例如 /mcp-o365/mcp
    • /.well-known/oauth-authorization-server 的路徑不包含 endpoint path

這兩個 well-known 端點需要完全相反的 rewrite 行為,無法合併,因此需要拆成 3 個 Ingress。

這個模式是在「無法新增 Subdomain」的限制下(無 DNS 管理權限),在現有 domain 的 subpath 上部署 OAuth-aware MCP Server 的解決方案。

核心內容

問題根源:FastMCP well-known 路徑不一致

MCP_SERVER_BASE_URL=https://one.wiwynn.com/mcp-o365 時,FastMCP 在 Server 內部(port 8000)產生的 well-known 路徑為:

/.well-known/oauth-protected-resource/mcp-o365/mcp   ← 包含 base_url path
/.well-known/oauth-authorization-server               ← 不包含 base_url path

Ingress 收到的外部路徑(Claude Desktop 請求):

GET /.well-known/oauth-protected-resource/mcp-o365/mcp  ← 需要 pass-through(保留完整路徑)
GET /.well-known/oauth-authorization-server/mcp-o365    ← 需要 strip /mcp-o365(回到根路徑)

一個路徑需要保留 /mcp-o365,另一個需要移除 /mcp-o365——這在同一個 Ingress 裡無法實現。

3 Ingress 架構設計

Ingress 名稱匹配路徑Rewrite 行為原因
mcp-o365-ingress/mcp-o365/*Strip prefix/$2標準 subpath rewrite,移除 /mcp-o365 前綴
mcp-o365-wk-resource/.well-known/oauth-protected-resource/mcp-o365/*Pass-through → 保留完整路徑FastMCP 內部已包含 /mcp-o365,不需再 rewrite
mcp-o365-wk-authserver/.well-known/oauth-authorization-server/mcp-o365*Strip /mcp-o365 → 固定路徑FastMCP 內部路徑不含 /mcp-o365,需移除

完整 URL 路由對照表

MCP_SERVER_BASE_URL=https://one.wiwynn.com/mcp-o365

Claude Desktop 請求 → Ingress → Server 收到

Claude Desktop 請求走哪個 IngressServer (port 8000) 收到
POST /mcp-o365/mcp主 Ingress/mcp
GET /mcp-o365/authorize主 Ingress/authorize
POST /mcp-o365/token主 Ingress/token
POST /mcp-o365/register主 Ingress/register
GET /mcp-o365/auth/callback主 Ingress/auth/callback
GET /.well-known/oauth-protected-resource/mcp-o365/mcpwk-resource/.well-known/oauth-protected-resource/mcp-o365/mcp
GET /.well-known/oauth-authorization-server/mcp-o365wk-authserver/.well-known/oauth-authorization-server

OAuth 2.1 完整流程(標示使用的 Ingress)

步驟  動作                                                      走哪個 Ingress
───── ─────────────────────────────────────────────────────── ──────────────
 1    POST /mcp-o365/mcp → 401 + WWW-Authenticate               主 Ingress
 2    GET  /.well-known/oauth-protected-resource/mcp-o365/mcp   wk-resource
      → 回傳 authorization_servers, scopes
 3    GET  /.well-known/oauth-authorization-server/mcp-o365     wk-authserver
      → 回傳 authorize/token/register URL
 4    POST /mcp-o365/register → DCR 取得 client_id              主 Ingress
 5    GET  /mcp-o365/authorize → redirect 到 Azure 登入          主 Ingress
 6-7  使用者完成 Microsoft 登入 → Azure redirect 回 callback
 8    GET  /mcp-o365/auth/callback → 處理 auth code              主 Ingress
 9    POST /mcp-o365/token → 取得 access_token                   主 Ingress
10    POST /mcp-o365/mcp (Bearer token) → 正常 MCP 通訊          主 Ingress

Claude Desktop 連線方式選擇

在確定使用 Subpath 架構之前,評估了三種 Claude Desktop 連線方式:

方案作法結果
A. config.json URLclaude_desktop_config.json 直接設 "url": "..."❌ Claude Desktop 要求 command 欄位,不支援直接 url
B. mcp-remote 橋接"command": "npx", "args": ["-y", "mcp-remote", "..."]⚠️ 有雙進程 PKCE bug(見下方)
C. ConnectorSettings > Connectors > 輸入 HTTPS URL✅ 生產環境最終方案

mcp-remote 的雙進程 PKCE 問題:

Claude Desktop 的 MCP 連線生命週期是「initialize → 斷線 → 重新 initialize」,導致 mcp-remote 被啟動兩次。兩個進程各自產生不同的 code_challenge,但先啟動的進程搶先占住 callback port,接收到另一個進程的 auth code,用自己的 code_verifier 嘗試交換,導致 incorrect code_verifier

Local 繞法:手動先執行 npx -y mcp-remote <url> 完成一次 OAuth,token cache 到 ~/.mcp-auth/,之後 Claude Desktop 重啟直接使用 cached token。此做法不適合一般使用者。

關鍵要點

  • nginx rewrite-target 是 per-Ingress 的限制,不能在同一 Ingress 內對不同路徑套用不同 rewrite
  • FastMCP 的 well-known 路徑不一致是上游行為,未來版本可能改變(需注意升級時重新測試)
  • Connector 方案需要 HTTPS(Claude Desktop 的安全要求),因此本機開發不適合直接使用 Connector
  • 若有 DNS 管理權限,subdomain 方案(mcp-o365.your-domain.com)可大幅簡化 Ingress 設定:只需 1 個 Ingress,無 well-known 路徑衝突

實際應用

K8s 資源清單(namespace: mcp

資源名稱用途
Namespacemcp所有 MCP 資源的命名空間
ConfigMapmcp-server-config非敏感設定
Secretmcp-server-secretAzure 認證資訊
Deploymentmcp-serverFastMCP server pod
DeploymentredisToken cache
Servicemcp-o365-serviceport 80 → 8000
Serviceredis-svcport 6379
Ingressmcp-o365-ingress主路徑 rewrite
Ingressmcp-o365-wk-resourceRFC 9728 well-known pass-through
Ingressmcp-o365-wk-authserverRFC 8414 well-known strip
Secretaks-ingress-tlsTLS 憑證(需複製到 mcp namespace)

部署設定參考

設計決策紀錄

決策選擇放棄方案理由
Claude Desktop 連線方式Connectorconfig.json URL(不支援)、mcp-remote(雙進程 PKCE bug)Connector 是唯一讓一般使用者無 CLI 操作的方案
Domain 配置Subpath /mcp-o365Subdomain mcp-o365.one.wiwynn.com無 DNS 權限新增 subdomain
Ingress 數量3 個合併為 1-2 個nginx rewrite-target per-Ingress 限制 + FastMCP well-known 路徑不一致
Namespacemcpingress-basic避免跨 namespace service 引用問題

部署驗證指令

bash
# Pod 狀態
kubectl get pods -n mcp

# MCP 端點 HEAD(預期 405 Method Not Allowed)
curl -sI https://one.wiwynn.com/mcp-o365/mcp

# MCP 端點 POST(預期 401 + WWW-Authenticate)
curl -s -X POST https://one.wiwynn.com/mcp-o365/mcp \
  -H "Content-Type: application/json" -d '{}' -D -

# RFC 9728 resource metadata(預期 200 + JSON)
curl -s https://one.wiwynn.com/.well-known/oauth-protected-resource/mcp-o365/mcp

# RFC 8414 auth server metadata(預期 200 + JSON)
curl -s https://one.wiwynn.com/.well-known/oauth-authorization-server/mcp-o365

相關概念

來源