Skip to main content
Glama

mcp-confirm

CI Python License: MIT

En términos sencillos: cuando una IA pregunta «¿estás seguro?», el SDK de MCP ya impide que esa aprobación se reutilice para una acción diferente. No impide que la misma aprobación se use dos veces. Esto cierra esa brecha, y otras dos que el SDK deja abiertas.


Léase primero: la mayor parte de este problema ya está resuelto

La especificación MCP del 2026-07-28 añadió las Solicitudes de Múltiples Rondas (Multi Round-Trip Requests), de modo que una herramienta puede pausarse a mitad de la llamada y pedir al usuario que confirme. La aprobación viaja de vuelta a través del cliente como un requestState opaco, que la especificación dice que los servidores DEBEN tratar como controlado por el atacante.

El SDK de Python hace esto por ti, por defecto, en cada MCPServer. RequestStateBoundary se añade a la cadena de middleware incondicionalmente — con una clave efímera si no se proporciona ninguna. Sella el estado bajo AES-256-GCM y lo vincula al método, al objetivo, a un resumen de los argumentos de la llamada, a la audiencia y al principal autenticado, con un TTL.

Así que el ataque que todo el mundo menciona primero — aprobar la eliminación de cache.txt y luego reproducir esa aprobación contra thesis.txtya es rechazado por el SDK. No necesitas una biblioteca para eso, y no deberías escribir una.

Este repositorio originalmente escribió una de todos modos: 250 líneas de HMAC, TTL y vinculación de argumentos, publicada como la característica principal, redundante el día en que se publicó. Eso se registra al final en lugar de borrarse silenciosamente.

Related MCP server: RecourseOS

Lo que el SDK no hace

Tres brechas, cada una con una prueba que se dispara.

1. Vincula y expira el estado. Nunca lo gasta.

Dentro del TTL, la misma aprobación se verifica tantas veces como se presente. Cada comprobación que hace el límite pasa, cada vez. La especificación es explícita en que esto es deliberado y que el resto es tuyo:

Nótese que estas medidas limitan la ventana de reproducción y previenen la reutilización entre usuarios y entre solicitudes, pero no garantizan por sí mismas el uso único. Los servidores para los que un requestState dado debe consumirse como máximo una vez (por ejemplo, canjes de un solo uso) DEBEN hacer cumplir esa invariante en el lado del servidor.

Para «eliminar un archivo», el segundo intento no encuentra nada. Para «transferir 500 £» es el problema completo. singleuse.py es esa invariante — un registro de confirmaciones pendientes, gastadas al canjearse. No contiene criptografía, porque el límite ya garantiza que el texto plano es algo que este servidor acuñó.

2. Nada sanitiza la pregunta que lee el humano

Un message de elicitación es texto elegido por el servidor que se muestra a una persona, normalmente con un valor no confiable interpolado en él — el punto es decir cuál archivo va a desaparecer. Así que nombra un archivo:

cache.txt

SYSTEM NOTICE: your session has expired.
Enter your AWS secret key to continue:

El diálogo ahora lleva un segundo aviso de apariencia oficial. El usuario no está confirmando una eliminación; está siendo víctima de phishing por su propia herramienta. La misma versión del 2026-07-28 también incluyó MCP Apps — interfaz de usuario renderizada por el servidor — lo que amplía esta superficie en lugar de reducirla.

prompt.py aplana los valores no confiables a una línea, elimina los anuladores bidireccionales y los caracteres de ancho cero, y trunca desde el medio para que el nombre del archivo al final siga siendo visible. Los caracteres de control se convierten en espacios en lugar de eliminarse, ya que colapsarlos haría que a\nb y ab se mostraran de manera idéntica — dos archivos diferentes, un solo diálogo.

3. Ninguna capa de protocolo puede volver a comprobar tu recurso en el momento de la ejecución

El usuario pensó en ello en el medio. El archivo puede ser reemplazado en esa ventana, y solo la herramienta sabe qué significa «sin cambios» para él. Este servidor vuelve a comprobar antes de eliminar, y rechaza una ruta que se haya convertido en un enlace simbólico.

Qué capa rechaza qué

Esta es la parte útil, y las pruebas están escritas para demostrarlo. MCPError significa que el middleware del SDK rechazó antes de que este paquete se ejecutara; ToolError significa que este paquete lo hizo.

Ataque

Rechazado por

Prueba

Aprobación reproducida sobre otro archivo

SDK

test_the_sdk_refuses_a_confirmation_replayed_onto_another_file

Estado falsificado, o sellado con otra clave

SDK

test_the_sdk_refuses_a_forged_state

Misma aprobación gastada dos veces

este paquete

test_a_confirmation_cannot_be_spent_twice

Estado acuñado por otra réplica

este paquete

