CodeGym /課程 /ChatGPT Apps /MCP Gateway:為什麼它要存在於 ChatGPT 與你的服務之間

MCP Gateway:為什麼它要存在於 ChatGPT 與你的服務之間

ChatGPT Apps
等級 16, 課堂 0
開放

1. 為什麼還需要再加一層?

幾乎大家都從同樣的方式開始:有一個 MCP 伺服器,它定義了幾個工具,ChatGPT 透過 HTTPS 直接連它,一切看起來都很好。概念性架構如下:

ChatGPT  →  你的 MCP 伺服器  →  資料庫 / 外部 API

在「pet project」階段,這確實是可行的選項。但一旦應用開始長出更多功能,開發團隊也在擴大,問題很快就會浮現。

首先,MCP 伺服器會變成「God object」。裡面同時塞了挑選禮物的工具、checkout、分析,還有各種「不如把報表也塞進來吧」。不同程式碼區塊有不同的 SLA 與安全需求,卻被綁在同一個行程中。

其次,ChatGPT 和其他客戶端被迫知道你內部服務的拓撲。半年後如果你又新增一個 commerce 用的 MCP 伺服器,就得重接客戶端、改設定與描述。所謂的「單一入口」最後變成一個 URL 動物園。

第三,無法判斷哪些共通功能要放哪裡實作:身分驗證、記錄、指標、rate limiting、權杖檢查、本地化與依區域路由。如果把這些分散到所有 MCP/Agent 服務上,你會得到大量重複與在各服務間不一致的行為。

為了打破這種耦合,同時把內部複雜度藏起來不讓 ChatGPT 看到,MCP Gateway 登場——它是所有 MCP 流量的網路閘道與單一進入點。

2. 在 ChatGPT App 的脈絡裡,什麼是 MCP Gateway

形式上,MCP Gateway 是位於 MCP 客戶端(ChatGPT、MCP Jam、你的內部工具)與一組後端服務之間的代理層與單一入口——通常是 REST/HTTP API、微服務、Agents 服務、commerce backend 等等。

Gateway 對外實作 MCP 協定(對 ChatGPT 來說就像一個 MCP 伺服器),對內則呼叫一般的 REST endpoints(HTTP/gRPC)。

對於 tools/list 請求,gateway 不會直接轉發,而是回應自己的工具清單:要嘛是硬編碼在程式裡,要嘛由設定組裝。每個 tool 都綁定到特定的 REST endpoint 與資料結構。對於 tools/call,gateway 會取出工具名稱,找到對應的 REST 路由,並透過 fetch/HTTP 用戶端呼叫它。

示意如下:

flowchart LR
    ChatGPT["ChatGPT / 模型"] --> |MCP JSON-RPC| Gateway["MCP Gateway<br/>(單一 MCP 伺服器)"]
    Gateway --> GiftAPI["Gift REST API<br/>/ 禮品微服務"]
    Gateway --> CommerceAPI["Commerce REST API<br/>/ ACP / 支付"]
    Gateway --> AnalyticsAPI["Analytics Service<br/>/ 事件與指標"]

對 ChatGPT 而言,這就是一台伺服器:單一 URL、單一工具集、單一事件流。對你而言——它是把流量導向各種後端服務的彈性路由點。

3. MCP Gateway 在 GiftGenius 架構中的角色

為了少一點抽象、展示 gateway「在活的系統中」如何運作,延續我們的 GiftGenius 範例——這個應用會為使用者挑禮物,並能透過 ACP/Instant Checkout 下單。

在簡單版中,我們只有一個 MCP 伺服器,同時會做 suggest_gifts 與 checkout_start。如今應用長大了,我們開始拆分職責:

  • Gift REST API——禮物搜尋與推薦、目錄與 feed(一般的 HTTP/REST 服務)。
  • Commerce REST API——ACP、checkout 工作階段、訂單狀態、與金流供應商串接。
  • Analytics Service / REST API——事件與指標的蒐集(開了哪些推薦、買了什麼)。
  • 獨立的 Agents 服務(如果需要)——處理複雜的多步流程。它同樣透過 HTTP/REST 存取,而不是透過 MCP。

