# Prueba herramientas MCP con credenciales falsas que fallan de forma segura

Probar herramientas MCP de forma segura requiere algo más que sustituir un token de producción por una cadena llamada `TEST_TOKEN`. Una herramienta puede interpretar el valor falso, devolver un mensaje de éxito cordial y fallar aun así la primera vez que un agente se encuentre con un límite real de permisos, una denegación de aprobación, un proveedor lento o una escritura completada a medias.

El entorno de pruebas debe permitirte demostrar dos cosas a la vez: que la herramienta envía la solicitud prevista y que no puede causar consecuencias reales en producción si la prueba sale mal. Las cuentas desechables y las credenciales falsas resuelven partes distintas de ese trabajo. Trátalas como controles separados.

## Las credenciales falsas prueban el análisis, no la autoridad

Una credencial falsa solo demuestra que la herramienta gestiona correctamente un valor con formato de credencial. No demuestra que el proveedor acepte la credencial, que tenga los permisos previstos ni que un valor revocado falle correctamente.

La diferencia se difumina porque muchos equipos llaman «secreto falso» a cualquier secreto que no sea de producción. En realidad hay tres cosas muy distintas:

- Una cadena sintética que existe únicamente en un stub local. No puede autenticar en ningún sitio.
- Una credencial de prueba que se autentica contra un proveedor real, pero solo dentro de un tenant o proyecto desechable.
- Una credencial de producción restringida que puede autenticarse contra producción, aunque sus permisos parezcan pequeños.

La primera opción corresponde a las pruebas unitarias y de contrato. La segunda, a las pruebas de integración. La tercera no pertenece a un conjunto automatizado de pruebas de agentes. Una credencial que puede leer el registro de un cliente de producción ya ha cruzado el límite que pretendías proteger.

Haz que los valores falsos se parezcan al formato que recibe realmente tu código. Si un cliente de API rechaza tokens sin prefijo o por debajo de una longitud mínima, usa un valor sintético que supere esa validación local. No copies un token real y cambies un carácter. La gente pega fixtures en sistemas de seguimiento, transcripciones de terminal y chats. Un secreto casi real te da todos los riesgos de manipulación sin ningún beneficio para las pruebas.

Un fixture útil indica la autoridad que no tiene. Por ejemplo, `stub_token_no_network` dice más que `token123`. Una referencia a una credencial de prueba como `billing_test_writer` indica que un almacén de secretos, no el agente, resolverá el valor real. Mantén estable la referencia y rota la credencial de prueba subyacente cuando sea necesario.

No confundas un mock exitoso con autorización. Cuando un stub devuelve `200`, ha aceptado el contrato que programaste. Es una evidencia útil sobre la construcción de tu propia solicitud, pero no dice nada sobre el modelo de permisos del proveedor.

## Separa la entrada no válida, el rechazo y el fallo operativo

Un agente necesita instrucciones distintas para un argumento incorrecto, una acción denegada y una dependencia averiada. Si tu herramienta MCP convierte los tres casos en `request failed`, el agente volverá a intentar acciones que debería detener y abandonará acciones que podría reparar.

Usa un vocabulario pequeño de resultados y consérvalo en el resultado de la herramienta. Yo uso cinco clases:

- Error de validación: el agente proporcionó un argumento no válido o incompleto. Puede corregir la llamada.
- Fallo de autenticación: falta la credencial, ha caducado, está malformada o fue revocada. Repetir la misma llamada no ayudará.
- Rechazo de autorización: la identidad se autenticó, pero no tiene permisos, o una persona denegó una aprobación. El agente no debe intentar rodear el límite.
- Conflicto: la solicitud era válida, pero no puede aplicarse al estado actual del recurso. Puede ser necesario consultar primero el estado actual.
- Fallo operativo: tiempo de espera agotado, fallo de conexión, límite de velocidad o error del proveedor. Reintentar puede tener sentido si la acción se puede repetir con seguridad.

HTTP define las partes conocidas de esta separación. RFC 9110 describe `401 Unauthorized` como un desafío de autenticación, pese a su nombre históricamente confuso, y `403 Forbidden` como una negativa a cumplir la solicitud. Tu proveedor puede usar esos códigos de forma imperfecta, así que prueba también el cuerpo y los códigos de error documentados. No construyas el comportamiento del agente basándote solo en el código de estado.

El resultado de una herramienta del Model Context Protocol admite un indicador `isError` junto al contenido. Úsalo cuando falle la llamada, pero incluye la categoría accionable en el texto o en el contenido estructurado que espera tu cliente. Un error JSON RPC de nivel de transporte y un error de nivel de herramienta también son diferentes. Reserva los errores JSON RPC para solicitudes de protocolo malformadas o métodos no disponibles. Devuelve un resultado normal de `tools/call` con `isError: true` cuando la herramienta se haya ejecutado y la acción ascendente haya fallado o sido rechazada.

