Skip to main content
Glama
jigonyoo

mcp-permission-server

by jigonyoo

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           0

NAIVE 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 alone
chain:  OK - the chain is intact
replay: 17/17 entries recompute to the verdict they record
secrets in the log: none

Có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

fs.read /data/reports-private/salaries.csv

un directorio distinto que empieza con los mismos caracteres

herramienta ✓ propósito ✓ caducidad ✓

fs.read /data/reports/../reports-private/salaries.csv

el mismo directorio, alcanzado a través del concedido

herramienta ✓ propósito ✓ caducidad ✓

fs.read /data/reports/q4.csv en t=140

la concesión caducó hace cuarenta ticks. Nada en la llamada es incorrecto

herramienta ✓ alcance ✓ propósito ✓

el tercer fs.write contra una concesión válida para dos

una concesión es un presupuesto además de un permiso

herramienta ✓ alcance ✓ propósito ✓

fs.read /data/reports/q3.csv para marketing-export

idéntica a la primera llamada de la sesión salvo por el motivo por el que se hace

herramienta ✓ alcance ✓ caducidad ✓

render_template /templates/leak.md

la plantilla incorpora /etc/service-token, para lo que el llamador no tiene concesión y nunca nombró

herramienta ✓ alcance ✓ propósito ✓ caducidad ✓

secrets.get DB_ADMIN_PASSWORD

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 nothing

El 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 record

Con 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 edited

Los 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 unnoticed
python3 ablate.py

Una 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.

  • purpose lo 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_template analiza 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 tests

Ejecutarlo

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 tests

Docker, con la red desactivada:

docker compose run --rm gate

Pruebas

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.

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
    Policy 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.
    2
    MIT
  • A
    license
    C
    quality
    D
    maintenance
    Enables 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.
    4
    1
    MIT
  • A
    license
    Not graded
    quality
    A
    maintenance
    An 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
  • A
    license
    Not graded
    quality
    B
    maintenance
    Enforces 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

View all related MCP servers

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

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/jigonyoo/mcp-permission-server'

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