MCP Gateway 成為這些元件的單一進入點。它會:

  • 在收到 tools/list 時回傳統一的工具清單(自己描述):每個 tool 綁定到其中一個服務的特定 REST endpoint;
  • 在收到 tools/call 時查看工具名稱(params.name),依據路由表決定要打哪個 REST 服務,並呼叫對應的 HTTP 方法(透過 fetch、axios 等)。

如果來的是名稱為 suggest_gifts 的 tools/call,gateway 就呼叫 Gift REST API 的對應 REST endpoint。若是 checkout_start,請求則會送往 Commerce REST API。

一小段 Express 風格的 TypeScript 偽碼如下:

// 極度簡化的 MCP 請求處理器
app.post("/mcp", async (req, res) => {
  const mcpReq = req.body as { method: string; params?: any };
  const ctx = buildContextFromHeaders(req); // auth、locale 等等

  const toolName = mcpReq.params?.name;
  const backendRes = await callBackend(toolName, mcpReq, ctx);

  res.json(backendRes);
});

在 pickBackend 內,你可以根據方法名、工具名、使用者語系,甚至服務版本(用於「金絲雀(canary)」與 blue/green 發行;稍後的章節會談)來做決策。

4. MCP Gateway 的職責:它確切該做什麼

我們看了 gateway 如何嵌入 GiftGenius 的架構。現在把它作為獨立層級,明確列出該做的事,與具體應用無關。重要的是把 gateway 視為網路與跨服務層。它的工作不是思考挑禮物的商業邏輯,而是處理其周邊的基礎設施問題。

請求路由

第一個角色——路由器。Gateway 接到 MCP 請求後,根據其內容、使用者情境與自身設定,選出目標服務。

例如,在 GiftGenius 可以設一張簡單的路由表:

const TOOL_ROUTES: Record<string, "gift" | "commerce" | "analytics"> = {
  suggest_gifts: "gift",
  get_similar_gifts: "gift",
  checkout_start: "commerce",
  get_order_status: "commerce",
  log_event: "analytics",
};

然後這樣使用:

function pickBackend(req: McpRequest, ctx: GatewayContext): Backend {
  if (req.method === "tools/list") return "aggregator";
  if (req.method === "tools/call") {
    const toolName = req.params?.name;
    const group = TOOL_ROUTES[toolName] ?? "gift";
    return group === "commerce" ? commerceBackend : giftBackend;
  }
  return giftBackend;
}

在我們的例子中,giftBackend、commerceBackend、analyticsBackend 都是一般的 REST 服務:各自有基底 URL("https://gift-api.internal"、"https://commerce-api.internal",…)。Gateway 並不把 MCP 封包往內轉發,而是把 MCP 呼叫拆成對目標 REST endpoint 的 HTTP 請求。

邊界的身分驗證與授權

第二個關鍵功能——保護系統邊界。Gateway 是驗證權杖、辨識使用者來自哪個組織、他擁有哪些權限的好地方。

它可以,例如,接收來自 ChatGPT 或你的 MCP Auth 伺服器的 OAuth 權杖,驗證它(最好用成熟的函式庫,而非自行實作密碼學),並轉成乾淨的情境物件:

type GatewayContext = {
  userId: string | null;
  tenantId: string | null;
  locale: string;
};

function buildContextFromHeaders(req: Request): GatewayContext {
  const token = req.headers["authorization"]; // "Bearer ..."
  const claims = token ? verifyJwt(token) : null;

  return {
    userId: claims?.sub ?? null,
    tenantId: claims?.tenant ?? null,
    locale: (req.headers["x-openai-locale"] as string) || "en-US",
  };
}