Esta forma de respuesta da al agente algo concreto sobre lo que actuar:

```json
{
  "jsonrpc": "2.0",
  "id": 42,
  "result": {
    "content": [
      {
        "type": "text",
        "text": "authorization_rejected: identity billing_test_writer cannot create invoices in tenant test-acme. Request a role change or stop."
      }
    ],
    "isError": true
  }
}
```

No incluyas en este resultado el bearer token, el encabezado de autorización, la solicitud ascendente completa ni el error sin filtrar del proveedor. Los mensajes de error forman parte de la ventana de contexto del agente, por lo que pueden quedar retenidos en sistemas que no controlas. El error debe tener suficiente detalle para orientar el comportamiento, no para servir como volcado forense.

## Las cuentas desechables necesitan un límite firme

Una cuenta desechable solo es segura cuando no tiene ninguna vía hacia los recursos de producción. Una dirección de correo separada y una etiqueta `test` no crean ese límite.

Empieza por la unidad de aislamiento más sólida del proveedor. Puede ser una organización, tenant, proyecto en la nube, base de datos o instancia autogestionada separada. Coloca la cuenta de prueba dentro de esa unidad y verifica que no pueda cambiar a una unidad de producción mediante un identificador, un rol compartido o un valor de configuración predeterminado.

Después concede a la cuenta los permisos necesarios para la prueba prevista y nada más. Si una herramienta crea facturas en las pruebas, su identidad de prueba necesita permiso para crear y listar facturas de prueba. No necesita permisos de exportación, acceso de administrador ni acceso a una configuración de pagos compartida. El acceso amplio en pruebas es popular porque acorta la configuración, pero también oculta qué permisos necesita realmente la herramienta.

Usa un marcador explícito de ejecución para cada recurso que cree el conjunto de pruebas. Ponlo en un campo de metadatos compatible, una descripción, una etiqueta o el nombre. El marcador hace más segura la limpieza y permite reconocer los artefactos de prueba que se hayan quedado atrás. No elimines todo lo que casualmente esté en un tenant de prueba. Otro desarrollador podría estar reproduciendo allí un error.

Una configuración mínima puede tener este aspecto:

```yaml
run_id: mcp-it-20250308-7f3c
account: agent-tool-test@invalid.example
allowed_tenant: test-acme
resource_prefix: mcp-it-20250308-7f3c-
cleanup_after_minutes: 90
```

La configuración evita un fallo común y costoso: que el ejecutor de pruebas apunte al tenant equivocado porque una variable de entorno del shell del desarrollador prevalece sobre la configuración versionada. El código de preparación debe obtener la identidad del tenant actual antes de crear nada y compararla con `allowed_tenant`. Si no coinciden, detente antes de la primera escritura.

Desechable no significa anónimo ni sin propietario. Asigna un responsable, registra cómo se emite la credencial y haz que la expiración sea intencionada. Una identidad de prueba olvidada sigue siendo una identidad con acceso.

## Construye contratos alrededor de solicitudes observables

Una buena prueba de contrato comprueba la solicitud que envía la herramienta, el resultado que devuelve y la información que se niega a exponer. No se limita a afirmar que se llamó a una función.

Coloca un stub HTTP local delante del cliente y haz que inspeccione el método, la ruta, los encabezados, los parámetros de consulta y el cuerpo. El stub debe rechazar campos inesperados. Los mocks permisivos enseñan a una herramienta a enviar argumentos accidentales hasta que un proveedor real los rechaza.

Para una herramienta `create_invoice`, una prueba centrada puede realizar esta llamada:

```json
{
  "jsonrpc": "2.0",
  "id": 42,
  "method": "tools/call",
  "params": {
    "name": "create_invoice",
    "arguments": {
      "tenant": "test-acme",
      "customer_id": "cus_mcp_it_7f3c",
      "amount_cents": 500,
      "currency": "USD"
    }
  }
}
```

El stub debe esperar `POST /v1/invoices`, confirmar que el encabezado de autorización contiene su valor de prueba sintético y confirmar que la solicitud incluye el marcador de ejecución. Puede devolver un identificador fijo como `inv_mcp_it_001`. El resultado MCP debe exponer el identificador y el estado de la factura, pero nunca repetir el encabezado de autorización.

