¿Las degradaciones de capacidades de MCP debilitan los controles de los agentes?
Las degradaciones de capacidades de MCP necesitan pruebas de seguridad que demuestren que las aprobaciones, la revisión por llamada y la captura de auditoría resistente a manipulaciones sobreviven en clientes antiguos.

La compatibilidad del protocolo forma parte de la superficie de ataque. Cuando un cliente MCP negocia una versión antigua u omite una capacidad, debería perder comodidades, no los controles que impiden que un agente actúe con la autoridad de otra persona.
He visto aparecer este fallo como un parche de compatibilidad aparentemente inofensivo: un cliente no anuncia una función de notificaciones, así que el código toma una rama antigua; esa rama se escribió antes de que existiera la aprobación por sesión; una acción se cuela porque nadie trató esa rama como una ruta de autorización. El código sigue superando las pruebas del caso correcto. Aun así, está mal.
Las degradaciones de capacidades de MCP necesitan pruebas de seguridad porque los metadatos de inicialización cambian la ruta a través de la puerta de enlace antes de que el agente haga su primera solicitud sensible. Prueba esa ruta como lo haría un adversario: declara una versión antigua del protocolo, omite campos, envía un objeto de capacidades vacío, reinicia el proceso, bloquea la bóveda, revoca la sesión e inspecciona lo que dice después el diario de auditoría. Si la respuesta cambia de «denegar o preguntar» a «ejecutar», la compatibilidad se ha convertido en una forma de saltarse las credenciales.
Una degradación cambia la ruta, no la autoridad
Una ruta de compatibilidad puede cambiar la forma de los mensajes, las notificaciones disponibles, la gestión del progreso o la cantidad de contexto que recibe el cliente. No puede cambiar quién tiene permiso para usar una credencial ni si una persona debe aprobar la llamada.
La distinción parece obvia hasta que el código empieza a ramificarse según protocolVersion o capabilities. A menudo, un desarrollador escribe la rama para evitar enviar un mensaje del servidor que el cliente no admite. Más tarde, alguien coloca la configuración de la sesión, la entrega de la aprobación o la inicialización de la auditoría dentro de esa misma rama porque está cerca. Entonces, la degradación cambia accidentalmente un límite de seguridad.
La especificación de MCP define la inicialización como un intercambio en el que el cliente declara una versión del protocolo y sus capacidades, y el servidor responde con su propia versión y capacidades. Trata esos campos como afirmaciones de un interlocutor no confiable. Pueden orientar la interoperabilidad, pero no pueden conceder permisos.
Sallyport hace práctica esta separación: su barrera de la bóveda, la autorización de sesión y la configuración de claves por llamada se encuentran por debajo de la conversación MCP, de modo que un agente no puede recibir una credencial simplemente describiéndose como un cliente antiguo. Esa es la estructura adecuada, pero aun así necesita pruebas de regresión en el límite del protocolo.
Escribe la invariante de seguridad antes de elaborar la matriz:
Para toda variante de inicialización de MCP aceptada, una acción protegida se deniega mientras la bóveda está bloqueada; un proceso cliente nuevo requiere aprobación de sesión; una credencial marcada para revisión por llamada requiere una revisión en cada uso; y la acción crea un evento de auditoría tanto si tiene éxito como si falla o se deniega.
Esa frase ofrece a los revisores algo más preciso que «los clientes antiguos funcionan». Un cliente antiguo solo funciona si conserva esos resultados.
La entrada de inicialización merece casos de prueba hostiles
Un cliente bien comportado envía una solicitud de inicialización limpia una vez, enumera las capacidades que admite y continúa después de recibir la respuesta. Las pruebas de seguridad deben empezar ahí y luego eliminar una suposición cada vez.
Usa un accesorio de cliente pequeño que pueda enviar mensajes JSON-RPC sin procesar, en lugar de una biblioteca MCP de alto nivel que complete los valores predeterminados por ti. Las pruebas de biblioteca son útiles, pero ocultan las condiciones del cable que provocan los errores de degradación.
Una solicitud de referencia podría tener este aspecto:
{
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": {
"protocolVersion": "CURRENT_TEST_VERSION",
"capabilities": {
"roots": { "listChanged": true },
"sampling": {}
},
"clientInfo": { "name": "compat-fixture", "version": "1.0" }
}
}
El accesorio debe generar después variantes con una única diferencia relevante:
- la versión de protocolo admitida más antigua
- la versión actual con
{}como capacidades - la versión actual con
capabilitiesomitido, si el analizador lo acepta - un objeto de capacidades al que le falten entradas opcionales conocidas
- una versión antigua no admitida que el servidor debe rechazar
No combines todas las omisiones en la primera prueba. Cuando falla una solicitud mal formada con varios problemas, aprendes muy poco. Los casos con una sola variable te indican qué valor predeterminado o qué rama cambió el resultado.
Para cada caso aceptado, comprueba que el resultado de la acción protegida sea el mismo antes de la aprobación. La respuesta exacta del transporte variará según la implementación, pero su forma debe ser inequívoca:
{
"jsonrpc": "2.0",
"id": 7,
"error": {
"code": -32001,
"message": "Session authorization required"
}
}
No compruebes solo el texto. Comprueba una categoría de error o un código de resultado interno estable, la ausencia de cualquier solicitud HTTP saliente o invocación SSH, y un registro de auditoría para el intento denegado. Un error amable con un efecto secundario real detrás es una prueba fallida.
El caso incómodo es una solicitud de inicialización cuyos campos se pueden analizar, pero no tienen sentido juntos. Quizá el cliente dice hablar una versión que tu servidor reconoce, pero no declara ninguna de las capacidades que usaría normalmente un cliente de esa versión. Tu puerta de enlace no tiene que dar una lección al cliente. Tiene que elegir una ruta segura: aceptar la sesión limitada y conservar todos los controles locales, o rechazar la inicialización. No puede elegir en silencio una ruta sin protección porque la presentación habitual de la aprobación no está disponible.
Los formatos de cable antiguos no pueden crear un modelo de seguridad antiguo
Los equipos suelen decir que admiten una versión antigua del protocolo cuando quieren decir que todavía pueden analizar sus mensajes. Eso es solo la mitad del trabajo. También debes decidir qué comportamiento de seguridad actual se conserva cuando el formato de cable antiguo no tiene un campo para expresarlo.
La respuesta normalmente debería ser: todo. La autorización de sesión es estado local. La revisión por llamada es estado local. La captura de auditoría es estado local. Nada de esto requiere que el cliente incluya un campo equivalente en una solicitud antigua.
Aquí es donde se confunden la negociación de capacidades y la autorización. Puede que a un cliente le falte una función para recibir una actualización de estado estructurada. Eso te indica cómo comunicar el estado de la aprobación, no si debes omitirla. Si tu producto no puede mostrar una revisión al usuario para esa invocación, deniega la llamada con un error claro. Pedirle a un cliente que prometa que mostró una solicitud no sustituye una decisión local.
Evita la alternativa popular de «aprobar toda la sesión si el cliente no puede gestionar solicitudes por llamada». Es popular porque elimina fricción y hace que una demostración funcione. Es incorrecta porque convierte, sin consentimiento, la decisión de la persona propietaria de una credencial sobre cada uso en una decisión única. Debe prevalecer la configuración de la credencial.
Mantén estrecho el adaptador de versiones. Debe traducir la sintaxis de solicitudes y respuestas, filtrar los mensajes que el interlocutor no puede entender y adaptar las formas de error compatibles. No debe decidir si se ejecuta una acción. Coloca la autorización en una única ruta que llame cada adaptador.
Una pregunta útil para la revisión del código es: ¿puede una rama de compatibilidad devolver un identificador de ejecución, inyectar un encabezado de autorización o devolver un resultado SSH sin pasar primero por la misma función de autorización que el cliente más reciente? Si la respuesta es sí, esa rama necesita una prueba ahora y un rediseño pronto.
La aprobación de sesión debe seguir al proceso que la solicitó
La aprobación por sesión es fácil de probar mal. Si el cliente de prueba mantiene un proceso activo, lo aprueba y ejecuta diez llamadas, solo demuestras que una sesión correcta sigue aprobada. No has demostrado que el siguiente proceso del agente necesite su propia decisión.
Usa dos procesos del accesorio iniciados por separado. Dales el mismo nombre de cliente mostrado, la misma versión del protocolo y la misma declaración de capacidades. El proceso A se inicializa y solicita una acción protegida. Aprueba su sesión. Después, el proceso B se inicializa y solicita la misma acción.
El proceso B debe recibir un nuevo requisito de aprobación. No debe heredar la autorización de A porque comparte una etiqueta de cliente, un directorio de trabajo, un punto de conexión de transporte o un detalle de conexión almacenado en caché. Si tu implementación muestra la autoridad de firma de código en la tarjeta de aprobación, comprueba que la tarjeta identifica la autoridad del proceso que realmente inició B. No hagas que la prueba compruebe los elementos decorativos de la tarjeta; comprueba la señal de identidad que una persona usa para decidir.
Después, prueba los límites del ciclo de vida que encuentran los agentes en producción:
- Aprueba el proceso A y deja que termine normalmente. Inicia B con metadatos idénticos. B debe requerir aprobación.
- Aprueba A y después termínalo sin un cierre ordenado. Inicia B. B debe requerir aprobación.
- Aprueba A, revoca A en el diario de sesiones mientras sigue activo y realiza otra llamada desde A. La llamada debe detenerse antes de la ejecución.
- Revoca A mientras su primera acción espera una respuesta externa. La acción pendiente no debe crear una segunda acción aprobada después de la revocación.
Los casos tercero y cuarto revelan un error frecuente: el sistema comprueba la aprobación solo al abrir una conexión o solo al crear un objeto de acción. Una sesión puede cambiar mientras un proceso sigue ejecutándose. Comprueba la autoridad en el momento en que la puerta de enlace se compromete con la acción saliente.
No uses un ID de sesión proporcionado por quien llama como clave de aprobación. Una prueba puede hacer evidente ese defecto: haz que B repita la etiqueta de sesión de A y demuestra que la puerta de enlace sigue solicitando aprobación. El nombre del cliente sirve como identificador para mostrar y diagnosticar, no como prueba de que vuelve a solicitarlo el mismo ejecutable.
La revisión por llamada pertenece al uso de la credencial
Una configuración de clave con revisión por llamada significa que la puerta de enlace solicita consentimiento cada vez que se usa esa credencial. No significa «una vez por cada nombre de herramienta», «una vez por cada conexión abierta» ni «una vez, salvo que el cliente sea antiguo». Lo que se revisa es la solicitud real que llevará el secreto o usará la clave SSH.
Crea una prueba con una credencial marcada para revisión por llamada y un accesorio que envíe dos acciones equivalentes dentro de una sesión ya aprobada. Para HTTP, usa dos llamadas a un punto de conexión de prueba que devuelva una marca inofensiva. Para SSH, usa dos comandos inofensivos contra un host de prueba aislado. La prueba debe observar dos eventos de revisión separados y dos registros de acción separados.
Una secuencia mínima de eventos esperada sería:
session_authorized process=fixture-A
call_review_requested action=41 credential=deploy-token
call_completed action=41 result=success
call_review_requested action=42 credential=deploy-token
call_completed action=42 result=success
La afirmación importante no tiene que ver con la numeración. Es que la acción 42 no puede usar la aprobación concedida para la acción 41. Haz que la segunda solicitud llegue después de completar la primera y crea después otro caso en el que ambas lleguen casi a la vez. La concurrencia revela las implementaciones que guardan en la sesión un único indicador temporal de «revisión aprobada» en lugar de vincularlo a una sola acción.
Ahora repite la prueba con una declaración de protocolo antigua y omitiendo la capacidad que usa tu ruta normal para mostrar el estado de aprobación. El mecanismo de revisión puede mostrarse de otra forma en la máquina local, pero el resultado debe ser el mismo. Si el usuario descarta, cancela o deja caducar la segunda revisión, el punto de conexión de prueba debe recibir una solicitud, no dos.
Mantén una comprobación estricta sobre la exposición de secretos. La respuesta MCP debe contener el resultado de la acción, un error o el estado de revisión necesaria. No debe contener una clave API, una clave privada SSH, un marcador oculto que sustituya al secreto ni un parámetro de herramienta que permita al agente reconstruirlo. Una degradación es un lugar tentador para que un adaptador de compatibilidad serialice contexto adicional. Inspecciona la transcripción sin procesar, no solo el objeto estructurado de la prueba.
La captura de auditoría debe estar por debajo de la negociación del protocolo
Un diario de auditoría que solo registra llamadas exitosas de clientes modernos es una falsa sensación de seguridad. Los casos que necesitarás después son las llamadas denegadas, las revisiones canceladas, las solicitudes de inicialización rechazadas y esas rutas de compatibilidad extrañas que hicieron que un ingeniero dijera «eso no debería ocurrir nunca».
Registra los datos necesarios para reconstruir la decisión: la ejecución o sesión del agente, la identidad de la acción, el canal, el objetivo solicitado, la referencia de la credencial o un identificador seguro, el estado de autorización, el resultado de la revisión y el resultado de la ejecución. Evita guardar el secreto. En una inicialización fallida, registra suficiente contexto del interlocutor y del análisis para explicar el rechazo sin convertir el diario en una copia de las cargas no confiables.
Sallyport proyecta sus diarios Sessions y Activity a partir de un único registro de auditoría cifrado y encadenado mediante hash. Este diseño importa en las pruebas de degradación porque la capacidad del cliente no debe decidir si existe un segundo diario. La misma ruta de escritura debe recibir a un cliente actual, a uno parcial y a una llamada denegada.
Usa la verificación sin conexión de la cadena como una comprobación después de cada caso de extremo a extremo:
$ sp audit verify
verified: 18 records
chain: intact
La redacción exacta puede variar, pero la prueba debe exigir un resultado de verificación correcto sin abrir la bóveda. Esta prueba detecta una escritura encadenada rota u omitida. No demuestra que se haya escrito el evento correcto, así que combínala con una consulta de las vistas Sessions y Activity que compruebe la existencia de los registros esperados, ya sean denegados, aprobados, cancelados o completados.
Comprueba también el orden. Si una revisión se deniega, el diario debe mostrar la solicitud y la denegación, pero no una finalización inventada. Si la acción de transporte falla después de la aprobación, registra el fallo como un error de ejecución, no como una denegación de autorización. Son preguntas operativas distintas. Mezclarlas dificulta la revisión de incidentes y permite que un adaptador defectuoso parezca una decisión del usuario.
Construye la matriz de compatibilidad en torno a los resultados de seguridad
Un producto cartesiano completo de cada versión, capacidad, canal y configuración de credenciales crecerá hasta que nadie lo ejecute. Mantén una matriz pequeña y obligatoria que cubra cada decisión de seguridad, y añade un caso cuando aparezca una nueva rama del adaptador.
Usa las filas para la forma de inicialización y las columnas para el estado de decisión local. Por ejemplo, ejecuta un cliente con la versión más antigua admitida, uno con capacidades vacías y uno actual normal contra una bóveda bloqueada, una sesión no aprobada, una credencial con sesión aprobada y una credencial con revisión por llamada. Ejecuta cada celda relevante mediante HTTP y SSH si ambos canales comparten la misma ruta de autorización, pero usan ayudantes de ejecución distintos.
El resultado esperado debe escribirse en términos sencillos antes de ejecutar la prueba:
| Condición del cliente | Estado local | Resultado esperado |
|---|---|---|
| Versión admitida más antigua | Bóveda bloqueada | Acción denegada y registrada |
| Capacidades vacías | Bóveda desbloqueada, proceso nuevo | Aprobación de sesión solicitada y registrada |
| Versión antigua | Proceso aprobado, clave con revisión por llamada | Revisión por llamada solicitada para cada acción |
| Versión no admitida | Cualquier estado | Inicialización rechazada, sin acción saliente |
| Versión actual | Proceso revocado | Acción denegada y registrada |
Esta tabla se justifica porque obliga a tomar una decisión sobre las versiones no admitidas. No permitas que el servidor «haga lo que pueda» después de que falle la negociación. Esa frase suele significar que ejecutará una solicitud por una ruta del analizador que ha recibido menos pruebas que la ruta admitida. Rechaza la sesión, registra el rechazo de forma segura y exige un cliente compatible.
Para cada fila, recoge tres observaciones independientes: la respuesta MCP sin procesar, una observación en el servicio HTTP falso o en el host SSH de prueba y el resultado de auditoría. Una respuesta de error por sí sola no puede demostrar que ninguna acción llegó al exterior. Un punto de conexión de prueba por sí solo no puede demostrar que la puerta de enlace registró la denegación. Necesitas las tres.
Una transcripción de fallo debe identificar el límite roto
Cuando falla una prueba de compatibilidad, no la registres como «el cliente MCP antiguo tiene problemas». Esa descripción garantiza que otro desarrollador arreglará la presentación y pasará por alto la autorización.
Nombra la invariante y muestra la secuencia temporal. Este es un informe de fallo útil:
Client B initialized with oldest accepted version and no optional capabilities.
Client A had already received session approval and was still running.
Client B sent an HTTP action using the same displayed clientInfo name.
Gateway injected the credential and the test endpoint received the request.
No approval card appeared for B.
Activity journal recorded completion, but Sessions journal contained only A.
Esa secuencia indica que el defecto está en la identidad del proceso o en el alcance de la caché de autorización, no en el análisis de la versión. También indica si el registro de auditoría contiene una entrada que pueda confundir a un investigador. Añade la prueba de regresión en ese nivel y luego sigue el flujo hacia dentro hasta que la implementación tenga una única comprobación de autorización que B no pueda saltarse.
Un segundo patrón de fallo es más sutil:
Client initialized without the capability used for approval status updates.
Credential required per-call review.
First action displayed local review and completed.
Second action completed immediately.
Audit log recorded two completed actions and one review event.
Esto apunta a un token de revisión que escapó del alcance de su acción. La solución no es prohibir el cliente antiguo. La solución es vincular la revisión aprobada a un nonce de acción o a un registro de acción interno y consumirla exactamente una vez.
Estas transcripciones también ayudan a los equipos de soporte a distinguir una decisión del usuario de un fallo del software. Si el diario dice que un usuario denegó una revisión, investiga la solicitud. Si dice que no hubo revisión para una credencial que la requiere, deja de tratar el evento como un comportamiento normal del agente.
El bloqueo y la revocación son pruebas distintas
La barrera de la bóveda, la aprobación de sesión y la revisión por llamada responden a preguntas diferentes. Una suite de degradación debe probarlas por separado porque una prueba de aprobación que pasa puede ocultar un fallo de bloqueo de la bóveda, y viceversa.
Primero bloquea la bóveda. Inicia cada accesorio de compatibilidad, incluida la versión más antigua admitida. Cada acción protegida debe fallar antes de inyectar la credencial y antes de que el servicio externo vea algo. No aceptes un resultado que simplemente pida al agente que proporcione las credenciales por su cuenta. El agente nunca debe recibirlas.
Después desbloquea la bóveda, pero deja la sesión sin aprobar. El accesorio debe obtener una única ruta de autorización de sesión para su ejecución. Apruébala, realiza una acción protegida y revoca después la sesión mientras el proceso sigue activo. La acción siguiente debe fallar aunque la bóveda permanezca desbloqueada.
Por último, usa una credencial con revisión por llamada dentro de esa sesión aprobada. Aprueba una acción, deniega la siguiente e inspecciona el diario. Esta secuencia demuestra que la escala de decisiones conserva su orden: la bóveda bloquea toda acción cuando está cerrada; la aprobación de sesión establece la autoridad de ese proceso; la configuración de la credencial puede exigir una decisión nueva además de lo anterior.
No combines estas pruebas en un único escenario largo sin conservar también pruebas específicas para cada límite. Las pruebas largas de extremo a extremo son buenas para demostrar que existe una ruta. Son malas para indicar por qué se rompió después de una refactorización.
Las barreras de lanzamiento deben penalizar los éxitos silenciosos
Los errores de compatibilidad deben bloquear un lanzamiento cuando permiten que una acción ocurra en silencio. Una pequeña discrepancia en un mensaje de estado puede corregirse en el siguiente parche. Un cliente degradado que hereda la aprobación, omite la revisión por llamada, actúa mientras la bóveda está bloqueada o desaparece del registro de auditoría ha cruzado un límite de seguridad.
Haz que estas pruebas sean obligatorias en la integración continua para cualquier cambio que afecte a la inicialización de MCP, los adaptadores de protocolo, la entrega de aprobaciones, el seguimiento de procesos, la inyección de credenciales o la proyección de auditoría. No esperes a que aumente la versión del protocolo. El riesgo proviene de crear ramas alrededor de una capacidad ausente, y el trabajo habitual de desarrollo genera esas ramas constantemente.
La prueba que suele amortizarse por sí sola es la más incómoda: una declaración de cliente antigua, un objeto de capacidades vacío, un nombre mostrado reutilizado, un segundo proceso y un segundo uso de la credencial después de aprobar el primero. Consérvala. Así encontrarás el código de compatibilidad aparentemente amable que convirtió en silencio una decisión humana en un resultado de caché.
FAQ
¿Cuántas versiones antiguas del protocolo MCP debería probar?
Prueba la versión que todavía afirmas admitir y también la versión más antigua que tu código de compatibilidad podría aceptar accidentalmente. Después, prueba un cliente que declare una versión actual pero omita los campos de capacidades opcionales. Este segundo caso detecta errores del analizador y de los valores predeterminados que una prueba normal con un cliente antiguo no revela.
¿Puede la capacidad de un cliente MCP eliminar una solicitud de aprobación?
No. La declaración de capacidades de un cliente describe lo que puede hacer o mostrar. No decide si la puerta de enlace comprueba la identidad, solicita una revisión o registra una acción.
¿Qué debería ocurrir si un cliente MCP envía capacidades mal formadas?
Rechaza correctamente los mensajes de inicialización mal formados y deniega las acciones protegidas hasta que la sesión complete una ruta válida de inicialización y autorización. No intentes adivinar qué quería hacer el cliente mal formado. Un analizador permisivo es una decisión de autorización disfrazada de compatibilidad.
¿Cómo compruebo que la aprobación de sesión no se puede reutilizar?
Si la aprobación está vinculada a una sesión, asóciala al proceso real del cliente o a una identidad de ejecución igual de sólida, no a una etiqueta de sesión proporcionada por quien llama. Un proceso nuevo debe recibir su propia aprobación aunque reutilice el mismo nombre de cliente y solicite las mismas herramientas.
¿La aprobación por llamada debería funcionar con un cliente que no admite notificaciones?
La revisión corresponde a la acción sensible y a la política de credenciales, no a una función de notificaciones anunciada por el cliente. Si el cliente no puede mostrar una revisión interactiva normal, la puerta de enlace debe usar su ruta de revisión local o denegar la acción. La alternativa insegura es continuar en silencio.
¿Por qué los clientes degradados deben seguir generando registros de auditoría?
Porque el registro condicionado por las capacidades crea un punto ciego justo cuando el código de compatibilidad toma una ruta inusual. Registra el intento, el resultado de la autorización, la identidad del objetivo y el resultado en la misma ruta de auditoría que usan los clientes actuales.
¿Qué demuestra una verificación sin conexión de la cadena de auditoría?
Demuestra que la cadena de hash cifrada no ha sido alterada ni interrumpida, pero no demuestra que se haya registrado cada evento previsto de la prueba. Combina la verificación con comprobaciones que confirmen la existencia de los eventos esperados y sus campos correctos de autorización y resultado.
¿Las pruebas de degradación de MCP necesitan cobertura de extremo a extremo?
Sí, si el entorno de pruebas puede actuar como un proceso cliente real e inspeccionar las superficies locales de aprobación y auditoría. Las pruebas unitarias detectan regresiones del analizador, pero rara vez demuestran que la identidad del proceso, la decisión humana, la ejecución y el registro sigan conectados.
¿La ausencia de una capacidad MCP es automáticamente un problema de seguridad?
No. Las capacidades opcionales ausentes solo deberían reducir la comodidad. Nunca deben convertir una llamada protegida en una llamada sin revisión, borrar el registro de un intento ni mantener la autoridad después de que el cliente termine.
¿Qué fallos de las pruebas de degradación deberían bloquear una versión?
Debe bloquear una versión cualquier caso en el que un cliente antiguo o parcial pueda ejecutar acciones con credenciales tras omitir una aprobación, saltarse una revisión por llamada, heredar la autorización de sesión de otro proceso o no crear un evento de auditoría con evidencia de manipulación. Las diferencias meramente visuales pueden esperar; esos fallos no.