這樣一來,內部的 backend/REST 服務就不必費心解析原始 HTTP 標頭與權杖,而是直接取得已正規化的 context,其中含有 userId、tenantId、locale。MCP 文件也建議:不要「從零」實作權杖驗證,要使用成熟的函式庫與短生命週期的權杖。

記錄、追蹤與指標

第三個角色——可觀測性。Gateway 看得到所有進來的 MCP 請求與回應,因此是補上 correlation id、記錄工具參數(不含敏感資料)、寫下回應時間與狀態的絕佳位置。

最簡單的作法:

app.use((req, res, next) => {
  const requestId = crypto.randomUUID();
  (req as any).requestId = requestId;

  const start = Date.now();
  res.on("finish", () => {
    const ms = Date.now() - start;
    console.log(
      `[${requestId}] ${req.method} ${req.url} -> ${res.statusCode} in ${ms}ms`
    );
  });

  next();
});

之後在可觀測性模組中,你可以把這些資料送到結構化儲存,而不是只用 console.log,並用它們建立儀表板。

基本負載控制

第四個同樣重要的任務——第一道的負載控制。把每位使用者、每個組織、每個工具與 endpoint 的呼叫數計在 gateway 上,可以避免某個「失控」的客戶端燒掉你的叢集與模型預算。

在這個模組我們先定錨概念:rate limiting 與佇列放在 gateway 層,具體實作(Redis、token bucket、leaky bucket)會放在下一堂關於邊界防護的課程中說明。

用情境補強請求

最後,gateway 是把 MCP 客戶端的原始情境轉成內部工具乾淨參數的好地方。

例如,ChatGPT 可能透過 openai/locale 與 _meta["openai/userLocation"] 傳遞使用者的語系與地理位置。Gateway 可以:

  • 挑選對應的區域性服務(ru 伺服器、en 伺服器等);
  • 把 locale 加進 tool 呼叫參數,即使該工具在 JSON Schema 中沒有顯式要求它(例如作為非必填欄位)。

概念上:

function enrichToolArgs(args: any, ctx: GatewayContext) {
  return {
    ...args,
    locale: args.locale ?? ctx.locale,
    tenantId: ctx.tenantId,
  };
}

結果是 Gift API 一開始就拿到「豐富的情境」,例如對 "ru-RU" 拉俄文禮物描述,對 "en-US" 拉英文描述。

5. MCP Gateway 不該做的事

當開發者有了「所有東西都會經過的魔法之地」,自然會想把原本在各服務裡的東西通通塞進去。這樣 gateway 很容易變成怪獸。

通常有幾件事不該放在這一層。

首先,複雜的商業邏輯。挑選禮物、折扣規則、運費計算、ACP 邏輯——這些都該留在專門的 backend/commerce 服務中。Gateway 充其量做輕量的前置驗證(例如檢查價格不可為負),但不該挑 SKU 或依區域計稅。

其次,長期的使用者狀態。Gateway 是典型的 stateless 服務。它應該可以水平擴充,不依賴本機記憶體,且可無痛重啟。如果你開始在其中儲存 checkout 精靈的狀態或暫存購物車,很快就會遇到跨實例同步的痛點。

第三,應留在後端服務內的專屬功能。例如,如果 Gift backend 想快取禮物搜尋結果,應由它自己處理,或許使用 Redis。Gateway 不需要知道這個內部最佳化。我們在邊界防護的章節會再強調:閘道專注於網路與跨服務功能,而非推薦的商業規則。

第四,重度計算。如果你在 gateway 內開始呼叫 LLM 模型、做複雜轉換與彙總,它將不再是「輕量前端」,而會變成另一個肥大的後端,難以擴充與除錯。

6. Gateway、本地化與服務版本

我們已經講完 gateway 的基本職責與不該放的東西。接著看看兩個典型且「進階」的任務:本地化與服務版本管理。Gateway 的另一個有趣角色——依語系與版本做智慧路由。

