A maioria dos serviços online foi feita para navegadores. Um usuário senta diante de uma página autenticada por cookie de sessão, clica em botões, preenche campos, e o backend presume esse fluxo de trabalho o tempo todo. Os agentes de IA não se encaixam nesse molde — eles não têm sessão de navegador, não têm DOM para clicar, não têm câmera para escanear um QR code e não têm paciência para danças de OAuth. Construir para agentes significa reconstruir as camadas de autenticação, pagamento e entrega em torno de suposições diferentes.
Este post percorre as escolhas de arquitetura por trás do servidor MCP da Roamzy. Ele é destinado a desenvolvedores que constroem agentes de IA que interagem com sistemas de comércio reais, e a engenheiros de outros serviços que se perguntam como uma API "pronta para agentes" realmente se parece em produção.
O servidor MCP é um wrapper fino
O servidor MCP em si é pequeno — cerca de 400 linhas de TypeScript, empacotado em um tarball de 100KB via esbuild. Ele existe apenas para traduzir entre dois protocolos: requisições MCP baseadas em stdio do Claude Desktop / Cursor / Continue, e chamadas REST por HTTPS para https://roamzy.io/api/v1/*.
Não há lógica de negócio no servidor MCP. Limites de gasto, validação de token, verificação de pagamento — tudo isso fica no backend, onde pode ser testado isoladamente, auditado de forma centralizada e alterado sem forçar cada usuário de agente a atualizar seu servidor MCP. O servidor MCP é apenas a ponte entre protocolos.
Essa é a fronteira certa. Colocar lógica de negócio no servidor MCP a espalharia por N+1 lugares (o servidor + o tarball instalado de cada cliente), introduziria atraso de atualização e criaria superfície de ataque no laptop do usuário. Ao manter o servidor MCP como cola pura de cliente HTTP, obtemos uma camada fina que pode ser auditada facilmente (cerca de 30 minutos para um usuário atento à segurança) e atualizada simplesmente republicando o tarball na mesma URL.
Os tokens recebem hash SHA-256 e são exibidos uma única vez
O formato do token é rk_live_<32-char-base64url> — no estilo Stripe. O prefixo é fixo para que o Secret Scanning do GitHub e ferramentas semelhantes possam detectar vazamentos. O corpo são 24 bytes aleatórios codificados em base64url, totalizando 192 bits de entropia.
Na criação, o texto puro é mostrado ao usuário exatamente uma vez, em uma caixa de aviso amarela. O servidor armazena apenas o hash SHA-256. Uma dica de prefixo de 12 caracteres (rk_live_abc1) é mantida para exibição, para que o usuário consiga identificar qual token é qual quando vários estão ativos.
A comparação usa o timingSafeEqual do Node para derrotar oráculos de tempo. A revogação de token é implementada como soft-delete (definindo revoked_at) para que a trilha de auditoria sobreviva. A revogação é imediata; a próxima chamada de API com um token revogado retorna 401 em milissegundos.
Defesa de gasto em cinco camadas
A maioria dos sistemas baseados em token tem um ou dois limites de gasto. Nós temos cinco, cada um tratando de um modo de falha diferente:
- Limite diário por token (US$ 50 USDT por padrão, configurável de US$ 1 a US$ 1000). Teto sobre o gasto móvel de 24 horas.
- Limite mensal por token (US$ 500 por padrão, configurável de US$ 1 a US$ 10000). Teto sobre o gasto móvel de 30 dias.
- Período de carência — nos primeiros 7 dias após a criação do token, o gasto total fica limitado a US$ 50 USDT, independentemente do limite diário. Fixo no código; não pode ser aumentado. Isso limita o raio de impacto caso um token vaze logo após a criação (um desenvolvedor o cola num gist público por engano).
- Limiar de transação grande (US$ 200 por padrão) — compras acima disso exigem confirmação manual pelo usuário humano no painel. A API retorna
big_txn_needs_confirmatione o agente precisa expor isso ao usuário. - Opt-in de escopo de compra — por padrão, a caixa "Permitir compras" fica desmarcada na criação do token, o que significa que o token só pode chamar endpoints de leitura. O usuário precisa optar conscientemente por habilitar. Para um agente que só precisa consultar o catálogo, essa é a configuração mais segura.
Os contadores ficam na tabela api_tokens com rotação preguiçosa de janela: quando uma requisição lê o contador, se a janela armazenada (YYYY-MM-DD para diária, YYYY-MM para mensal) não corresponde à atual, o contador é tratado como zero. Isso evita um cron job diário e tolera o desvio do relógio da máquina.
A aplicação acontece duas vezes. No momento da criação do pedido, o servidor faz uma verificação leve: o valor + gasto_hoje ultrapassaria o limite diário? Se sim, a requisição é rejeitada com um código de erro estável e uma dica da margem restante. No momento do webhook de pagamento, o contador é incrementado de fato — somente quando o USDT real foi realmente recebido.
Integridade do pagamento — HMAC e restrições de unicidade
A camada antifraude mais importante de todas é a verificação HMAC-SHA-512 no webhook da NowPayments. O segredo compartilhado (IPN_SECRET) fica no ambiente do servidor. Todo payload de webhook chega com um cabeçalho x-nowpayments-sig; o servidor calcula o HMAC sobre o corpo bruto da requisição e compara. Sem o segredo, um atacante não consegue forjar um payload de "pago" — mesmo que conheça o ID do pedido e o ID do intent (provavelmente conhece, já que são retornados na resposta do pedido).
A segunda camada é uma restrição UNIQUE no nível do banco de dados sobre (ref_type, ref_id) na tabela do ledger. Todo crédito é gravado com ref_type="nowpayments" e ref_id=<payment_id>. Se o webhook disparar duas vezes para o mesmo payment_id (retry de rede, ataque de replay), o segundo insert falha na restrição de unicidade e a aplicação interrompe o fluxo com alreadyApplied=true. Nenhum crédito em dobro é possível.
A terceira camada é arquitetônica: somente as funções de services/billing.ts podem alterar o saldo do eSIM. Não existe endpoint administrativo para adicionar saldo arbitrariamente; até a ferramenta administrativa de "ativar eSIM manualmente" passa pelo mesmo caminho de crédito. Isso minimiza a superfície de ataque — existe exatamente um caminho de código pelo qual o saldo pode aumentar, e é aquele com todas as verificações de segurança.
Três interruptores de emergência para resposta a incidentes
As coisas vão dar errado. Tokens vazam. O agente de um usuário pifa e dispara pedidos em massa. Um provedor de pagamento tem uma indisponibilidade. Construímos três interruptores de emergência independentes para que a resposta possa ser proporcional:
- Revogação por token — o usuário clica em Revogar no painel. Afeta apenas aquele token. Usado quando um agente está se comportando mal ou um token vazou.
- Bloqueio de agente por usuário — o admin chama
POST /api/admin/users/:id/agent-blockcom um motivo. Afeta todos os tokens daquele usuário. Usado em casos de abuso ou comprometimento de uma conta específica. - Pausa global de agentes — o admin chama
POST /api/admin/agents/pause. Todo token Bearer de todos os usuários retorna 503. Usado em incidentes de infraestrutura ou investigações de segurança.
O status fica visível para os agentes em GET /api/v1/status (sem autenticação, CORS *). Agentes bem-comportados consultam isso antes das compras e recuam quando purchases_paused ou agents_paused é verdadeiro. A Roamzy reserva-se o direito de limitar agentes que ignoram o endpoint de status.
Há também uma pausa mais granular: agents.purchases.paused bloqueia apenas o POST /orders, deixando os endpoints de leitura vivos. Útil ao investigar um problema do lado do pagamento sem apagar as chamadas de API informativas.
O QR é a superfície de ativação principal, não o LPA
A maioria dos fluxos de agente roda em um dispositivo diferente daquele onde o eSIM vive. O agente pode ser o Claude Desktop num Mac, enquanto o eSIM precisa chegar ao iPhone do usuário. Nesse caso de múltiplos dispositivos, o QR é a superfície certa: a API retorna qr_image_url (uma URL de PNG via o renderizador qrserver.com), o agente o embute diretamente no chat, e o usuário escaneia com a câmera do celular.
O campo lpa_url também é retornado, mas só é útil quando o agente e o dispositivo de destino são o mesmo — por exemplo, um agente rodando no iPhone do usuário. No iOS 17.4+ e no Android 14+, tocar em uma URL lpa: abre o instalador de eSIM do sistema diretamente. Mas para a maioria das experiências de agente, o QR é mais confiável.
Essa é uma pequena escolha de design, mas ela importa: muitos servidores MCP são escritos com um modelo mental de "aba de navegador", em que o usuário está sentado ao lado do agente e tocaria num link. A realidade do uso de eSIM pelo consumidor é entre dispositivos.
Por que USDT, e não moeda fiduciária
Os pagamentos em cripto são nativos de agentes por três razões que nem sempre são óbvias:
- Sem conformidade de armazenamento de cartão. Armazenar dados de cartão exige conformidade com o PCI DSS Nível 1, além das regras das bandeiras sobre como o portador do cartão deve autenticar cada transação. A maioria dessas regras presume que há um humano no teclado. A cripto contorna todo esse regime.
- Sem risco de chargeback. Uma vez que uma transação em USDT é confirmada on-chain, ela é definitiva. Não existe o caminho do "o portador do cartão contestou esta transação seis meses depois". Isso importa mais para o comércio com agentes do que para o comércio em navegador, porque o usuário humano pode não ter sido quem revisou e aprovou o gasto em tempo real.
- Clareza de liquidação. O orçamento do agente é denominado em USDT. A carteira do usuário guarda USDT. O comerciante liquida em USDT. Não há conversão fiduciária no meio, nenhuma exposição a taxa de câmbio, nenhum atraso de horário bancário. O fluxo inteiro tem formato de API de ponta a ponta.
Isso de fato estreita o mercado endereçável — a maioria dos viajantes não nativos em cripto não vai comprar um eSIM em USDT. Mas para a parcela que compra (viajantes nativos em cripto, fluxos guiados por agentes, desenvolvedores automatizando viagens), isso remove uma pilha de atrito.
O que não construímos (e por quê)
Tokens com escopo no estilo OAuth. Temos uma única flag de permissão (compra / sem compra) em vez de uma lista granular de escopos. O raciocínio: os agentes em 2026 ainda não precisam de uma distinção "somente ler saldo" ou "somente ler eSIM". Se precisarem depois, adicionaremos escopos. Modelos de escopo prematuros criam mais bugs do que evitam.
Cadastro anônimo com agente em primeiro lugar. Atualmente o agente não consegue criar um usuário Roamzy na hora; o humano precisa entrar uma vez em um navegador para emitir um token. Isso é atrito. Temos isso no backlog como fluxo agent-first (criação de usuário anônimo + reivindicação por magic-link), mas estamos esperando dados de tráfego antes de construir. Se 50% dos usuários em potencial desistirem no "criar conta", esse é um sinal que vale otimizar; se for 5%, não é.
Entrega por webhook para eventos do agente. Atualmente o agente consulta GET /orders/:id em busca de atualizações de status. Um webhook baseado em push seria mais eficiente em escala, mas adiciona complexidade (o agente tem que hospedar um endpoint, verificação de assinatura, política de retry). Fazer polling a intervalos de 5 segundos durante uma janela de confirmação de USDT de 10 minutos dá 120 requisições — barato.
Experimente você mesmo
Se você está construindo um agente de IA e quer integrar compras de eSIM, o caminho mais rápido é o servidor MCP. Instale no Claude Desktop em 60 segundos — veja o tutorial. Para clientes não-MCP, a especificação completa OpenAPI 3.0 está em /api/v1/openapi.json, a Swagger UI interativa está em /api/v1/docs, e a orientação em formato longo para agentes está em /llms-full.txt.
Para perguntas mais aprofundadas, o bot de suporte no Telegram está em @roamzy_support_bot.