Escribe al menos una afirmación de contrato negativa para cada campo sensible de la solicitud. Envía un campo `authorization`, `base_url`, `account_id` o `tenant` proporcionado por quien llama si el esquema pudiera admitirlo. Confirma que la herramienta rechaza o ignora los valores que podrían redirigir una acción a otra cuenta. Muchas filtraciones de credenciales empiezan como una vía de escape aparentemente inofensiva para un endpoint personalizado.

Prueba la serialización de la solicitud en los bytes relevantes. Un proveedor puede distinguir entre campos omitidos y `null`, cadenas vacías y valores ausentes, o valores numéricos y cadenas numéricas. Los agentes generan formas de argumentos inesperadas, sobre todo cuando la descripción de una herramienta no indica las unidades. Tu esquema debe decir `amount_cents`, no `amount`, si el proveedor espera unidades menores.

Las pruebas de contrato también son el lugar donde impones la higiene de los registros. Captura el evento de registro estructurado y afirma que contiene una referencia a la credencial o un marcador redactado, nunca el secreto sintético. Un secreto falso se convierte en un problema real de divulgación cuando los ingenieros se acostumbran a imprimirlo en todas partes.

## Prueba los permisos como una matriz, no como un camino feliz

Una única identidad de prueba autorizada no puede decirte si una herramienta gestiona correctamente los permisos. Necesitas varias identidades con derechos deliberadamente distintos y un conjunto de casos que nombre el resultado esperado.

Mantén la matriz lo bastante pequeña para poder mantenerla. Para una herramienta de escritura, estos casos suelen merecer un lugar:

| Estado de la identidad | Acción solicitada | Resultado esperado |
| --- | --- | --- |
| escritor en el tenant de prueba | crear un registro marcado | éxito |
| lector en el tenant de prueba | crear un registro marcado | rechazo de autorización |
| escritor en otro tenant de prueba | crear un registro en el tenant objetivo | rechazo de autorización |
| credencial revocada | listar registros marcados | fallo de autenticación |
| credencial caducada | crear un registro marcado | fallo de autenticación |

Esta matriz detecta un defecto común: la herramienta comprueba que exista un token, pero nunca comprueba qué tenant representa realmente. El camino feliz pasa porque el escritor tiene acceso amplio. El fallo aparece cuando un agente recibe un identificador de tenant en una tarea y el cliente lo acepta sin vincularlo a la credencial seleccionada.

Ejecuta cada caso de la matriz contra el proveedor desechable real cuando puedas. La semántica del proveedor respecto a roles heredados, permisos predeterminados y revocación retrasada suele diferir de la documentación. Mantén los casos aislados. Si una prueba eleva un rol que otra espera que no exista, las ejecuciones paralelas producirán fallos que parecen errores de autorización.

No fabriques respuestas prohibidas únicamente en un stub y des por terminado el conjunto. Un stub confirma que tu conversor de errores reconoce un `403`. La identidad de prueba real confirma que la emisión de credenciales, la configuración del proveedor y el enrutamiento de la herramienta producen un rechazo genuino.

Hay una excepción útil: probar respuestas del proveedor que no puedes provocar de forma fiable, como JSON malformado o un tipo de contenido no válido. Esos casos pertenecen al stub. El objetivo no es la pureza, sino saber qué evidencia aporta cada prueba.

## Las escrituras necesitan planes de limpieza antes del código de prueba

Para cada herramienta que cambie el estado, decide cómo eliminarás o neutralizarás ese estado antes de escribir la prueba de éxito. Si no puedes describir la limpieza, elige otro objetivo u otra operación.

Prefiere operaciones que creen registros dentro de un proyecto de prueba de corta duración. Evita llamadas que envíen correo electrónico, cobren una tarjeta, roten un secreto compartido, activen un despliegue o contacten con un tercero real. El sandbox de un proveedor aún puede enviar webhooks a un endpoint que configuraste hace años. Comprueba los efectos colaterales, no solo la etiqueta de la API.

Usa un marcador de ejecución único y limpia en un bloque `finally` o su equivalente. La limpieza debe tolerar un recurso que nunca se creó, uno que se creó dos veces después de un reintento y una cuenta configurada parcialmente. Una limpieza idempotente ahorra tiempo cuando una prueba falla a mitad del aprovisionamiento.

Un fallo que vale la pena probar tiene este aspecto. La herramienta envía una solicitud de creación. El proveedor crea el registro, pero cierra la conexión antes de devolver la respuesta. El agente ve un fallo operativo y vuelve a intentarlo. Sin un valor de idempotencia, el reintento crea un duplicado. Sin un marcador de ejecución, la limpieza no puede encontrar ambos registros de forma segura.