當 ChatGPT 呼叫你的 App,它通常已掌握使用者語言(openai/locale)與經常性地理位置(_meta["openai/userLocation"])。Gateway 可以利用這些資訊把請求導到適合的後端服務。

例如,可以設計「一個 gateway——多個單語系的 backend 伺服器」:

  • ru‑Gift API——只有俄文的禮品目錄與文案。
  • en‑Gift API——只有英文。
  • jp‑Gift API——日文(當你準備征服世界時)。

在這種情況下,Gateway 對 ChatGPT 來說是 MCP 伺服器,並依 locale 與 userLocation 選擇內部服務。

概念上:

function pickGiftBackendByLocale(ctx: GatewayContext): Backend {
  if (ctx.locale.startsWith("ru")) return giftRuBackend;
  if (ctx.locale.startsWith("ja")) return giftJpBackend;
  return giftEnBackend;
}

同樣也適合實作最簡單的金絲雀路由。在這個生產架構模組中,我們建議用 gateway 把部分流量導向服務的新叢集,其他留在舊叢集。

一個非常粗略的金絲雀示例:

function pickGiftBackendCanary(ctx: GatewayContext): Backend {
  const hash = hashUser(ctx.userId ?? "anonymous");
  const bucket = hash % 100;
  return bucket < 5 ? giftBackendV2 : giftBackendV1; // 5% 的流量導到 v2
}

如此就能在不一次性衝擊整個生產環境的前提下,觀察指標與錯誤,安全地推出 Gift API 新版。

7. 常見拓撲:從「All‑in‑one」到 Gateway

先前課程中你已看過幾種 ChatGPT App 的生產拓撲。在本模組我們歸納三種基礎拓撲,能涵蓋 90% 的情境。

第一種——「全都在一起」。App 小工具(Next.js)、MCP 伺服器、Agents 邏輯與簡單的 commerce backend 都在同一個服務裡,常見是同一個版本庫,甚至同一個 Vercel 專案。優點——幾乎不需要 DevOps、部署簡單、延遲最低。缺點——難以獨立擴充單一部分,某個熱門功能可能拖垮整個應用,元件邊界也模糊。

第二種——App + MCP Gateway + 多個後端服務。這裡 Next.js 小工具獨立(例如在 Vercel),所有 MCP 流量經由 Gateway,然後路由到 Gift REST API、Commerce REST API、Agents 服務、ACP backend 等等。這正是我們在 GiftGenius 內解析的架構,也適用於 90% 真實的生產案例。

第三種——與第二種相同,但跨多區域(multi‑region),在 gateway 前面放全球負載平衡器。來自歐洲的使用者走 eu 叢集,來自美國的走 us 叢集;每個區域都用「Gateway + 多個後端服務」的結構。這通常用於面向全球的大型專案。

此刻重點不是背下所有變體,而是養成把 gateway 視為獨立邏輯元件的思維——即使在早期,這個角色可能由一個 MCP 單體或 App 的 backend 來扮演。

8. MCP Gateway 實際部署在哪裡

好消息:MCP Gateway 不一定要是部署在 Kubernetes 上的龐大獨立服務。多數情況它會經歷幾個成長階段。

在最小規模時,MCP 伺服器本身就能擔任 gateway 的角色。你只需要把程式結構整理好:把路由、驗證、記錄抽成一個模組,把工具邏輯放在其他模組。在本模組我們也明確指出,小系統裡 gateway 功能可以位於 MCP 伺服器或 App 的 backend(例如 Next.js 的 API route)。

下一步——獨立的 Node/TypeScript 服務。它可以是一個 Express/Fastify 應用,監聽 "/mcp" 並向內部多個 HTTP 服務發請求。對許多團隊來說這很順手:能沿用既有 DevOps 工具。