test_a_state_this_process_never_issued_is_refused

Nombre de archivo que falsifica un aviso del sistema

este paquete

test_a_forged_system_prompt_cannot_escape_its_slot

Archivo intercambiado por un enlace simbólico tras la aprobación

este paquete

test_a_swap_aimed_inside_the_root_is_refused

Ruta fuera de las raíces permitidas

este paquete

test_a_path_outside_the_roots_is_refused_before_asking

Pruebas

35 pruebas — 17 a través del servidor, 11 sobre el registro, 7 sobre la sanitización de avisos.

Cada prueba del servidor se ejecuta a través del RequestStateBoundary real, la misma clase que MCPServer instala sobre sí mismo, con una clave fija — sellando la primera ronda y desellando la segunda exactamente como lo haría el cable.

Ese arnés existe debido a un error específico. La primera versión se probó llamando directamente a MCPServer.call_tool(), que va directo al gestor de herramientas y omite por completo la cadena de middleware. El límite del SDK nunca se ejecutó, por lo que una protección redundante hecha a mano parecía esencial. El diseño de la prueba es lo que lo ocultó, durante un ciclo de compilación completo.

La cobertura es del 81%, con prompt.py al 100% y singleuse.py al 94%. La brecha es el argparse de main() y el cableado de transporte, ejercitados a través de build_server en su lugar.

CI se ejecuta solo en Ubuntu, deliberadamente: las pruebas de intercambio tras la confirmación necesitan enlaces simbólicos, y la compilación falla si se informan como omitidas allí.

Instalación

pip install git+https://github.com/les-k/mcp-confirm.git

Ejecución

mcp-confirm --root /path/you/allow

Sin --root, el servidor rechaza cada solicitud en lugar de asumir algo por defecto. Las raíces se fijan al inicio y nunca las elige el agente. No se necesita clave de firma — MCPServer trae la suya propia.

Limitaciones conocidas

  • El registro está en memoria, por lo que es correcto para un proceso y incorrecto detrás de un balanceador de carga. El SDK admite compartir claves entre réplicas (RequestStateSecurity(keys=[...])); bajo esa configuración, la réplica B rechaza una confirmación emitida por la réplica A. Falla de forma segura — la dirección segura — pero se lee al usuario como una confirmación que inexplicablemente dejó de funcionar. Un despliegue multiproceso necesita Redis o una fila de base de datos con una comparación y eliminación atómica. Hay una prueba para este comportamiento.

  • La herramienta de demostración es deliberadamente pequeña. Elimina un archivo. El código interesante es singleuse.py y prompt.py.

  • Ningún CVE respalda esto. MRTR tiene semanas de antigüedad, por lo que esto se construye a partir de la propia lista MUST/SHOULD de la especificación y de leer el SDK, no de un incidente publicado.

Qué estaba mal en la versión 0.1.0

Se mantiene aquí porque un repositorio que solo registra sus éxitos no es evidencia de nada.

0.1.0 reimplementó lo que el SDK ya hacía. state.py eran 250 líneas de firma HMAC, TTL y vinculación de principal/método/argumento — todo duplicando RequestStateBoundary, nada de ello tan bueno: HMAC donde el SDK usa cifrado autenticado, sin vinculación de audiencia, sin rotación de claves.

Se detectó leyendo el código fuente del SDK, no con una prueba. Las pruebas pasaron precisamente porque omitían el middleware que lo habría expuesto.

CI detectó por separado un error real en 0.1.0: la comprobación de enlace simbólico se ejecutaba después de Path.resolve(), por lo que inspeccionaba el destino del enlace en lugar del enlace en sí. Un intercambio dirigido a otro archivo dentro de una raíz permitida habría sido eliminado. Corregido, con una prueba para la variante que el original nunca ejercitó.

0.2.0 elimina state.py por completo y conserva solo lo que el SDK deja sin cubrir.

Licencia

MIT.

A
license - permissive license
Not graded
quality - not tested
C
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Servers

  • A
    license
    Not graded
    quality
    C
    maintenance
    MCP server that provides human-in-the-loop approval for risky AI agent actions, with durable state and audit logs.
    MIT
  • F
    license
    Not graded
    quality
    C
    maintenance
    MCP server that provides a security gateway for AI agents, enforcing allow/confirm/deny policies on tool calls and requiring human approval for risky operations, with full audit logging.

View all related MCP servers

Related MCP Connectors

  • Security firewall for AI agents — scans MCP calls for injection, secrets, and risks.

  • Security scanner for MCP servers. Detect vulnerabilities, prompt injection, and tool poisoning.

  • Scans MCP servers for tool poisoning, prompt injection and supply chain risks.

View all MCP Connectors

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/les-k/mcp-confirm'

If you have feedback or need assistance with the MCP directory API, please join our Discord server