Asigna a la solicitud de creación un valor de idempotencia derivado del identificador de ejecución y de la acción lógica, no del intento de transporte. El primer intento y el reintento deben usar el mismo valor. Después prueba el caso de respuesta perdida con un stub que registre la primera solicitud, cierre la conexión y devuelva éxito al recibir de nuevo el mismo valor de idempotencia. Afirma que en el lado del proveedor solo existe un registro.

Si el proveedor no admite idempotencia, haz que la herramienta consulte una referencia externa única antes de repetir una escritura. Este enfoque puede sufrir condiciones de carrera con concurrencia, así que documenta el riesgo restante. No repitas en silencio movimientos de dinero ni escrituras irreversibles porque un cliente HTTP genérico considere que `POST` se puede reintentar.

## Los tiempos de espera y los límites de velocidad revelan un mal comportamiento del agente

Una herramienta que gestiona correctamente el éxito y `403` aún puede causar daños cuando su dependencia se vuelve lenta. Los agentes tienden a reintentar porque intentan terminar la tarea. Tu herramienta debe darles una respuesta limitada y veraz.

Prueba un tiempo de espera de conexión antes de que la solicitud llegue al servidor, un tiempo de espera de respuesta después de que el servidor reciba la solicitud y una respuesta `429` del proveedor. Estos casos son diferentes. El primero normalmente significa que no hubo efecto secundario. El segundo puede significar que el servidor completó la escritura. Un `429` puede incluir una instrucción de reintento, pero úsala solo si el proveedor documenta el campo y tu herramienta conserva su significado.

Establece tiempos de espera de prueba breves para que el conjunto siga siendo manejable, pero no sustituyas los tiempos de producción por valores de prueba en el código. Inyecta un reloj o una configuración de transporte. Un tiempo de espera de dos segundos escrito directamente en el código es una comodidad de prueba que se convertirá en una interrupción cuando alguien lo despliegue.

Tus afirmaciones deben cubrir el comportamiento que observa el agente además del cliente HTTP. Para un límite de velocidad, devuelve un fallo operativo que nombre al proveedor e indique si se permite reintentar después de una espera. Para un tiempo de espera de respuesta posterior a una escritura, indica que el resultado es desconocido e instruye a quien llama a buscar la operación mediante su valor de idempotencia o referencia externa. Llamar a ese caso un fallo simple invita a duplicar escrituras.

No pruebes los reintentos afirmando solo un número de llamadas. Registra si el reintento reutilizó el valor de idempotencia, si esperó cuando debía y si se detuvo al alcanzar el límite configurado. Un bucle de reintentos que termina finalmente aún puede crear una ráfaga que agote un tenant de prueba pequeño y oculte el defecto real.

## La denegación de aprobación debe dejar intacto el objetivo

Si una persona puede aprobar acciones de agentes, prueba la ruta de denegación contra un endpoint donde el efecto secundario sea evidente. Una pantalla que dice «denegado» no demuestra que la capa de ejecución se haya detenido.

Configura un objetivo desechable con un contador, un registro marcado o una lista de eventos de prueba de solo adición. Inicia el proceso del agente, emite la llamada de la herramienta y deniega la sesión o la llamada. Después consulta directamente el objetivo con una identidad de observador de prueba separada. El recuento esperado no cambia.

Esta prueba detecta un error de orden: la herramienta inicia la solicitud ascendente y después pide aprobación mientras espera la respuesta. Ese flujo puede parecer correcto en una demostración si el proveedor es lento, pero no cumple el propósito de la aprobación. La decisión de autorización debe producirse antes de que el despachador de acciones abra la conexión de red o inicie un asistente SSH.

Prueba por separado un proceso de agente nuevo y una segunda llamada dentro del mismo proceso. Son promesas de seguridad distintas para sistemas que conceden autorización por ejecución. Prueba también un proceso cuya identidad de firma difiera de la esperada. El aviso de aprobación debe mostrar suficiente información de origen para que la persona pueda distinguir el agente previsto de un proceso local arbitrario.

En un flujo de trabajo local de Mac, Sallyport mantiene las credenciales de API y SSH en su almacén cifrado y puede exigir una aprobación para cada ejecución nueva del agente o para cada uso de una credencial seleccionada. Prueba esos controles contra endpoints desechables, no los uses como motivo para omitir las pruebas de permisos ascendentes.

## Las pruebas SSH necesitan un objetivo que se pueda desechar

SSH añade modos de fallo que los ejemplos HTTP ocultan: verificación del host, comillas de comandos, herencia del entorno, comportamiento del shell remoto y archivos que quedan tras una conexión interrumpida. Nunca dirijas pruebas SSH automatizadas de agentes a una estación de trabajo de desarrollador ni a un host de administración compartido.

