Skip to content
Home Prices Guides FAQ Journal

Servidor MCP para eSIM — una guía para desarrolladores

Cómo construimos la primera API de eSIM nativa para agentes: ciclo de vida de los tokens, defensa de gasto en cinco capas, verificación de pagos con HMAC, interruptores de apagado y las decisiones de diseño que hacen que los raíles de pago en cripto encajen de forma única con los agentes autónomos.

La mayoría de los servicios en línea se construyeron para navegadores. Una persona se sienta frente a una página autenticada con cookie de sesión, hace clic en botones, rellena campos, y el backend asume ese flujo de principio a fin. Los agentes de IA no encajan en ese molde: no tienen sesión de navegador, ni DOM en el que hacer clic, ni cámara para escanear un código QR, ni paciencia para los bailes de OAuth. Construir para agentes significa reconstruir las capas de autenticación, pago y entrega en torno a suposiciones distintas.

Este artículo recorre las decisiones arquitectónicas detrás del servidor MCP de Roamzy. Está pensado para desarrolladores que crean agentes de IA que interactúan con sistemas de comercio reales, y para ingenieros de otros servicios que se preguntan cómo es realmente una API "lista para agentes" en producción.

El servidor MCP es una capa fina

El servidor MCP en sí es pequeño: unas 400 líneas de TypeScript, empaquetadas en un tarball de 100KB mediante esbuild. Existe únicamente para traducir entre dos protocolos: peticiones MCP basadas en stdio desde Claude Desktop / Cursor / Continue, y llamadas REST por HTTPS a https://roamzy.io/api/v1/*.

No hay lógica de negocio en el servidor MCP. Los límites de gasto, la validación de tokens, la verificación de pagos: todo eso vive en el backend, donde puede probarse de forma aislada, auditarse de manera centralizada y cambiarse sin obligar a cada usuario de agentes a actualizar su servidor MCP. El servidor MCP es solo el puente entre protocolos.

Este es el límite correcto. Poner lógica de negocio en el servidor MCP la dispersaría por N+1 lugares (el servidor más el tarball instalado de cada cliente), introduciría retrasos en las actualizaciones y crearía superficie de ataque en el portátil del usuario. Al mantener el servidor MCP como puro pegamento de cliente HTTP, obtenemos una capa fina que puede auditarse con facilidad (unos 30 minutos para un usuario consciente de la seguridad) y actualizarse con solo volver a publicar el tarball en la misma URL.

Los tokens se almacenan con hash SHA-256 y se muestran una sola vez

El formato del token es rk_live_<32-char-base64url>, al estilo de Stripe. El prefijo es fijo para que el Secret Scanning de GitHub y herramientas similares puedan detectar filtraciones. El cuerpo son 24 bytes aleatorios codificados en base64url, lo que da 192 bits de entropía.

Al crearse, el texto plano se muestra al usuario exactamente una vez, en un recuadro de advertencia amarillo. El servidor almacena solo el hash SHA-256. Se conserva una pista de prefijo de 12 caracteres (rk_live_abc1) para mostrarla, de modo que el usuario pueda identificar qué token es cada uno cuando hay varios activos.

La comparación usa timingSafeEqual de Node para frustrar los oráculos de temporización. La revocación del token se implementa como un borrado lógico (estableciendo revoked_at) para que el rastro de auditoría sobreviva. La revocación es inmediata; la siguiente llamada a la API con un token revocado devuelve un 401 en cuestión de milisegundos.

Defensa de gasto en cinco capas

La mayoría de los sistemas basados en tokens tienen uno o dos límites de gasto. Nosotros tenemos cinco, cada uno orientado a un modo de fallo distinto:

  1. Límite diario por token (predeterminado $50 USDT, configurable entre $1 y $1000). Tope sobre el gasto de las últimas 24 horas.
  2. Límite mensual por token (predeterminado $500, configurable entre $1 y $10000). Tope sobre el gasto de los últimos 30 días.
  3. Periodo de enfriamiento: durante los primeros 7 días tras la creación del token, el gasto total se limita a $50 USDT independientemente del límite diario. Está fijado en el código; no se puede ampliar. Esto acota el alcance del daño si un token se filtra justo después de crearse (un desarrollador lo pega en un gist público por error).
  4. Umbral de transacción grande (predeterminado $200): las compras por encima de este importe requieren confirmación manual por parte del usuario humano en el panel. La API devuelve big_txn_needs_confirmation y el agente debe presentarlo al usuario.
  5. Alcance de compras opcional: de forma predeterminada, la casilla "Permitir compras" está sin marcar al crear el token, lo que significa que el token solo puede llamar a endpoints de lectura. El usuario debe optar conscientemente por habilitarlo. Para un agente que solo necesita consultar el catálogo, esta es la configuración más segura.

