MCP Gateway 与本地化架构:单语服务器、将 locale 作为参数、客户端状态
1. 为什么要考虑本地化架构当你只有一种语言且目录很小,一切都很简单:你维护一个 gift_catalog.json,所有文本都是俄语,MCP‑服务器会如实把这些礼物返回给所有用户。但一旦你希望:面向美国和欧洲的英文 UI,单独的俄语目录(包含套娃和俄语书籍),不同市场(美国用 Amazon,俄罗斯用 Ozon),天真的做法是在每个 handler 里再加一个 if(locale === "ru"),最终会把代码写成“圣诞树式分支”。MCP 一方面是协议,另一方面是该协议的服务器实现。服务器会接收来自 ChatGPT 的请求及其元数据,其中包括 locale 和 userLocation。问题不在于“能不能读到 locale”,而在于你在架构的哪个位置利用这个信号。可以在每个工具里处理,也可以把一部分逻辑抽到单独一层——Gateway。一个好的本地化架构需要回答三个问题:我们在哪里决定使用哪种语言与地区。我们在哪里选择所需的数据与集成(目录、商店 API、货币)。我们在哪里以及如何保存用户状态(locale、货币,乃至一些偏好),以避免每次都手动传递。今天我们就来拆解这些。2. MCP、_meta 与无状态特性:为什么必须显式传递 locale在决定架构中哪里处理 locale 之前,先回顾一下协议层面的 MCP 请求长什么样,以及平台已经帮你传了哪些元数据。要点提醒:MCP 请求是 JSON‑RPC 消息。每条消息都是独立的,协议并不强制有状态会话。因此,如果你希望服务器考虑本地化,就需要要么:把它作为工具参数显式传入(locale 在 inputSchema 中),要么从 _meta["openai/locale"] 读取,ChatGPT 会把它加到请求里。这是一个从 _meta 读取 locale 的最简 handler 示例:server.registerTool( "suggest_gifts", { title: "Suggest gifts", inputSchema: { /* ... */ }, }, async (args, extra) = { const meta = extra?._meta ?? {}; const locale = (meta["openai/locale"] as string | undefined) || "en-US"; const country = meta["openai/userLocation"]?.country as string | undefined; // 接下来用 locale 和 country 选择目录 const gifts = await loadGiftCatalog(locale, country); return { structuredContent: { gifts } }; } );这里我们不通过参数传递 locale,而是依赖 SDK 已经放在 extra 里的 _meta。这完全可行,并且会在第一种模型(单个多语言 MCP)中派上用场。在第二种模型(带 Gateway)中,_meta 同样关键:网关从元数据里读取 locale,并据此决定把请求转发到哪里。 至于 locale 具体以什么形式存在——只放在 _meta 里,还是也体现在工具的 schema 中——我们会在下面单独讨论。3. 模型 1:单个多语言 MCP‑服务器(“多语单体”)先从最简单的架构开始。你有一个 MCP 服务器、一个 URL、一次部署、一套代码。在每个工具内部你:获取 locale(来自 _meta 或参数)。基于 locale 选择资源:gift_catalog.en.json、gift_catalog.ru.json 等。用正确的语言返回结果。GiftGenius 示例假设我们有两个目录文件:data/gift_catalog.en.jsondata/gift_catalog.ru.json写一个小工具函数 loadGiftCatalog(locale) 来选择需要的文件:async function loadGiftCatalog(locale: string) { const lang = locale.split("-")[0]; // "en-US" → "en" const fileName = lang === "ru" ? "gift_catalog.ru.json" : "gift_catalog.en.json"; const data = await import(`../data/${fileName}`); return data.default; // 礼物数组 }