Crea una máquina de pruebas controlada o una máquina virtual de corta duración con una cuenta exclusiva. Dale un directorio personal sin datos útiles, un conjunto de comandos restringido cuando sea posible y ninguna credencial que pueda alcanzar otros sistemas. Usa una clave SSH de prueba exclusiva y deséchala cuando expire el entorno.

Prueba argumentos de comandos que rompen una construcción ingenua del shell. Incluye espacios, comillas, caracteres de nueva línea, rutas que comiencen con un guion y datos parecidos a sintaxis del shell. La herramienta debe pasar los argumentos al comando remoto sin concatenar valores proporcionados por el usuario en una cadena de shell. Si la interfaz remota necesaria solo acepta texto de shell, limita la gramática de comandos permitida y rechaza todo lo demás.

Una prueba segura puede pedir al objetivo que cree un archivo cuyo nombre contenga el marcador de ejecución y después lea exactamente ese archivo. Una prueba de rechazo puede solicitar una ruta fuera del directorio permitido para la cuenta de prueba y esperar que el objetivo la deniegue. Una prueba operativa puede terminar la conexión SSH después de iniciar el comando y comprobar si el proceso remoto continuó.

Recopila el estado de salida, una salida estándar limitada y una salida de error estándar limitada. No devuelvas una cantidad ilimitada de salida de comandos a un agente. Las salidas grandes consumen contexto y a menudo contienen valores de configuración que nunca debieron salir del host.

## La evidencia de auditoría debe conectar la llamada del agente con el efecto secundario

Cuando una prueba de agente falla, necesitas saber si la herramienta realizó la llamada, si el proveedor la recibió y si la limpieza la eliminó. Un montón de texto de consola no responde a esas preguntas de forma fiable.

Asigna a cada ejecución de prueba un identificador y pásalo por la solicitud MCP, el registro de la herramienta, los metadatos del proveedor y el registro de limpieza. No incluyas credenciales en ese identificador. Un registro de evidencia útil contiene el nombre de la herramienta, la categoría de la acción, la referencia de la credencial, el tenant objetivo, el identificador de correlación de la solicitud, la categoría del resultado y el identificador del recurso devuelto.

Mantén separados los registros de sesiones de agentes y los registros de acciones. Una sesión indica qué proceso realizó una ejecución y cuándo la revocaste. Un registro de acción indica qué llamada externa ocurrió. Unirlos mediante un identificador de correlación hace auditable una prueba de denegación: puedes demostrar que el agente intentó una acción, que la autorización la denegó y que no existe ningún registro de acción externa correspondiente.

La evidencia de manipulación importa cuando usas los resultados de las pruebas para revisar un nuevo límite de herramientas. Sallyport proyecta sus diarios de sesiones y actividad desde un registro de auditoría cifrado y encadenado mediante hash, y `sp audit verify` puede verificar esa cadena sin conexión y sin una clave del almacén. Esto no sustituye los registros del proveedor, pero permite que las pruebas locales detecten un historial de acciones alterado.

No trates la salida de auditoría como un depósito de secretos. Registra referencias de credenciales y atributos redactados, y prueba después esas reglas. El registro de auditoría debe ayudarte a reconstruir una acción sin convertirse en el lugar más fácil para robar el acceso que la hizo posible.

## Un candidato a lanzamiento obtiene acceso a producción poco a poco

Una herramienta debe avanzar por límites cada vez más realistas: stubs sintéticos locales, un proveedor desechable real, identidades denegadas y caducadas, inyección de fallos y una prueba de aprobación humana si el flujo la utiliza. Saltar directamente a producción porque el sandbox es diferente es la forma en que los equipos descubren su comportamiento de seguridad bajo presión.

Mantén un conjunto corto de lanzamiento que se ejecute con cada cambio y otro más profundo que aprovisione recursos desechables con menor frecuencia. El conjunto corto debe cubrir el esquema de la herramienta, la construcción de solicitudes, la redacción y fallos representativos. El conjunto profundo debe cubrir los permisos reales, la limpieza del ciclo de vida, la revocación y el aislamiento del objetivo.

Antes de permitir una acción nueva contra producción, revisa la evidencia de una llamada denegada deliberadamente y de un tiempo de espera de escritura deliberadamente ambiguo. Esos casos revelan si la herramienta respeta un límite y si dice la verdad al agente cuando el proveedor puede haber actuado. Los caminos de éxito son fáciles de preparar. Los caminos de rechazo e incertidumbre son los que hacen que el acceso a producción sea controlado o imprudente.
