MCPConnect: Cómo asegurar servidores MCP


Asegurar un servidor del Protocolo de Contexto de Modelo (MCP, por sus siglas en inglés) no es una tarea única; hay varios problemas distintos que resolver. Lo más probable es que tu servidor se comunique con algo que requiere un secreto (como la contraseña de una base de datos o una clave de API externa), por lo que ese secreto debe almacenarse de forma segura. Si se puede acceder a él a través de HTTP, también necesitas establecer quién está al otro lado de la conexión y, una vez que lo sepas, decidir qué se le permite hacer realmente a ese llamador. No todos los servidores se enfrentan a todos los problemas: cuáles aplican en tu caso depende principalmente del transporte y de quién utilice el servidor.
Este artículo detalla cómo almacenar credenciales de forma segura, autenticar al llamador y autorizar aquello a lo que puede acceder, mostrando cómo se maneja cada aspecto con MCPConnect. Su enfoque es práctico: elige el mecanismo que se adapte a tu caso y luego configúralo con unas pocas líneas de código.
Elegir el mejor enfoque para la seguridad en MCP
La primera decisión está dictada por el transporte. Los servidores STDIO se ejecutan como un subproceso de un cliente de confianza en una máquina controlada, mientras que los servidores HTTP pueden ser alcanzados por cualquier cosa que pueda abrir un socket.
Para un servidor HTTP, la siguiente pregunta es cómo los llamadores prueban quiénes son. Si se les puede entregar una clave (un usuario, un equipo de confianza, un puñado de clientes o otro programa que llame a tu servidor), las claves de API hacen el trabajo: un único secreto compartido, o claves que tú mismo validas y asocias a un llamador. Si los usuarios deben iniciar sesión con su propia cuenta, o deseas utilizar un proveedor de identidad que ya posees, utiliza OAuth 2.1.
CORS es una opción independiente. No es una forma de autenticar al llamador: solo lo necesitas cuando los clientes basados en navegador se conectan a tu servidor, independientemente de la autenticación que hayas elegido.

El resto del artículo profundiza en cada una de estas opciones.
Cómo almacenar credenciales para servidores MCP
Para los servidores STDIO, el problema suele ser menor porque el entorno está más controlado. La mayoría de los clientes MCP te permiten pasar variables de entorno o argumentos de línea de comandos al servidor cuando este se inicia. Se prefieren las variables de entorno. Los argumentos pasados a un ejecutable son visibles para otros usuarios a través de herramientas como el Administrador de Tareas de Windows, por lo que un token colocado en la línea de comandos es efectivamente público.
Cuando las variables de entorno no son una opción, utiliza un archivo de configuración convencional (INI, JSON, etc.) y encripta cualquier valor sensible. Una buena referencia es FireLinkMCP, un servidor MCP en Delphi que expone bases de datos locales. Puede sellar contraseñas con la API de Protección de Datos de Windows (DPAPI) o leerlas desde una variable de entorno, y nunca incrusta una clave en el ejecutable ni expone el secreto al cliente MCP.
DPAPI es ideal cuando el servidor se ejecuta bajo una cuenta fija de Windows en una sola máquina: encripta el valor para que solo esa cuenta en esa máquina pueda desencriptarlo, y no tienes que administrar una clave por ti mismo. No es la herramienta adecuada para secretos que deben compartirse entre máquinas o implementaciones; en esos casos, utiliza una variable de entorno o un almacén de secretos.
Ese mismo patrón —mantener los secretos fuera del ejecutable y de la línea de comandos— se aplica a cualquier servidor MCP que escribas.
Configuración de la autenticación por Clave de API (API Key) a través de HTTP para MCP
Para un transporte HTTP, la forma más sencilla de restringir el acceso es requerir una clave de API válida en cada solicitud. MCPConnect ofrece esto a través del complemento IAuthTokenConfig.
ℹ️ Nota: Las características descritas en esta sección —validadores de claves personalizados, identidad del llamador, el desafío 401 y la exención de STDIO— están disponibles en la última versión de MCPConnect en GitHub.
Agrega la unidad a tu cláusula uses:

Luego configura el complemento en tu instancia de TJRPCServer:

Una vez que se establece un token, el servidor verifica cada solicitud entrante y rechaza aquellas que no porten el valor esperado con un código 401 Unauthorized y una cabecera WWW-Authenticate. El token se compara distinguiendo mayúsculas de minúsculas y en tiempo constante, por lo que el tiempo de respuesta no revela qué parte de una clave adivinada era correcta. Los intentos fallidos se registran, pero nunca se incluye la clave en el registro.
La verificación se aplica únicamente a HTTP. El transporte STDIO no tiene cabeceras para llevar una clave, y un servidor generado por su cliente ya se ejecuta con la autoridad de ese cliente, por lo que en ese caso se omite.
¿Dónde busca una conexión MCP un token de autorización?
Por defecto, MCPConnect lee el token de la cabecera estándar Authorization utilizando el esquema Bearer:
HTTPAuthorization: Bearer my-secret-token
La enumeración TAuthTokenLocation te permite cambiar esto. Puedes leer el token desde una cabecera personalizada o desde una cookie, lo cual es útil al integrarse con clientes existentes o con infraestructura de sesiones.

Con las ubicaciones Header y Cookie, se requiere especificar un nombre: si falta SetTokenCustomHeader, ApplyConfig genera una excepción al iniciarse en lugar de dejar que cada solicitud falle silenciosamente.
Cómo validar claves de autorización MCP en tu propio código
Una única clave integrada directamente (hard-coded) en la configuración es útil para una configuración rápida, pero las implementaciones reales suelen requerir más: una clave por cliente, claves almacenadas en una base de datos, o claves que puedan ser revocadas o expirar. Para lograr esto, escribe tu propio validador utilizando SetTokenValidator:

El validador recibe el contexto de la solicitud, la clave enviada por el cliente (nunca vacía) y la identidad del llamador para que sea completada. Las reglas son las siguientes:
- Retorna True para aceptar la clave.
- Cuando se registra un validador, el token establecido con SetToken se ignora.
- Se llama de forma concurrente desde cada hilo de solicitud, por lo que todo lo que utilice debe ser seguro para hilos (thread-safe).
- Cualquier excepción generada en su interior se registra y se trata como una clave rechazada: el cliente recibe un 401, nunca un 500, y jamás el mensaje de error.
Si no puedes pasar un método anónimo —por ejemplo, desde C++Builder— registra una clase que implemente IAuthTokenValidator usando SetTokenValidatorClass en su lugar. Se crea una instancia por solicitud, por lo que la clase necesita un constructor sin parámetros y debe tener conteo de referencias (heredar de TInterfacedObject es la forma habitual).
Cómo averiguar quién o qué está llamando a tu servidor MCP
El parámetro AIdentity es el mismo objeto TMCPAccessToken que OAuth rellena con las claims (reivindicaciones) de un JWT (consulta Cómo leer un token dentro de una herramienta). Se crea vacío para cada solicitud y se inyecta en el contexto de la misma, de modo que todo lo que el validador escriba en él —a través de los establecedores (setters) Subject, Name, EMail, Scope, ClientId, o cualquier claim personalizada agregada a Payload— es lo que tus herramientas leerán mediante [Context].
Esto convierte a las claves de API en algo más que una simple puerta de entrada. Un secreto compartido simple responde a la pregunta "¿tiene este llamador permiso para entrar?"; un validador también responde "¿quién es este llamador?", y los scopes que asignas a la clave deciden qué puede hacer el llamador (consulta Cómo autorizar herramientas, recursos y prompts). Lo que las claves de API no pueden proporcionarte es un inicio de sesión (login): los usuarios nunca se autentican con su propia cuenta, a menos que se entregue una clave por usuario, la cual los identifica pero sigue sin ser un inicio de sesión. Cuando necesites un inicio de sesión real, utiliza OAuth.
Cómo lidiar con CORS en conexiones MCP
Los hosts MCP basados en navegador (por ejemplo, MCPJam Inspector o clientes web) envían solicitudes desde una página servida en un origen diferente. El navegador aplica la política de mismo origen (Same-Origin Policy) y bloquea las respuestas a menos que el servidor acepte cabeceras de CORS (Cross-Origin Resource Sharing). En resumen, CORS es el mecanismo mediante el cual un servidor le indica al navegador qué orígenes, métodos y cabeceras está dispuesto a aceptar.
CORS se configura en la sección .Security de IMCPConfig:

Configuraciones clave:
- MétodoDescripciónSetCORS(True): Habilita las cabeceras CORS en cada respuesta, incluidos errores y endpoints conocidos.
- SetAllowedMethods(methods): Métodos HTTP permitidos. POST es suficiente para JSON-RPC; agrega GET para permitir que los clientes de navegador abran el flujo SSE. Las solicitudes de sondeo previo (OPTIONS preflights) siempre se responden.
- SetAllowedOrigins(origins): Lista blanca de orígenes aceptados. Soporta coincidencias exactas y comodines en subdominios como https://*.example.com.
- SetCookieSecure(False): Desactiva el indicador Secure en las cookies de sesión. Solo para desarrollo con HTTP plano; por defecto, las cookies son HttpOnly + SameSite=Strict + Secure.
Ten cuidado con la lista blanca de orígenes. Si nunca se llama a SetAllowedOrigins, se acepta cualquier origen, incluidas las solicitudes que no llevan ninguna cabecera Origin. Tan pronto como configures una lista blanca, las solicitudes con un origen faltante o que no coincida serán rechazadas, lo cual puede romper herramientas de línea de comandos como curl o Bruno que no envían una cabecera Origin. Para el desarrollo, puedes omitir la llamada o protegerla con una directiva condicional:

Cómo usar OAuth 2.1 con MCP
Cuando necesitas identificar al usuario real detrás de una solicitud y tomar decisiones de autorización basadas en esa identidad, OAuth 2.1 es el enfoque recomendado. Es el marco de autorización adoptado por la especificación MCP: los clientes obtienen acceso limitado a los recursos del servidor en nombre de un usuario, sin compartir directamente las credenciales de este. Para una introducción más amplia sobre cómo funciona OAuth en MCP, consulta la guía de OAuth de MCPJam.
MCPConnect actúa como un servidor de recursos OAuth 2.1. No implementa un servidor de autorización; delega la autenticación a un proveedor externo. Si aún no tienes uno, puedes utilizar un servicio comercial como Microsoft Entra ID, o una opción gratuita y autohospedada como Keycloak. Auth0, Okta y cualquier proveedor de OpenID Connect también funcionan.
Actualmente, MCPConnect solo soporta el modo de prerregistro (credenciales de cliente): el cliente MCP se registra en el servidor de autorización de antemano y utiliza sus propias credenciales para obtener un token de acceso.

Cómo configurar tu conexión MCP para soportar OAuth
OAuth se configura a través del complemento IOAuthConfig:

Si no se configura ningún servidor de autorización, la aplicación de OAuth se deshabilita por completo y se permite el paso de todas las solicitudes.
En la práctica, estos valores se leen desde variables de entorno, como en la demo MCPServerOAuth:

Opciones notables:
- SetResource(url): URL pública canónica de este servidor MCP. Llámala primero; los demás métodos derivan URLs a partir de ella.
- SetRealm(realm): Ámbito (realm) enviado en la cabecera WWW-Authenticate. Por defecto es 'mcp'.
- AddAuthorizationServer(url): Registra un servidor de autorización externo. Se puede llamar múltiples veces.
- AddTrustedIssuer(url): Agrega un emisor de tokens de confianza. Útil cuando el emisor (iss) del token difiere de la URL de descubrimiento (por ejemplo, en tokens v1.0 de Entra ID).
- SetTokenValidatorClass(class): Registra la clase que valida los tokens portadores (bearer tokens). Sin ella, se rechaza cada token portador.
- SetAudience(audience): Valor que debe contener la reclamación aud del token. Por defecto es SetResource.
- AddRequiredScope(scope): Scope que el token debe poseer obligatoriamente; de lo contrario, la solicitud falla con insufficient_scope.
- SetClockSkew(seconds): Tolerancia en las reclamaciones exp/nbf. Por defecto es 60 segundos.
- SetKeyCacheTTL(seconds): Tiempo de vida del JWKS en caché. Por defecto es 3600 segundos.
- EnableMetadataProxy(issuer): Hace un proxy del documento de descubrimiento del servidor de autorización, parcheando los campos faltantes (consulta El proxy de metadatos).
Validores de tokens (tokens JWT y JOSE)
MCPConnect incluye tres validadores con diferentes niveles de rigor:
- TJoseTokenValidatorTodo, incluida la firma criptográfica verificada contra las claves JWKS publicadas por el proveedor. Producción
- TClaimsTokenValidatoriss, aud, exp/nbf, scopes requeridos, rechaza "alg": "none", verifica que el kid exista en el JWKS, pero no verifica la firma. Pruebas con un proveedor real
- TDecodeOnlyTokenValidator, Decodifica la carga útil (payload) y no verifica nada. Solo desarrollo local
TJoseTokenValidator requiere la biblioteca JOSE-JWT en tiempo de compilación (controlada por la directiva de compilación DELPHI_JOSE_JWT en MCPConnect.inc) y las bibliotecas OpenSSL en tiempo de ejecución.
Cómo leer un token de acceso MCP dentro de una herramienta
Una vez que una solicitud ha sido autenticada, las reclamaciones validadas están disponibles a través de la inyección mediante [Context] como un objeto TMCPAccessToken. Esto significa que una herramienta puede comportarse de manera diferente según quién sea el llamador, sin necesidad de código adicional para pasar la identidad:

TMCPAccessToken expone las reclamaciones estándar (Subject, Name, EMail, Scope, Issuer, Audience, Expiration, entre otras) además de la carga útil RAW del JWT a través de la propiedad Payload, permitiendo el acceso a cualquier reclamación que no se exponga directamente. Para probar los scopes, utiliza HasScope('orders:read') en lugar de buscar dentro de la cadena Scope: realiza coincidencias únicamente con scopes completos y distinguiendo mayúsculas de minúsculas, por lo que orders no satisface a orders:read.
Este mismo objeto es completado por un validador de clave de API (consulta Cómo averiguar quién o qué está llamando a tu servidor MCP), por lo que una herramienta escrita de esta manera funciona sin cambios sin importar qué autenticación utilice el servidor.
Cómo probar y depurar OAuth
Probar OAuth suele ser más complicado de lo que parece. El token debe provenir de un proveedor real, el documento de descubrimiento debe coincidir con lo que espera el servidor, y un fallo en cualquier paso —descubrimiento, redirección, intercambio de tokens o validación— puede parecer idéntico desde el lado del cliente. Herramientas como MCPJam, con su depurador de OAuth, ayudan en este aspecto: te permiten inspeccionar cada etapa del flujo de autenticación para ver exactamente dónde se rompe en lugar de adivinar.
El proxy de metadatos
Algunos proveedores soportan características que MCP requiere pero no las publicitan en sus metadatos de descubrimiento. Un ejemplo común es Microsoft Entra ID: soporta PKCE, el cual MCP exige para el flujo de código de autorización, pero sus metadatos no siempre listan code_challenge_methods_supported, por lo que un cliente estricto se niega a iniciar el flujo. El proxy de metadatos de MCPConnect (EnableMetadataProxy(issuer)) refleja el documento de descubrimiento del servidor de autorización a través del servidor MCP, parcheando los campos faltantes sobre la marcha. Recurre a él cuando un proveedor funcione en la práctica pero falle en el descubrimiento, no por defecto.
Cómo configurar HTTPS durante el desarrollo
Los proveedores de OAuth normalmente requieren que el canal de retorno hacia el servidor MCP sea seguro (HTTPS), y configurar un certificado local válido suele ser engorroso. Algunos van más allá y simplemente se niegan a trabajar con una redirección a localhost, por lo que no hay nada que configurar. Un servicio de túnel como Cloudflare Tunnel o Microsoft Dev Tunnels te proporciona un punto de enlace HTTPS público que se reenvía a tu servidor local, de modo que el proveedor puede redirigir a una URL en la que confía. Eso es suficiente para poner en práctica todo el flujo durante el desarrollo sin necesidad de desplegar nada en producción.
Cómo autorizar herramientas, recursos y prompts para MCP
La autenticación te dice quién es el llamador; la autorización decide qué puede ver y usar ese llamador. Verificar los scopes de forma manual dentro de cada herramienta funciona, pero es fácil olvidar uno, y la herramienta seguirá apareciendo en la lista que el cliente le entrega al modelo. MCPConnect te permite declarar el requisito una sola vez, junto al código que protege.
ℹ️ Nota: La autorización basada en scopes descrita en esta sección (McpRequiredScope, RequireScope y los autorizadores personalizados) está disponible únicamente en la última versión de MCPConnect en GitHub.
Declaración de los scopes requeridos
El atributo McpRequiredScope lista los scopes que el token del llamador debe poseer en su totalidad, separados por comas o punto y coma. Funciona en herramientas, recursos, plantillas de recursos, interfaces de usuario de aplicaciones (App UIs) y prompts:

Aplicado a una clase, el atributo afecta a todo lo que dicha clase registre; aplicado a un método, se suma a los requisitos de la clase. Aquí, list_orders requiere orders:read, mientras que delete_order necesita orders:read, orders:write y orders:admin. Los elementos que no tienen el atributo permanecen abiertos para cualquier llamador, exactamente igual que antes.
Los scopes provienen del token con el que se autenticó la solicitud: la reclamación de scope del JWT con OAuth, o lo que tu validador haya escrito en AIdentity.Scope en el caso de las claves de API. El mismo atributo funciona con ambos métodos.
Lo que ve el cliente MCP
La verificación se ejecuta en cada método MCP que expone un elemento:

- Un elemento denegado parece uno ausente. Un cliente que no tiene permiso para llamar a delete_order recibe exactamente el mismo error que obtendría si la herramienta no existiera, por lo que el servidor no revela lo que oculta. La razón real (los scopes requeridos y los que portaba el token) se registra en el archivo de registro (log) del servidor.
- La verificación es de tipo cerrado ante fallos (fail-closed). Un llamador sin token, como un cliente STDIO o cualquier cliente de un servidor sin autenticación configurada, no puede ver ni usar ningún elemento que requiera un scope.
Registro sin atributos
Las clases registradas mediante código —por ejemplo, desde C++Builder, que no soporta atributos de Delphi— obtienen la misma protección utilizando RequireScope, ya sea en el constructor de la herramienta o, tras el registro, en la sección que contiene el elemento:

RequireScope siempre se suma a los scopes ya declarados, y los atributos encontrados en una clase se respetan incluso cuando se registra de forma programática, por lo que registrar una clase manualmente nunca elimina sus restricciones.
Cómo crear reglas personalizadas para un servidor MCP
Los scopes son la forma recomendada, pero no la única. Con SetAuthorizer puedes reemplazar la verificación predeterminada con tu propia regla. Se llama para cada elemento de una lista y para el elemento específico que solicita una llamada, y debe retornar True para permitir el acceso. Recibe el contexto de la solicitud, una descripción del elemento (su tipo, nombre, URI, scopes declarados y etiquetas) y la identidad del llamador, la cual nunca es nula.
Una regla personalizada reemplaza a la predeterminada, por lo que si deseas conservar la verificación de scopes y agregarle lógica adicional, debes llamar primero a TMCPScopeAuthorizer.Check. Las etiquetas (tags) facilitan marcar elementos para tus propias reglas sin necesidad de nuevos atributos:

Se aplican las reglas habituales: la función se llama de forma concurrente desde cada hilo de solicitud y debe ser segura para hilos, y cualquier excepción generada en su interior se registra y cuenta como una denegación. Desde C++Builder, registra una clase que implemente IMCPAuthorizer usando SetAuthorizerClass; se crea una instancia por solicitud, no por elemento.
Resumen de la creación y prueba de un servidor MCP con autorización
La configuración de seguridad adecuada para un servidor MCP se desprende de su transporte y de quién lo utiliza:
- STDIO: mantén las credenciales fuera de la línea de comandos; prefiere variables de entorno o archivos de configuración sellados con DPAPI.
- HTTP, los llamadores pueden recibir una clave: exige una clave de API para que el servidor no quede abierto a cualquiera que pueda alcanzarlo. Utiliza un secreto compartido único para el caso más simple, o un validador para comprobar las claves contra tu propio almacén y distinguir a los llamadores.
- HTTP, los usuarios inician sesión con su propia cuenta: utiliza OAuth 2.1 como servidor de recursos respaldado por un proveedor de identidad externo, y valida los tokens con TJoseTokenValidator.
- En ambos casos: la identidad del llamador llega a tus herramientas como un TMCPAccessToken a través de [Context].
- Decidir a qué puede acceder cada llamador: declara los scopes requeridos con McpRequiredScope (o RequireScope), y cambia a un autorizador personalizado solo cuando los scopes no sean suficientes.
- Clientes basados en navegador: habilita CORS, sin importar qué autenticación utilices.
Cada mecanismo se configura a través del mismo patrón de complementos fluidos mostrado anteriormente, lo que te permite comenzar de manera sencilla y agregar una autenticación más sólida a medida que tu implementación evoluciona.
Lecturas adicionales
- Autenticación y CORS en MCPConnect: https://mcpconnect.delphiblocks.dev/authentication.html
- OAuth 2.1 en MCPConnect: https://mcpconnect.delphiblocks.dev/oauth.html
- FireLinkMCP (manejo de credenciales en la práctica): https://github.com/delphi-blocks/FireLinkMCP
Esta es una publicación de blog invitada escrita por los MVP Paolo Rossi y Luca Minuti, creadores de MCP Connect.





Comentarios
Publicar un comentario