此類服務最簡骨架:

const app = express();
app.use(express.json());

app.post("/mcp", handleMcpRequest); // gateway 的魔法都在這裡

app.listen(4000, () => {
  console.log("MCP Gateway listening on :4000");
});

在更成熟的階段,可以基於託管解決方案實作 gateway:AWS API Gateway、Cloudflare Workers/Routes、NGINX/Envoy 加上路由設定與 Lua/JS 腳本。要理解的是:這是實作層面的變化,不是概念的改變。架構上,ChatGPT 仍然只面向單一入口,而所有細節由 gateway 負責。

9. 迷你範例:GiftGenius 的簡易 MCP Gateway

我們已分別看過路由、情境與 tools/list 處理。現在把重點湊成一個小而清晰的例子。假設我們有兩個內部 REST 服務:

  • GIFT_API_BASE = "https://gift-api.internal";
  • COMMERCE_API_BASE = "https://commerce-api.internal"。

以及一個 gateway,ChatGPT 會透過 "https://gateway.giftgenius.com/mcp" 呼叫它。

先定義幾個型別:

type Backend = "gift" | "commerce";

type ToolRoute = {
  backend: Backend;
  method: "GET" | "POST";
  path: string;
};

const TOOL_ROUTES: Record<string, ToolRoute> = {
  suggest_gifts: {
    backend: "gift",
    method: "POST",
    path: "/api/gifts/suggest",
  },
  checkout_start: {
    backend: "commerce",
    method: "POST",
    path: "/api/checkout/start",
  },
  get_order_status: {
    backend: "commerce",
    method: "GET",
    path: "/api/orders/status",
  },
};

接著實作 backend 選擇與呼叫:

async function callBackend(toolName: string, mcpReq: McpRequest, ctx: GatewayContext) {
  const route = TOOL_ROUTES[toolName];
  if (!route) {
    throw new Error(`Unknown tool: ${toolName}`);
  }

  const base =
    route.backend === "gift" ? GIFT_API_BASE : COMMERCE_API_BASE;

  const url = base + route.path;

  // 來自 MCP tools/call 的 args
  const args = {
    ...(mcpReq.params?.arguments ?? {}),
    locale: ctx.locale,
  };

  const res = await fetch(url, {
    method: route.method,
    headers: { "content-type": "application/json" },
    body: route.method === "POST" ? JSON.stringify(args) : undefined,
  });

  const data = await res.json();

  // 將 REST 服務的回應包成 MCP 回應
  return {
    result: data,
  } satisfies McpResponse;
}

最後是主要處理器,會:

  1. 從標頭建立情境(auth、locale)。
  2. 選擇 backend。
  3. 要嘛彙整 tools/list,要嘛代理 tools/call。
app.post("/mcp", async (req, res) => {
  const mcpReq = req.body as McpRequest;
  const ctx = buildContextFromHeaders(req);

  if (mcpReq.method === "tools/list") {
    // Gateway 自行宣告工具與其 schema
    const tools = [
      {
        name: "suggest_gifts",
        description: "依照預算與興趣挑選禮物。",
        inputSchema: { /* ... JSON Schema ... */ },
      },
      {
        name: "checkout_start",
        description: "建立訂單草稿並啟動 checkout。",
        inputSchema: { /* ... */ },
      },
      // ...
    ];

    return res.json({ result: { tools } });
  }

  if (mcpReq.method === "tools/call") {
    const toolName = mcpReq.params?.name;
    const backendRes = await callBackend(toolName, mcpReq, ctx);
    return res.json(backendRes);
  }

  res.status(400).json({ error: { message: "Unsupported MCP method" } });
});

這當然是簡化過的設計,但已經展現關鍵觀念:

  • gateway 並不知道 Gift API 具體如何挑選禮物;
  • 它只是負責乾淨地路由、補強參數,並在需要時記錄與做頻率限制。

10. 與本模組後續主題的關聯