Los contadores viven en la tabla api_tokens con rotación de ventana perezosa: cuando una petición lee el contador, si la ventana almacenada (YYYY-MM-DD para la diaria, YYYY-MM para la mensual) no coincide con la actual, el contador se trata como cero. Esto evita un cron diario y tolera el desfase del reloj de la máquina.

La aplicación de los límites ocurre dos veces. En el momento de crear el pedido, el servidor ejecuta una comprobación blanda: ¿el importe más spent_today superaría el límite diario? Si es así, la petición se rechaza con un código de error estable y una pista del margen restante. En el momento del webhook de pago, el contador se incrementa de forma definitiva, solo cuando se han recibido realmente USDT.

Integridad de los pagos — HMAC y restricciones de unicidad

La capa antifraude más importante es la verificación HMAC-SHA-512 sobre el webhook de NowPayments. El secreto compartido (IPN_SECRET) reside en el entorno del servidor. Cada carga útil del webhook llega con una cabecera x-nowpayments-sig; el servidor calcula el HMAC sobre el cuerpo crudo de la petición y lo compara. Sin el secreto, un atacante no puede falsificar una carga útil de "pagado", aunque conozca el ID del pedido y el ID del intent (probablemente los conoce, ya que se devuelven en la respuesta del pedido).

La segunda capa es una restricción UNIQUE a nivel de base de datos sobre (ref_type, ref_id) en la tabla del libro mayor. Cada crédito se escribe con ref_type="nowpayments" y ref_id=<payment_id>. Si el webhook se dispara dos veces para el mismo payment_id (reintento de red, ataque de repetición), el segundo insert falla la restricción de unicidad y la aplicación corta el flujo con alreadyApplied=true. No es posible un doble crédito.

La tercera capa es arquitectónica: solo las funciones de services/billing.ts pueden modificar el saldo de la eSIM. No hay ningún endpoint de administración para añadir saldo de forma arbitraria; incluso la herramienta de administración "activar eSIM manualmente" pasa por la misma ruta de crédito. Esto minimiza la superficie de ataque: existe exactamente una ruta de código por la que el saldo puede aumentar, y es la que tiene todas las comprobaciones de seguridad.

Tres interruptores de apagado para la respuesta ante incidentes

Las cosas van a salir mal. Los tokens se filtran. El agente de un usuario se descontrola y satura el sistema con pedidos. Un proveedor de pagos sufre una caída. Construimos tres interruptores de apagado independientes para que la respuesta pueda ser proporcional:

  • Revocación por token: el usuario hace clic en Revocar en el panel. Afecta solo a ese token. Se usa cuando un agente se comporta mal o un token se ha filtrado.
  • Bloqueo de agentes por usuario: un administrador llama a POST /api/admin/users/:id/agent-block con un motivo. Afecta a todos los tokens de ese usuario. Se usa ante el abuso o el compromiso de una cuenta individual.
  • Pausa global de agentes: un administrador llama a POST /api/admin/agents/pause. Cada token Bearer de todos los usuarios devuelve 503. Se usa ante incidentes de infraestructura o investigaciones de seguridad.

El estado es visible para los agentes en GET /api/v1/status (sin autenticación, CORS *). Los agentes que se comportan bien lo consultan antes de comprar y se detienen cuando purchases_paused o agents_paused es verdadero. Roamzy se reserva el derecho de limitar a los agentes que ignoren el endpoint de estado.

También hay una pausa más granular: agents.purchases.paused bloquea solo POST /orders, dejando vivos los endpoints de lectura. Es útil al investigar un problema del lado del pago sin dejar a oscuras las llamadas informativas a la API.

El QR es la superficie principal de activación, no la LPA

La mayoría de los flujos con agentes se ejecutan en un dispositivo distinto de aquel donde vive la eSIM. El agente podría ser Claude Desktop en un Mac, mientras que la eSIM tiene que aterrizar en el iPhone del usuario. En este caso entre dispositivos, el QR es la superficie correcta: la API devuelve qr_image_url (una URL de PNG mediante el renderizador qrserver.com), el agente lo incrusta en línea en el chat y el usuario lo escanea con la cámara de su teléfono.

El campo lpa_url también se devuelve, pero solo es útil cuando el agente y el dispositivo de destino son el mismo, por ejemplo un agente que se ejecuta en el iPhone del usuario. En iOS 17.4+ y Android 14+, tocar una URL lpa: abre directamente el instalador de eSIM del sistema. Pero para la mayoría de las experiencias con agentes, el QR es más fiable.

Es una decisión de diseño menor, pero importa: muchos servidores MCP se escriben con un modelo mental de "pestaña de navegador" en el que el usuario está sentado junto al agente y tocaría un enlace. La realidad del uso de eSIM por parte del consumidor es entre dispositivos.

Por qué USDT, no dinero fiat

Los pagos en cripto son nativos para agentes por tres razones que no siempre son evidentes:

  1. Sin cumplimiento de almacenamiento de tarjetas. Almacenar datos de tarjetas exige el cumplimiento de PCI DSS Nivel 1, además de las reglas de las redes de tarjetas sobre cómo debe autenticar el titular cada transacción. La mayoría de estas reglas dan por hecho que hay una persona en el teclado. La cripto esquiva todo ese régimen.
  2. Sin riesgo de contracargos. Una vez que una transacción en USDT se confirma en la cadena, es definitiva. No existe la vía de "el titular de la tarjeta disputó esta transacción seis meses después". Esto importa más en el comercio con agentes que en el comercio por navegador, porque puede que el usuario humano no haya sido quien revisó y aprobó el gasto en tiempo real.
  3. Claridad en la liquidación. El presupuesto del agente está denominado en USDT. El monedero del usuario contiene USDT. El comercio liquida en USDT. No hay conversión a dinero fiat por el medio, ni exposición al tipo de cambio, ni retraso por horario bancario. Todo el flujo tiene forma de API de principio a fin.

Esto sí reduce el mercado al que se puede llegar: la mayoría de los viajeros que no son nativos de cripto no comprarán una eSIM en USDT. Pero para el grupo que sí lo hace (viajeros nativos de cripto, flujos impulsados por agentes, desarrolladores que automatizan sus viajes), elimina toda una pila de fricción.

Lo que no construimos (y por qué)

Tokens con alcances al estilo de OAuth. Tenemos un único indicador de permiso (compra / sin compra) en lugar de una lista granular de alcances. El razonamiento: los agentes de 2026 todavía no necesitan una distinción de "solo leer saldo" o "solo leer eSIM". Si la necesitan más adelante, añadiremos alcances. Los modelos de alcance prematuros crean más errores de los que evitan.

Registro anónimo con prioridad para el agente. Por ahora el agente no puede crear un usuario de Roamzy sobre la marcha; la persona tiene que iniciar sesión una vez en un navegador para emitir un token. Esto es fricción. Lo tenemos en la lista de pendientes como flujo con prioridad para el agente (creación de usuario anónimo + reclamación por enlace mágico), pero esperamos datos de tráfico antes de construirlo. Si el 50% de los usuarios potenciales abandona en "crear cuenta", es una señal que merece la pena optimizar; si es el 5%, no lo es.

Entrega por webhook para los eventos del agente. Actualmente el agente consulta GET /orders/:id para las actualizaciones de estado. Un webhook basado en push sería más eficiente a gran escala, pero añade complejidad (el agente tiene que alojar un endpoint, verificación de firma, política de reintentos). Consultar a intervalos de 5 segundos durante una ventana de confirmación de USDT de 10 minutos son 120 peticiones: barato.

Pruébalo tú mismo

Si estás construyendo un agente de IA y quieres integrar compras de eSIM, la vía más rápida es el servidor MCP. Instálalo en Claude Desktop en 60 segundos: consulta el tutorial. Para clientes que no son MCP, la especificación completa de OpenAPI 3.0 está en /api/v1/openapi.json, la interfaz interactiva de Swagger está en /api/v1/docs y la guía extensa para agentes está en /llms-full.txt.

Para preguntas más a fondo, el bot de soporte de Telegram está en @roamzy_support_bot.