mcp-permission-server
mcp-permission-server
Una capa de permisos delante de las herramientas MCP, y un registro que puede demostrar lo que decidió.
Todo registro de auditoría documenta lo que ocurrió. Casi ninguno documenta lo suficiente para recalcular por qué se permitió, y la mayoría solo documenta lo que tuvo éxito — así que cuando alguien pregunta «cómo llegó el agente a ese archivo», el registro no puede responder. Solo puede repetir la afirmación de que se tomó una decisión.
NAIVE GUARDED
calls in the session 17 17
calls that must be denied 8 8
EXECUTED WITHOUT A GRANT 7 0
LEGITIMATE CALLS REFUSED 0 0 ← no over-blocking
DECISIONS THAT CANNOT BE 16 0 ← the number nobody reports
RECONSTRUCTED FROM THE LOG
denials that left no trace 1 0
secrets sitting in the log 2 0
entry removable unnoticed 1 0NAIVE es el modelo de permisos con el que un servidor MCP viene de serie cuando los permisos no eran el objetivo: una lista de nombres de herramientas permitidas y un registro de lo que tuvo éxito. Todas las llamadas de esta sesión excepto una lo superan.
python3 demo.py
python3 gate.py --log audit.json # run the session
python3 gate.py --verify audit.json # re-decide every entry from the log alonechain: OK - the chain is intact
replay: 17/17 entries recompute to the verdict they record
secrets in the log: noneCódigo de salida: 0 todo permitido, 1 algo denegado, 2 el registro no se verifica.
Una concesión es cuatro cosas a la vez
Lo que supone cuatro formas distintas de que una concesión vigente siga sin cubrir una llamada — y la razón por la que «¿tiene este llamador permiso para esta herramienta?» es la pregunta equivocada.
{"id": "G1", "tool": "fs.read", "scope": "/data/reports",
"purpose": "q3-report", "expires_at": 100, "max_uses": 10}la llamada | qué tiene de malo | qué más la habría permitido |
| un directorio distinto que empieza con los mismos caracteres | herramienta ✓ propósito ✓ caducidad ✓ |
| el mismo directorio, alcanzado a través del concedido | herramienta ✓ propósito ✓ caducidad ✓ |
| la concesión caducó hace cuarenta ticks. Nada en la llamada es incorrecto | herramienta ✓ alcance ✓ propósito ✓ |
el tercer | una concesión es un presupuesto además de un permiso | herramienta ✓ alcance ✓ propósito ✓ |
| idéntica a la primera llamada de la sesión salvo por el motivo por el que se hace | herramienta ✓ alcance ✓ caducidad ✓ |
| la plantilla incorpora | herramienta ✓ alcance ✓ propósito ✓ caducidad ✓ |
| tener una concesión para una clave no es tener el llavero | herramienta ✓ propósito ✓ caducidad ✓ |
Hay una prueba que, para cada una de estas, desactiva solo la comprobación que nombra y exige que la llamada pase. Una denegación que varias comprobaciones detectan es una denegación que no demuestra nada sobre ninguna de ellas.
Related MCP server: MCP Airlock
El diputado confundido
render_template está concedida. /templates/leak.md está dentro del alcance concedido. La plantilla contiene {{include:/etc/service-token}}.
El servidor posee derechos que el llamador no tiene, y se le pide que los gaste en un recurso que el llamador nunca nombró. Una capa de permisos que comprueba el recurso en los argumentos está comprobando lo incorrecto precisamente en las herramientas donde importa.
Así que la comprobación examina lo que una llamada alcanza, antes de que lo alcance — y aun así dice sí a /templates/report.md, que alcanza /data/reports/q3.csv, para lo que este llamador sí tiene una concesión. La mitad de una comprobación de permisos es el sí. Una regla que bloquea toda plantilla porque las plantillas pueden incluir cosas no es una regla.
Contención de rutas, que es donde esto suele fallar
Ambos errores son de una línea.
path.startswith(scope) # /data/reports-private is "inside" /data/reports
".." in path # and this breaks ordinary callers to catch nothingEl segundo merece su propio caso. /data/reports/../reports/q3.csv se resuelve de vuelta dentro del alcance concedido, y está en el corpus como una llamada que debe ser permitida. Prohibir los caracteres es el arreglo barato; resolver la ruta captura todo lo que prohibirlos capturaría, y nada que no capturaría.
La resolución aquí es léxica y nunca toca un sistema de archivos — hay una prueba que busca con grep realpath, os.stat y open(. La decisión se ejecuta antes de la llamada, sobre una ruta que el llamador proporcionó, y no debe poder responderse de forma distinta según lo que exista.
El registro es el producto
Dos propiedades lo convierten en evidencia en lugar de un diario.
Registra denegaciones. Un registro de lo que tuvo éxito no puede responder «qué intentó el agente», que es la pregunta que cualquiera hace después. Aquí, ocho de las diecisiete entradas son rechazos, cada una con la comprobación que lo causó y el motivo en lenguaje claro.
Se puede reproducir. Cada entrada lleva la concesión bajo la que se decidió y la versión de la política, de modo que la decisión puede recalcularse solo a partir del registro y compararse con el veredicto que registra:
python3 gate.py --verify audit.json
replay: 17/17 entries recompute to the verdict they recordCon el campo replay desactivado, ese número es 0/17 — las entradas siguen diciendo lo que se decidió y no dan a nadie forma alguna de comprobarlo.
Y las entradas están encadenadas por hash, de modo que una eliminada o editada es visible:
entry 6 does not follow the one before it -- something was removed or editedLos secretos nunca entran en él. Un registro de auditoría que contiene el secreto es una segunda copia del secreto, en un archivo que más personas pueden leer. secrets.get está permitida en esta sesión y su valor no aparece en ningún lugar del registro; el registro ingenuo contiene dos.
Las nueve comprobaciones, y la prueba de que cada una se gana su lugar
check what turning it off lets through
grant 1 more unauthorised, 1 more unreconstructable, 1 more egress
scope 3 more unauthorised, 3 more unreconstructable
expiry 2 more unauthorised, 2 more unreconstructable
purpose 1 more unauthorised, 1 more unreconstructable
deputy 1 more unauthorised, 1 more unreconstructable
redact 1 more secret in the log
deny_log 8 more denials that left no trace
replay 17 more decisions that cannot be reconstructed
chain 1 more entry removable unnoticedpython3 ablate.pyUna corrección digna de registrarse: la primera versión de la ablación de scope no desactivaba la comprobación — sustituía la comparación por prefijo de cadena, que es una comprobación distinta en lugar de la ausencia de una, y seguía denegando silenciosamente una llamada que existía para demostrar que la comprobación era necesaria. Desactivar una comprobación tiene que significar desactivarla, o la ablación está midiendo un intercambio.
La misma sesión también expuso una concesión dimensionada de modo que su última llamada fue denegada por ser un uso de más en lugar de por nombrar una clave que no cubría. Estaba probando la comprobación equivocada y pasando. El arreglo estaba en el fixture, y la prueba que lo encontró es la que exige que cada denegación aísle su propia comprobación.
Límites conocidos, declarados en lugar de ocultos
Esta es la capa de decisión, no un transporte. Decide, ejecuta contra una tabla de herramientas en proceso y registra. Conectarla a un transporte MCP real es trabajo ordinario; lo que hay aquí es la parte que suele faltar, y funciona sin red y sin clave.
Las concesiones se escriben, no se obtienen. Un despliegue real pregunta a una persona y almacena la respuesta. Lo que esa respuesta debe contener — herramienta, alcance, propósito, caducidad, número de usos — es la parte sobre la que esto argumenta.
purposelo declara el llamador y se toma al pie de la letra. Un llamador que miente sobre su propósito vence la comprobación. Aun así compra algo real: el propósito declarado está en el registro, así que la mentira consta en el registro y el alcance de la concesión no se amplió para acomodarla.La comprobación del diputado conoce el alcance de una herramienta.
render_templateanaliza sus inclusiones. Una herramienta cuyo alcance el servidor no puede calcular no puede comprobarse de esta manera, y la respuesta honesta para tal herramienta es una concesión más estrecha en lugar de una comprobación más amplia.La cadena demuestra orden e integridad, no custodia. Cualquiera que pueda reescribir el archivo completo puede reescribir la cadena completa. Hace visible una edición selectiva, que es la amenaza realista; un almacén de solo añadido o un ancla externa es lo que hace visible el resto, y eso es trabajo de despliegue.
Estructura
fixtures/spec.py the session, the grants, the world, and the answer key
build_fixtures.py renders session.json, policy.json, world.json, truth.json
mcpgate/scope.py is this path inside that scope
mcpgate/policy.py a grant is four things at once
mcpgate/tools.py the tool table, and what a call actually reaches
mcpgate/audit.py the hash-chained, redacted, replayable log
mcpgate/server.py naive() and guarded(), and replay()
gate.py the CLI: run a session, or verify a log
score.py every number above
ablate.py each check off, one at a time
tests/ 38 testsEjecutarlo
python3 demo.py # both servers + ablation
python3 gate.py --log audit.json # the product
python3 gate.py --verify audit.json # the proof
python3 -m pytest tests -q # 38 testsDocker, con la red desactivada:
docker compose run --rm gatePruebas
Una afirma que la lista de permitidos sigue dejando pasar casi todo. Una reproduce cada decisión registrada y exige el mismo veredicto. Una exige que cada denegación sobreviva solo por la comprobación que nombra, de modo que los números de ablación signifiquen lo que dicen. Una busca con grep en el resolvedor de rutas llamadas al sistema de archivos.
This server cannot be installed
Maintenance
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
AlicenseNot gradedqualityCmaintenancePolicy enforcement gateway for MCP tool calls, evaluating every tool invocation against declarative YAML policies (allow/deny/escalate-to-human), generating cryptographic hash-chained audit receipts, and including built-in content safety scanning.2MIT- AlicenseCqualityDmaintenanceEnables secure, zero-trust access to MCP tools through short-lived, signed capability leases that bind tool execution to specific sessions, intents, and constraints. Prevents prompt injection attacks and privilege escalation with dynamic risk scoring, policy enforcement, and tamper-evident audit logging.41MIT
- AlicenseNot gradedqualityAmaintenanceAn authorizing reverse proxy for MCP servers that enforces per-call policy rules on tool arguments with audit logging, dry-run, and rate limiting.Apache 2.0
- AlicenseNot gradedqualityBmaintenanceEnforces identity-based access control and audit logging for MCP servers, letting you grant fine-grained tool permissions to users and systems while failing secure by default.MIT
Related MCP Connectors
Remote MCP for A2A caller identity, scope policy, verdict receipts, and audit history.
Runtime permission, approval, and audit layer for AI agent tool execution.
Remote MCP for Copilot CLI switch gate MCP, structured receipts, audit logs, and reviewer-ready evid
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
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/jigonyoo/mcp-permission-server'
If you have feedback or need assistance with the MCP directory API, please join our Discord server