MCP en Producción: Cómo Convertir tu SaaS en Agéntico (y Qué Rompe por el Camino)
Hay una diferencia enorme entre montar un servidor MCP de juguete y abrir un producto en producción a los agentes de IA. La primera te lleva una tarde. La segunda te obliga a tomar decisiones que afectan a tu modelo de permisos, a tu facturación y a tu superficie de ataque.
Llevo meses en la segunda. Un ERP multi-tenant, con cientos de clientes reales, expuesto a Claude y a cualquier otro cliente MCP a través de un servidor propio. Cuatro dominios abiertos: clientes, facturas, gastos y tareas.
Este artículo no es un tutorial de cómo montar un servidor MCP. De eso ya escribí una guía completa con OAuth 2.1, que tengo pendiente de actualizar. Este artículo trata de qué le pasa a tu producto cuando lo abres a los agentes: qué decides, qué puedes cobrar, y qué se rompe.
Incluye dos caminos que llegamos a construir y acabamos retirando. Ninguno de los dos falló: los dos funcionaban. Uno pedía demasiado al usuario y el otro enseñaba de más, y en ambos casos elegimos lo mismo antes que la comodidad.
Lo primero: la especificación que aprendiste ya no es la vigente
Si montaste tu servidor MCP hace unos meses, hay cosas que se te han quedado obsoletas. La especificación del 28 de julio de 2026 trajo cambios de fondo:
- El núcleo pasa a ser stateless. De protocolo bidireccional con estado a petición/respuesta. Un servidor remoto que antes necesitaba sticky sessions y un almacén compartido ahora corre detrás de un balanceador de carga normal.
- Dynamic Client Registration queda deprecado en favor de CIMD (Client ID Metadata Documents). Sigue funcionando, pero es la vía de salida.
- El transporte HTTP+SSE también queda deprecado, con un año de margen.
- Los resultados de
tools/listpasan a ser cacheables con unttlMsque decide el servidor.
Todo esto se mueve más rápido de lo que tu documentación se actualiza. Si vas a abrir tu producto a agentes, asume que parte de lo que construyas caducará en meses, y no te acoples al protocolo más de lo imprescindible.
La decisión que define tu producto: quién es el usuario del agente
Esta es la primera bifurcación real, y la decide el equipo técnico aunque sus consecuencias sean comerciales.
Cuando abres tu API a un agente, tienes dos caminos:
Camino A: una credencial de servicio. El agente se autentica como aplicación, con una clave que tú emites, y accede a lo que esa clave permita.
Camino B: el agente opera como el usuario que lo autorizó, con sus mismos permisos.
El camino A es muchísimo más fácil de construir. Es una cabecera con un token y listo. Y es el que te deja fuera de cualquier conversación seria con un cliente enterprise.
Nosotros fuimos por el B, y la consecuencia arquitectónica es que el servidor MCP no decide quién puede hacer qué. No tiene lógica de permisos. Recibe el token del usuario, lo reenvía al backend, y es el backend quien resuelve permisos y ámbito de organización. Si el usuario no puede ver algo en la interfaz del ERP, el agente tampoco lo ve.
El flujo, resumido:
Cliente MCP → POST /mcp sin token
← 401 + WWW-Authenticate: Bearer resource_metadata="…"
→ GET /.well-known/oauth-protected-resource
← { resource, authorization_servers: [backend] }
→ se registra contra el servidor de autorización
→ abre la pantalla de consentimiento del ERP en el navegador
↓
[el usuario autoriza]
↓
→ POST /mcp con su token; cada tool corre con sus permisos
Dicho en la frase que usamos con los clientes: conectar el agente no da permisos nuevos. Es la misma persona, con la misma cuenta, haciendo lo mismo por otro canal. En cualquier revisión de seguridad acaba llegando la pregunta de qué acceso tiene la IA a los datos, y con este modelo la respuesta cabe en una línea: ninguno propio.
Hay un segundo nivel de control: el usuario no solo hereda sus permisos, además elige cuáles cede. En la pantalla de consentimiento marca qué le deja tocar al agente, dominio por dominio y separando lectura de escritura. Leer clientes sí, escribir facturas no.
No es una casilla decorativa. El agente solo ve las herramientas de los permisos que se le concedieron: una conexión de solo lectura no puede llamar a una que escriba, porque esa herramienta ni siquiera aparece en su catálogo. Y si lo intenta por otra vía, recibe un insufficient_scope.
Eso cubre lo que de verdad frena una compra, que casi nunca es “no quiero que la IA vea mis datos” sino “no quiero que la IA me los cambie”.
La suite de conformidad
Todo lo anterior —el 401 con su cabecera, el registro del cliente, la pantalla de consentimiento, los permisos aplicados en cada llamada— son casuísticas que se rompen en silencio. La forma que tenemos de garantizar que siguen funcionando es una suite de conformidad que habla el protocolo de verdad.
Ejecuta el flujo OAuth completo —login, registro, autorización, el paso del código al token con PKCE— y después lanza un cliente MCP real, el del SDK oficial, que hace initialize, tools/list y tools/call contra el servidor vivo, recorriendo las herramientas de cada dominio y comprobando que responden lo que deben.
Corre aparte de la unitaria, que mockea el ERP y el cliente y por eso no puede ver nada de esto: que el servidor de autorización anunciado responda, que se anuncie S256 y no plain, que un código no se pueda usar dos veces, que un redirect_uri distinto se rechace, que los permisos se apliquen en cada llamada y que dos clientes no compartan sesión.
Es la suite que decide si se despliega.
Desplegar sin cortar una llamada a la mitad
Un despliegue normal mata el proceso viejo y levanta el nuevo. Con una API eso se nota poco: la petición que se pierde la reintenta el cliente. Con un agente, lo que se corta es el turno de un usuario que está esperando una respuesta, y el reintento no siempre existe.
Node, por defecto, muere en el acto cuando llega el SIGTERM. Así que hay que sostenerlo a mano, y el reparto que nos funciona es este: el pod viejo deja de aceptar conexiones nuevas pero no muere hasta terminar lo que tiene entre manos. Lo que llega a partir de ese momento va al pod nuevo.
En números, sobre un margen total de 40 segundos: 5 segundos de preStop para que el ingress tenga tiempo de sacar el pod del balanceo, hasta 25 drenando la llamada que estuviera en curso, y el resto para volcar la auditoría pendiente antes de cerrar.
Esa pausa inicial hace falta porque la retirada del endpoint y el SIGTERM se dan en paralelo: sin ella, el pod puede seguir recibiendo tráfico cuando ya está cerrando. Y al final hay un corte de seguridad, porque una conexión que no cierra sola no puede retrasar el apagado hasta que el kubelet mande un SIGKILL y deje el cierre limpio a medias.
¿Se puede saber con certeza que un pod ya no recibe nada?
Del todo, no.
Lo que sí se puede saber con certeza es el trabajo propio: en vez de dormir un número fijo de segundos, el proceso lleva la cuenta de las llamadas que tiene en vuelo y espera a que llegue a cero, con el temporizador solo como tope por si alguna se queda colgada. Eso no es una estimación, es un hecho observado desde dentro.
Lo que no se puede saber desde dentro es si el tráfico ha dejado de llegar, y ahí hay dos motivos distintos:
La retirada del pod del balanceo es asíncrona. Cuando Kubernetes lo marca para terminar, la noticia se propaga a kube-proxy, al ingress y a los servicios, cada uno a su ritmo. Los 5 segundos de pausa son una apuesta a que da tiempo, no una garantía. En vez de esperar un rato fijo, el preStop puede preguntarle a la API de Kubernetes si el pod sigue apareciendo en el EndpointSlice y no seguir hasta que no aparezca.
Las conexiones ya abiertas siguen trayendo peticiones. Este es el que se olvida. server.close() deja de aceptar conexiones nuevas, pero no toca las que ya están establecidas, y con HTTP keep-alive un cliente puede seguir mandando peticiones por un socket abierto aunque el pod ya no figure en ningún balanceador. Cerrarlo del todo pide dos cosas más: responder con Connection: close mientras se drena, y cerrar las conexiones que estén ociosas.
Aun con las dos cosas, queda una ventana de milisegundos en la que una petición ya en vuelo llega a un pod que se está apagando. Ahí la respuesta no es del servidor: es que haya más de una réplica y el cliente reintente. Con una sola réplica no hay a dónde reintentar, y devolver un error solo convierte “no llega nada” en “llega un error”.
El núcleo stateless de la especificación de julio quita este problema de en medio. Sin sesiones pegadas a un pod concreto, cualquier réplica atiende cualquier petición, y un reintento pasa de ser una conversación perdida a un salto de milisegundos.
La historia del PDF: cuatro intentos
De esto es de lo que menos se escribe en los tutoriales.
El caso de uso que queríamos implementar: el usuario le pide una factura al agente y quiere el PDF.
Intento 1: el binario embebido
La especificación MCP contempla devolver recursos embebidos. Así que devolvimos el PDF en base64 dentro de la respuesta.
El payload era correcto. Lo verificamos sobre tráfico real: 41.116 caracteres de base64 que empezaban por %PDF-1.4. Impecable según la especificación.
Y Claude Desktop no hacía absolutamente nada con él. El usuario pedía su presupuesto y recibía un mensaje diciendo que no se podía mostrar ese tipo de archivo.
La especificación deja en manos del cliente qué hacer con un recurso embebido. Cumplirla no te garantiza que funcione. No era un fallo nuestro y no se arreglaba emitiendo mejor.
Miramos qué hacían los servidores MCP que sí resuelven esto. Los tres que encontramos coincidían en no mandar el fichero: escriben en disco y devuelven la ruta. Pueden porque corren en la máquina del usuario. El nuestro vive en Kubernetes.
Intento 2: el puente local
Si el fichero tiene que escribirse en la máquina del usuario, pongamos algo en la máquina del usuario. Publicamos un puente stdio: un paquete de npm que corre en su ordenador, reenvía la conversación al servidor remoto y guarda los ficheros en disco.
Funcionaba de punta a punta. Está despublicado.
El motivo no fue técnico. Exigía instalar Node y editar un fichero de configuración JSON, y la mayoría de la gente conecta pegando una dirección en un formulario.
| Conector remoto | Puente local | |
|---|---|---|
| Qué hace el usuario | pega una dirección | instala y edita un fichero |
| Qué necesita saber | nada | manejar la configuración del cliente |
| Consultar y crear datos | sí | sí |
| Guardar ficheros en su disco | no | sí |
La columna de la derecha gana en capacidades y pierde en adopción. Para recuperar un PDF estábamos pidiendo un proceso de instalación entero, y ese no era el camino: si tu MCP necesita que el usuario instale algo, tienes un techo que no rompes con funcionalidades.
Intento 3: el enlace firmado
Tercera vía, esta vez sin pedirle nada al usuario: el backend firma un enlace temporal sobre el documento y el MCP lo presenta. El binario deja de viajar por el protocolo, y el usuario solo tiene que pinchar.
Se implementó, se validó y nunca pasó del entorno de integración. Lo paramos ahí al ver lo que el propio enlace lleva dentro: la ruta de almacenamiento y datos de la infraestructura que lo sirve. Una dirección pensada para descargar un fichero acaba describiendo dónde vive ese fichero.
Lo que lo convierte en un error de bulto es la incoherencia. El servidor ya tenía una función cuyo único trabajo era que ninguna dirección interna llegara al modelo. Y el enlace las regalaba todas.
Pero hay algo peor que la fuga de nombres. El enlace es una credencial portadora. Cualquiera que lo tenga se descarga el documento, sin token y sin sesión. Una caducidad corta acota la ventana, no elimina el problema.
Intento 4: la sesión del usuario
Lo que quedó: el MCP devuelve una dirección de la propia aplicación, que se abre con la sesión que el usuario ya tiene. Sin credencial portadora, sin infraestructura expuesta, y con el control de acceso que llevaba años funcionando.
Cuatro intentos para entregar un PDF. En un producto que ya tenía la descarga resuelta desde hacía años.
Esa es la parte que subestimas cuando decides “abrimos el producto a agentes”. No es exponer la API que ya tienes. Es descubrir que cada primitiva que dabas por resuelta —descargar un fichero, mostrar una imagen, paginar un listado— vuelve a estar sin resolver cuando al otro lado hay un modelo en vez de un navegador.
La parte que factura
Hasta aquí lo técnico. Ahora lo que justifica el proyecto.
Multi-tenant desde el primer día. Un solo servidor sirve a varias organizaciones aisladas, cada una por su dominio. No hay una instancia por cliente que mantener, actualizar y vigilar. El coste marginal de sumar una organización tiende a cero.
El acceso agéntico es un módulo activable por organización. No es una feature que se enciende para todos cuando la despliegas: es una capacidad que se activa por cliente. Eso no es una decisión de arquitectura, es una línea de precio. Un cliente que quiere que su equipo opere el ERP desde Claude contrata el módulo. El que no, no paga por él y no lo tiene expuesto.
El trabajo de abrir tu producto a los agentes solo se sostiene si acaba en una línea de facturación o en una ventaja competitiva clara. Si es un experimento de innovación sin dueño, morirá en cuanto haya que mantenerlo.
Un apunte que ayuda al caso de negocio: el servidor MCP no guarda ningún secreto. No tiene bucket, ni cuenta de servicio, ni credenciales propias. Todo lo delega. Eso reduce muchísimo lo que hay que auditar cuando un cliente te manda su cuestionario de seguridad.
Lo que aparece al abrir la superficie
Abrir una superficie nueva sobre un sistema maduro es la mejor auditoría que le vas a hacer.
En el proceso apareció un endpoint que no existía y cuyo 404 se estaba tragando en silencio. Llevaba tiempo fallando sin que nadie lo notara, porque el error se perdía por el camino.
Nadie lo habría encontrado revisando el código. Apareció porque un agente pidió el documento y no llegó nada.
Las superficies que nadie te cuenta
El historial de la conversación es una superficie de almacenamiento nueva. Cuando el binario viajaba dentro del protocolo, moría ahí. Un enlace, en cambio, queda escrito en la conversación —y esa conversación la guarda el proveedor del cliente, no tú. Si el nombre del documento aparece en el texto, esa línea revela la serie y el número de la factura. Se puede aceptar, pero tiene que ser una decisión consciente, no un descuido.
El nombre del fichero lo escribe el usuario del ERP. Entra en un bloque de texto que el modelo lee. Eso es un vector de inyección de prompt por la puerta de atrás: quien sube un adjunto controla parte de lo que va a leer el agente. Normalizarlo a [A-Za-z0-9._-] antes de usarlo es obligatorio, no una buena práctica.
Tu traza de auditoría se convierte en un almacén de datos sensibles. Si registras las llamadas a herramientas con sus argumentos, y una de esas herramientas acepta un fichero adjunto, acabas guardando el fichero. Un documento escaneado no pinta nada en una tabla de auditoría con noventa días de retención y sin control de acceso propio. Hay que redactar los campos binarios antes de escribirlos, y acotar el tamaño de lo que se guarda.
Firmar enlaces es una capacidad peligrosa. Si cualquiera con un token pudiera convertir cada documento que ve en una URL reenviable, la exfiltración masiva saldría baratísima. Por eso, mientras existió, vivió acotada por permiso y por módulo, y no llegó a salir del entorno de integración.
Ninguno de estos cuatro problemas existía antes de abrir el producto a los agentes. Todos son consecuencia directa de haberlo hecho.
Cuándo tiene sentido y cuándo no
Sí, si:
- Tus usuarios ya piden datos que están en tu producto. Si el trabajo real es consultar, cruzar y redactar sobre tus datos, un agente con acceso te ahorra el viaje a la interfaz.
- Tu modelo de permisos es sólido. Si ya resuelves bien quién ve qué, el MCP hereda esa solidez gratis. Si no, la vas a multiplicar.
- Puedes cobrarlo o defenderlo comercialmente. Módulo, plan superior, diferenciación en un concurso. Algo.
No, si:
- Tu autenticación es una API key compartida. Arregla eso primero. Un MCP encima de una credencial de servicio es un incidente esperando fecha.
- Buscas un titular. El coste está en el mantenimiento, y el protocolo se mueve rápido, como acabamos de ver con la especificación de julio.
- Tu producto es de escritura crítica y no tienes confirmación. Que un agente consulte facturas es una cosa. Que las emita es otra muy distinta.
Nosotros empezamos por lectura y fuimos abriendo escritura por dominios, uno a uno.
Conclusión
Convertir tu SaaS en agéntico no es exponer tu API con otro protocolo encima. Es volver a responder preguntas de producto que creías cerradas.
Quién es el usuario cuando el que pregunta es un modelo. Qué significa “descargar un fichero” cuando al otro lado no hay navegador. Dónde acaba escrito lo que devuelves. Qué parte de esto se cobra.
La parte técnica es la fácil. La especificación está documentada, los SDK funcionan y en una tarde tienes un servidor hablando el protocolo. Lo difícil, y lo que decide si el proyecto sobrevive, es todo lo demás: el modelo de permisos que te abre o te cierra clientes, la fricción de instalación que marca tu techo de adopción, y las superficies nuevas que creas sin darte cuenta.
Nosotros construimos dos soluciones que funcionaban y las tiramos: una porque pedía una instalación que nadie iba a hacer, otra porque enseñaba lo que no debía.
Si vas a abrir tu producto a los agentes, cuenta con que te pasará algo parecido. Y presupuéstalo.
Si lo que quieres es usarlo, no construirlo
Todo lo anterior es el ERP desde dentro. Si has llegado hasta aquí porque lo que buscas es una herramienta así para tu empresa, y no montarla tú, escríbeme: a quien venga de este artículo le hago un 20 % de descuento durante el primer año.
Lo que cubre hoy: clientes, leads, facturación compatible con Verifactu, gastos, gestión de proyectos —que sustituye a Jira y Confluence—, fichajes y algún módulo más.
La idea de fondo no es tener otra herramienta, es tener una menos. Deja de pagar cinco suscripciones que no se hablan entre sí, y sobre todo deja de tener los datos partidos: el proyecto, las horas que le has metido, el gasto asociado y la factura que sale de ahí, conectados en el mismo sitio. Que es lo que hace que un agente pueda responder preguntas que ninguna de esas cinco herramientas respondería por separado.
P.D.: Si estás montando un MCP sobre un producto en producción y te has encontrado con alguna de estas piedras, cuéntamelo en Twitter como @lm_martinbar. Y si te interesa la disciplina de especificaciones que hay detrás de decisiones como estas, la conté en SDD con OpenSpec.
¿Te ha gustado este artículo?
Aprende a alinear tecnología con objetivos de negocio.