MCP Gateway 是我們接下來幾堂課討論內容的基石:

  • 下一主題談邊界防護:rate limiting、佇列與回壓(backpressure)。這些主要都發生在 gateway 層,因為它看得到所有進來的流量,能在請求拖垮後端之前就「切掉多餘的」。
  • 接著談穩健性:超時(timeouts)、斷路器(circuit breakers)、隔艙(bulkheads)。Gateway 是集中設定外部呼叫超時、並可開關有問題服務(例如暫時「切掉」錯誤升高的 Commerce API)的好地方。
  • 最後,在討論擴充與部署時,我們會把 gateway 視為獨立叢集:可以負載平衡、以 blue/green 與 canary 方式發布,並能獨立於內部 MCP 服務回滾。

本質上,以前你可能只想「我有 App 與 MCP 伺服器」,現在架構擴展為「我有 App、MCP Gateway、多個 backend/Agents 叢集與 commerce backend」。而正是 gateway 讓 ChatGPT 的設定仍然簡單——它仍然只看到一個 MCP 入口。

11. 使用 MCP Gateway 的常見錯誤

錯誤 #1:把 gateway 變成「商業怪獸」。
常見陷阱:既然所有東西都會經過 gateway,何不把折扣計算、SKU 挑選、複雜分類規則或折扣碼驗證都丟進去?結果你得到一個肥大難改的服務,失去把 Gift API、Commerce API 與其他專門元件分拆的意義。請讓 gateway 保持薄的網路層,所有領域特定(domain‑specific)的東西留在專門服務中。

錯誤 #2:在 gateway 儲存長期的使用者狀態。
「不如把使用者的購物車直接記在 gateway 記憶體裡」在只有一個實例時看似可行。一旦有第二個實例,災難就來了:真正的購物車在哪——A 還是 B?重啟後怎麼辦?Gateway 應維持 stateless:最多只有少量的握手或設定快取;所有工作階段與訂單狀態放在資料庫或專門服務。

錯誤 #3:讓 ChatGPT 知道內部服務拓撲。
如果你開始同時把多個 API 伺服器(Gift API、Commerce API)直接暴露給 ChatGPT,只在部分情境用 gateway,你就失去最大優勢:單一入口與集中控制。拓撲一改,就得在多處調整設定。更好的方式是把 MCP Gateway 設為 App 的官方 endpoint,並把所有內部變動藏在它背後。

錯誤 #4:在所有後端重複實作跨服務邏輯。
有時團隊會在每個 REST 服務裡各自實作身分驗證、rate limiting、記錄與本地化。結果是 Gift API 一套權限與限制政策、Commerce API 又是另一套,App 行為變得不可預期。Gateway 正是用來集中處理這些事:驗權杖、判定 tenant 與 locale、記錄呼叫、套用限制,然後再去特定服務。

錯誤 #5:用重度計算與 LLM 呼叫把 gateway 壓垮。
技術上你可以從 gateway 再呼叫一個 LLM、做複雜彙總或冗長的批次作業。但這會很快把它變成另一個肥大後端,既難擴充也難隔離。Gateway 應維持快速且可預期:最多做輕量轉換與路由。重活留給 REST 服務或我們之後會講到的佇列/worker。

錯誤 #6:太早過度複雜化基礎設施。
另一個極端——為一個小型教學 App,一開始就建獨立 Kubernetes 叢集、NGINX 堆疊、Cloudflare Workers 與一堆複雜設定。沒有實際流量與高可用需求時,這沒有意義。從一個 MCP 單體或簡單的 Node gateway 開始完全沒問題,隨著成長再逐步把元件抽出到叢集與託管服務。

1
任務
ChatGPT Apps, 等級 16, 課堂 0
上鎖
工具路由表 + 上下文增豐(locale、requestId)
工具路由表 + 上下文增豐(locale、requestId)
留言
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION