1. Por que precisamos de mais uma camada?
Quase todo mundo começa do mesmo jeito: há um único servidor MCP, ele descreve algumas ferramentas, o ChatGPT fala com ele diretamente via HTTPS, e tudo parece ótimo. A arquitetura conceitual fica assim:
ChatGPT → seu servidor MCP → banco / APIs externas
Na fase de “pet project”, isso é realmente uma opção normal. Mas assim que o aplicativo começa a ganhar funcionalidades e a equipe de desenvolvimento — a crescer, os problemas aparecem rapidamente.
Primeiro, o servidor MCP vira um “God object”. Nele convivem, ao mesmo tempo, as ferramentas de seleção de presentes, o checkout, a análise e mais alguma coisa do tipo “vamos enfiar relatórios aqui também”. Partes diferentes do código têm SLAs diferentes e exigências de segurança diferentes, mas estão coladas em um único processo.
Segundo, o ChatGPT e outros clientes são obrigados a conhecer a topologia dos seus serviços. Se, em seis meses, aparecer mais um servidor MCP para commerce, você terá de reconectar clientes, mudar configs e descrições. Em vez de “um ponto de entrada”, você ganha um zoológico de URLs.
Terceiro, fica difícil entender onde implementar o que é comum a todos os serviços: autenticação, logging, métricas, rate limiting, verificação de tokens, localização e roteamento por regiões. Se isso for espalhado por todos os serviços MCP/Agent, você terá muita duplicação e comportamentos diferentes em serviços diferentes.
Para quebrar esse acoplamento e, ao mesmo tempo, esconder a complexidade interna do ChatGPT, entra em cena o MCP Gateway — um gateway de rede e ponto único de entrada para todo o tráfego MCP.
2. O que é MCP Gateway no contexto do ChatGPT App
Formalmente, o MCP Gateway é uma camada de proxy e ponto único de entrada entre clientes MCP (ChatGPT, MCP Jam, suas ferramentas internas) e um conjunto de seus serviços de backend — normalmente REST/HTTP APIs, microsserviços, serviços de Agents, commerce‑backend etc.
O gateway implementa o protocolo MCP para fora (para o ChatGPT ele parece um único servidor MCP) e, por dentro, apenas chama endpoints REST comuns via HTTP/gRPC.
Na solicitação tools/list, o gateway não encaminha a chamada adiante; ele devolve sua própria lista de ferramentas: ou definida rigidamente no código, ou montada a partir de configuração. Cada tool está vinculada a um endpoint REST e a um esquema de dados. Na solicitação tools/call, o gateway pega o nome da ferramenta, encontra a rota REST correspondente e a chama via fetch/cliente HTTP.
Esquematicamente, isso pode ser representado assim:
flowchart LR
ChatGPT["ChatGPT / modelo"] --> |MCP JSON-RPC| Gateway["MCP Gateway<br/>(único servidor MCP)"]
Gateway --> GiftAPI["Gift REST API<br/>/ microsserviço de presentes"]
Gateway --> CommerceAPI["Commerce REST API<br/>/ ACP / pagamentos"]
Gateway --> AnalyticsAPI["Analytics Service<br/>/ eventos e métricas"]
Para o ChatGPT é um único servidor: uma URL, um conjunto de ferramentas, um fluxo de eventos. Para você — um ponto flexível de roteamento de tráfego para diferentes serviços frios e quentes.
3. MCP Gateway na arquitetura do GiftGenius
Para falar menos em abstrações e mostrar o gateway “em um sistema real”, vamos continuar nosso exemplo GiftGenius — um aplicativo que seleciona presentes e consegue finalizar pedidos via ACP/Instant Checkout.
Na versão simples tínhamos um único servidor MCP, que fazia tanto suggest_gifts quanto checkout_start. Agora que o app cresceu, separamos as responsabilidades:
- Gift REST API — busca e recomendações de presentes, trabalho com catálogo e feed (serviço HTTP/REST comum).
- Commerce REST API — ACP, sessões de checkout, status de pedidos, integração com o provedor de pagamento.
- Analytics Service / REST API — coleta de eventos e métricas (quais recomendações são abertas, o que é comprado).
- Um serviço de Agents separado (se necessário) — cenários complexos de múltiplas etapas. Também acessível via HTTP/REST, não via MCP.
O MCP Gateway vira o ponto único de entrada para todos esses componentes. Ele:
- na solicitação tools/list retorna uma lista única de tools, que ele mesmo descreve: cada tool está ligada ao endpoint REST específico de um dos serviços;
- na solicitação tools/call olha o nome da ferramenta (params.name), determina pela tabela de roteamento a qual serviço REST ir e chama o método HTTP correspondente (via fetch, axios etc.).
Se chega um tools/call com o nome suggest_gifts, o gateway chama o endpoint REST correspondente no Gift REST API. Se for checkout_start, a solicitação vai para o Commerce REST API.
Um pequeno pseudocódigo em TypeScript no estilo Express pode ser assim:
// Manipulador de solicitações MCP bem simplificado
app.post("/mcp", async (req, res) => {
const mcpReq = req.body as { method: string; params?: any };
const ctx = buildContextFromHeaders(req); // auth, locale etc.
const toolName = mcpReq.params?.name;
const backendRes = await callBackend(toolName, mcpReq, ctx);
res.json(backendRes);
});
Dentro de pickBackend você pode se basear no nome do método, no nome da ferramenta, na localidade do usuário e até na versão do serviço (para lançamentos “canários” e blue/green, sobre os quais falaremos mais adiante no módulo).
4. Responsabilidades do MCP Gateway: o que ele faz de fato
Vimos como o gateway se encaixa na arquitetura do GiftGenius. Agora vamos explicitar quais responsabilidades ele tem como uma camada separada, independentemente do aplicativo específico. É importante enxergar o gateway como uma camada de rede e entre serviços (cross‑service). Sua tarefa não é pensar na lógica de negócios de presentes, mas resolver questões de infraestrutura ao redor delas.
Roteamento de solicitações
A primeira função é de roteador. O gateway recebe uma solicitação MCP e, com base em seu conteúdo, no contexto do usuário e na própria configuração, escolhe o serviço de destino.
Por exemplo, no GiftGenius dá para criar uma tabela de roteamento simples:
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",
};
E então usá-la:
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;
}
No nosso caso, giftBackend, commerceBackend, analyticsBackend são serviços REST comuns: cada um tem uma URL base ("https://gift-api.internal", "https://commerce-api.internal", …). O gateway não encaminha MCP para dentro; ele decompõe a chamada MCP em uma solicitação HTTP ao endpoint REST necessário.
Autenticação e autorização no perímetro
A segunda função chave é proteger o perímetro. O gateway é um lugar conveniente para verificar o token, entender quem é o usuário, de qual organização ele veio e quais permissões ele tem.
Ele pode, por exemplo, aceitar um token OAuth do ChatGPT ou do seu servidor MCP Auth, validá-lo (de preferência com uma biblioteca consolidada, não criptografia caseira) e transformá-lo em um objeto de contexto enxuto:
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",
};
}
Os serviços internos de backend/REST então não precisam se preocupar em analisar cabeçalhos HTTP crus e tokens; eles recebem um context já normalizado com userId, tenantId e locale. As recomendações da documentação do MCP dizem claramente: não implemente validação de tokens “do zero”; use bibliotecas confiáveis e tokens de curta duração.
Logging, tracing e métricas
A terceira função é a observabilidade. O gateway vê todas as solicitações MCP de entrada e todas as respostas, portanto é o lugar ideal para atribuir um correlation‑id, registrar parâmetros de ferramentas (sem dados sensíveis), salvar o tempo de resposta e o status.
Uma ideia bem simples:
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();
});
Mais adiante no módulo de observabilidade, você poderá enviar esses dados não apenas para o console.log, mas para um armazenamento estruturado e construir dashboards em cima deles.
Controle básico de carga
A quarta, mas também importante, tarefa é o controle primário de carga. É conveniente colocar no gateway contadores de chamadas por usuários, organizações, ferramentas e endpoints, para não deixar um único cliente “desenfreado” queimar seu cluster e o orçamento de modelos.
Neste módulo, por enquanto, apenas registramos a ideia: rate limiting e filas vivem no nível do gateway; os detalhes de implementação (Redis, token buckets, leaky buckets) serão abordados na próxima aula sobre proteção de perímetro.
Enriquecimento de solicitações com contexto
E, por fim, o gateway é um bom lugar para transformar o contexto bruto do cliente MCP em argumentos bem formados para as ferramentas internas.
Por exemplo, o ChatGPT pode enviar a localidade do usuário via openai/locale e _meta["openai/userLocation"]. O gateway pode:
- escolher o serviço regional apropriado (servidor ru, servidor en etc.);
- adicionar locale aos argumentos da chamada da ferramenta, mesmo que a própria ferramenta não o solicite explicitamente no JSON Schema (por exemplo, como um campo opcional).
De forma ilustrativa:
function enrichToolArgs(args: any, ctx: GatewayContext) {
return {
...args,
locale: args.locale ?? ctx.locale,
tenantId: ctx.tenantId,
};
}
Como resultado, o Gift API recebe imediatamente um “contexto rico” e pode, por exemplo, puxar descrições de presentes em russo para "ru-RU" e em inglês para "en-US".
5. O que o MCP Gateway NÃO deve fazer
Quando o desenvolvedor ganha “um lugar mágico por onde tudo passa”, surge a vontade natural de enfiar ali tudo o que antes ficava em serviços separados. Assim, o gateway corre o risco de virar um monstro.
Há algumas coisas que, em geral, não devem viver nessa camada.
Primeiro, lógica de negócios complexa. Seleção de presentes, regras de desconto, cálculo de frete, lógica de ACP — tudo isso deve permanecer dentro de serviços especializados de backend/commerce. O gateway pode, no máximo, fazer uma validação leve e preliminar (por exemplo, verificar se o preço não é negativo), mas não escolher SKUs nem calcular impostos por região.
Segundo, estado de usuário de longa duração. O gateway é um serviço tipicamente stateless. Ele deve escalar horizontalmente sem problemas, não depender de memória local e reiniciar sem consequências. Se você começar a armazenar nele, por exemplo, o estado do assistente de checkout ou o conteúdo temporário do carrinho, rapidamente terá dores com sincronização entre instâncias.
Terceiro, funções específicas que fazem mais sentido dentro dos próprios serviços de backend (Gift API, Commerce API). Por exemplo, se o backend de Gift quiser fazer cache do resultado da busca de presentes, que o faça por conta própria, talvez usando Redis. O gateway não precisa conhecer essa otimização interna. Vamos falar separadamente sobre proteção de perímetro, e lá enfatizamos: o gateway lida com funções de rede e entre serviços, não com regras de negócio de recomendação.
Quarto, cálculos pesados. Se, dentro do gateway, você começar a chamar modelos de LLM, fazer transformações complexas e agregações, ele deixará de ser uma “frente leve” e se tornará mais um backend pesado, difícil de escalar e depurar.
6. Gateway, localização e versões de serviços
Já analisamos as responsabilidades básicas do gateway e o que não é bom colocar nele. Agora vejamos duas tarefas “avançadas” típicas que é conveniente resolver justamente nessa camada: localização e versionamento de serviços. Outra função interessante do gateway é o roteamento inteligente por localidade e por versões de serviços.
Quando o ChatGPT chama seu App, ele já tem uma noção do idioma do usuário (openai/locale) e, muitas vezes, de sua geolocalização (_meta["openai/userLocation"]). O gateway pode usar essa informação para enviar as solicitações aos serviços de backend apropriados.
Por exemplo, é possível construir uma arquitetura “um gateway — muitos servidores de backend monolíngues”:
- ru‑Gift API — apenas catálogo e textos em russo.
- en‑Gift API — apenas inglês.
- jp‑Gift API — japonês (quando vocês decidirem conquistar o mundo).
O gateway, nesse caso, atua como servidor MCP para o ChatGPT e, com base em locale e userLocation, escolhe o serviço interno adequado.
De forma ilustrativa:
function pickGiftBackendByLocale(ctx: GatewayContext): Backend {
if (ctx.locale.startsWith("ru")) return giftRuBackend;
if (ctx.locale.startsWith("ja")) return giftJpBackend;
return giftEnBackend;
}
Também é conveniente implementar ali um roteamento canário simples. Neste módulo sobre arquitetura de produção, sugerimos usar o gateway para enviar parte do tráfego para um novo cluster do serviço, enquanto o restante vai para o antigo.
Exemplo de canário bem grosseiro:
function pickGiftBackendCanary(ctx: GatewayContext): Backend {
const hash = hashUser(ctx.userId ?? "anonymous");
const bucket = hash % 100;
return bucket < 5 ? giftBackendV2 : giftBackendV1; // 5% do tráfego vai para a v2
}
Assim é possível lançar com segurança uma nova versão do Gift API, observando métricas e erros, sem quebrar toda a produção de uma vez.
7. Arquiteturas típicas: do “tudo em um” ao Gateway
Em aulas anteriores do curso você já viu várias opções de arquitetura de produção de um ChatGPT App. Neste módulo, destacamos três topologias básicas que bastam em 90% dos casos.
A primeira — “tudo em um”. O widget do App (Next.js), o servidor MCP, a lógica de Agents e um backend de commerce simples vivem em um único serviço, muitas vezes no mesmo repositório e até no mesmo app da Vercel. Vantagens — praticamente nada de DevOps, deploy simples, latência mínima. Desvantagens — é difícil escalar partes separadamente, uma feature quente pode derrubar todo o app, e as fronteiras entre os componentes ficam difusas.
A segunda — App + MCP Gateway + vários serviços de backend. Aqui o widget Next.js vive separado (por exemplo, na Vercel), e todo o tráfego MCP passa pelo Gateway, que roteia solicitações para o Gift REST API, para o Commerce REST API, para o serviço de Agents, para o ACP‑backend e outros. Este é exatamente o esquema que estamos explorando agora no contexto do GiftGenius e que atende a 90% dos casos reais em produção.
A terceira — a mesma coisa, mas em várias regiões (multi‑region), com um balanceador global na frente do gateway. Nesse caso, o usuário da Europa cai no cluster eu, dos EUA — no cluster us, e cada região é construída no esquema “Gateway + vários serviços de backend”. Essa já é uma história para projetos bem maiores, com audiência global.
Para nós, agora, não é tão importante memorizar todas as variantes, e sim acostumar a pensar no gateway como um componente lógico separado da arquitetura, mesmo que, nas primeiras etapas, seu papel seja exercido por um único monólito MCP ou pelo backend do seu App.
8. Onde o MCP Gateway vive fisicamente
Boa notícia: o MCP Gateway não é necessariamente um grande serviço separado no Kubernetes. Na maioria das vezes, ele passa por algumas etapas de amadurecimento.
Na menor escala, o próprio servidor MCP pode desempenhar o papel de gateway. Nesse caso, basta estruturar o código com cuidado: extrair roteamento, autenticação e logging para um módulo, e a lógica das ferramentas para outros. Neste módulo, destacamos explicitamente que, em sistemas pequenos, as funções do gateway podem estar dentro do servidor MCP ou da parte de backend do App (por exemplo, numa rota de API do Next.js).
O próximo passo é um serviço separado em Node/TypeScript. Pode ser um aplicativo Express/Fastify que ouve "/mcp" e chama para dentro alguns serviços HTTP. Para muitas equipes, essa é uma opção confortável: se encaixa bem nas ferramentas DevOps já conhecidas.
O esqueleto mais simples de um serviço assim:
const app = express();
app.use(express.json());
app.post("/mcp", handleMcpRequest); // aqui acontece toda a mágica do gateway
app.listen(4000, () => {
console.log("MCP Gateway listening on :4000");
});
Em um estágio ainda mais maduro, o gateway pode ser implementado sobre soluções gerenciadas: AWS API Gateway, Cloudflare Workers/Routes, NGINX/Envoy com configuração de roteamento e scripts Lua/JS. É importante entender que isso é uma mudança de implementação, não de conceito. Arquiteturalmente, o ChatGPT continua indo a um único ponto, e todos os detalhes ficam a cargo do gateway.
9. Mini-exemplo: um MCP Gateway simples para o GiftGenius
Já vimos separadamente roteamento, contexto e o processamento de tools/list. Agora vamos juntar tudo em um exemplo claro, porém pequeno. Suponha que tenhamos dois serviços REST internos:
- GIFT_API_BASE = "https://gift-api.internal";
- COMMERCE_API_BASE = "https://commerce-api.internal".
E um gateway para o qual o ChatGPT fará chamadas no endereço "https://gateway.giftgenius.com/mcp".
Primeiro, vamos definir alguns tipos:
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",
},
};
Em seguida, implementamos a escolha do backend e a chamada:
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;
// args que chegaram na chamada MCP tools/call
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();
// Envolvemos a resposta do serviço REST em uma resposta MCP
return {
result: data,
} satisfies McpResponse;
}
E, por fim, o handler principal, que:
- Constrói o contexto a partir dos cabeçalhos (auth, locale).
- Escolhe o backend.
- Ou agrega tools/list, ou faz proxy de tools/call.
app.post("/mcp", async (req, res) => {
const mcpReq = req.body as McpRequest;
const ctx = buildContextFromHeaders(req);
if (mcpReq.method === "tools/list") {
// O gateway declara as ferramentas e seus esquemas por conta própria
const tools = [
{
name: "suggest_gifts",
description: "Seleciona presentes por orçamento e interesses.",
inputSchema: { /* ... JSON Schema ... */ },
},
{
name: "checkout_start",
description: "Cria um rascunho de pedido e inicia o 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" } });
});
Claro, este é um esquema simplificado, mas já mostra as ideias principais:
- o gateway não sabe como exatamente o Gift API seleciona presentes;
- ele apenas roteia com cuidado, enriquece argumentos e, se quiser, registra logs e aplica limites às chamadas.
10. Como tudo isso se conecta com os próximos tópicos do módulo
O MCP Gateway é uma parte fundamental de tudo o que vamos discutir nas próximas aulas do módulo:
- No próximo tópico, vamos falar sobre proteção de perímetro: rate limiting, filas e backpressure. Tudo isso vive, antes de tudo, no nível do gateway, porque é ele que vê todo o tráfego de entrada e pode “cortar o excesso” antes que as solicitações derrubem o backend.
- Depois, discutiremos resiliência: timeouts, circuit breakers, bulkheads. O gateway é o ponto em que é conveniente definir centralmente timeouts para chamadas externas e ligar/desligar serviços problemáticos (por exemplo, “desligar” temporariamente o Commerce API se ele estiver falhando).
- Por fim, ao falar de escalabilidade e deploy, veremos o gateway como um cluster separado, que pode ser balanceado, lançado em esquemas blue/green e canary e revertido independentemente dos serviços MCP internos.
Em essência, se antes você pensava “eu tenho o App e um servidor MCP”, agora o esquema se expande para “eu tenho o App, um MCP Gateway, vários clusters de backend/Agents e um commerce‑backend”. E é justamente o gateway que permite não complicar a configuração para o ChatGPT — ele continua vendo um único ponto MCP.
11. Erros típicos ao trabalhar com MCP Gateway
Erro nº 1: transformar o gateway em um “monstro de negócios”.
Armadilha comum: já que tudo passa pelo gateway, por que não adicionar nele o cálculo de descontos, a seleção de SKUs, regras complexas de categorias ou validação de promo codes? No fim, você ganha um serviço gordo, difícil de escalar e alterar, e perde o sentido de separar Gift API, Commerce API e outros componentes especializados. É melhor manter o gateway como uma camada de rede fina e deixar tudo o que é específico do domínio dentro dos serviços de perfil.
Erro nº 2: armazenar no gateway estado de usuário de longa duração.
A ideia “vamos guardar o carrinho do usuário direto na memória do gateway” soa tentadora enquanto você tem uma instância. Assim que aparece a segunda, começa a dor: onde está o carrinho “verdadeiro” — na instância A ou B? O que acontece após um restart? O gateway deve permanecer stateless: no máximo — um pequeno cache de handshakes ou configs; já todo o estado de sessões e pedidos fica no BD ou em serviços especializados.
Erro nº 3: deixar o ChatGPT ciente da topologia interna dos serviços.
Se você começa a expor ao ChatGPT vários servidores de API (Gift API separado, Commerce API separado) e usar o gateway “só em alguns lugares”, perde a principal vantagem: ponto único de entrada e controle centralizado. Ao mudar a topologia, você terá de alterar configurações em vários pontos. É muito mais simples configurar o MCP Gateway uma vez como o endpoint oficial do App e esconder atrás dele todas as mudanças internas.
Erro nº 4: duplicar a lógica entre serviços em todos os backends.
Às vezes, as equipes tentam implementar autenticação, rate limiting, logging e localização em cada serviço REST separadamente. No fim, a política de direitos e limites no Gift API é uma, no Commerce API é outra, e o comportamento do App se torna imprevisível. O gateway existe justamente para centralizar essas coisas: verificar token, determinar tenant e locale, registrar a chamada, aplicar limites e, só então, seguir para o serviço específico.
Erro nº 5: sobrecarregar o gateway com cálculos pesados e chamadas de LLM.
Tecnicamente, nada impede você de chamar outro modelo de LLM a partir do gateway, fazer agregações complexas ou operações em batch demoradas. Mas isso rapidamente o transformará em mais um backend pesado, impossível de escalar e isolar direito. O gateway deve permanecer rápido e previsível: no máximo, transformação leve e roteamento. Tudo o que for pesado — para dentro dos serviços REST ou para filas/workers, de que falaremos mais adiante no módulo.
Erro nº 6: complicar a infraestrutura cedo demais.
O extremo oposto é erguer logo um cluster Kubernetes separado, um stack NGINX, Cloudflare Workers e um monte de configs complexas para um App pequeno de estudo. Isso não faz sentido enquanto você não tiver carga real e requisitos de alta disponibilidade. É perfeitamente normal começar com um único monólito MCP ou um gateway Node simples e só depois, à medida que crescer, separar componentes em clusters e serviços gerenciados.
GO TO FULL VERSION