Skip to main content
Glama
hishamalward

mcpclerk

by hishamalward

mcpclerk

ci python license

Un proxy de gobernanza para servidores MCP: se sitúa delante de cualquier servidor MCP, aplica una lista blanca por herramienta, retiene las herramientas de clase escritura para aprobación humana, aplica cuotas por herramienta, redacta argumentos con aspecto de secreto y escribe un registro de auditoría encadenado por hash de cada llamada.

Un agente de IA en servidores MCP puede llamar a cualquier herramienta que estos expongan, tantas veces como quiera, con cualquier argumento, y nada registra lo que hizo de una forma que cualquiera pueda auditar. En una empresa, la pregunta no es "¿puede el agente hacer el trabajo?" sino ¿qué se le permite hacer, quién aprobó las partes peligrosas y qué hizo realmente?

mcpclerk responde a esas tres con código. Es en sí mismo un servidor MCP: el agente se conecta a él, él se conecta a los servidores reales y reexpone sus herramientas como upstream.tool. Cada llamada pasa por un único pipeline: lista blanca, cuota, redacción, aprobación, reenvío, registro. Una herramienta no listada es denegada. Una herramienta de clase escritura espera a que un humano responda y. Las denegaciones vuelven como errores legibles. El registro es JSON Lines de solo añadido, cada entrada con hash de la anterior, de modo que cualquier edición rompe la cadena.

La demo envuelve el servidor oficial de sistema de archivos: una lectura pasa, una escritura se retiene y se aprueba, un movimiento se deniega, la cuarta búsqueda en un minuto se deniega por cuota, y el registro lo verifica. 49 pruebas demuestran cada control contra un upstream falso, incluido que el upstream siempre recibe los argumentos sin redactar.

demo

Instalación

pip install mcpclerk          # Python 3.10+ (the MCP SDK requires it); pulls in mcp and pyyaml
mcpclerk --version

Desde el código fuente: git clone https://github.com/hishamalward/mcpclerk && cd mcpclerk && pip install -e ".[dev]" && pytest.

Related MCP server: Agentrim MCP

Cinco minutos

  1. Escribe una política. Esta es la de la demo (examples/policy.filesystem.yaml):

    version: 1
    defaults:
      unlisted: deny              # a tool not named here is an unreviewed tool
      approval_timeout_s: 120     # a call nobody answers in time is refused, and logged as such
    upstreams:
      fs:
        transport: stdio
        command: npx
        args: ["-y", "@modelcontextprotocol/server-filesystem", "/tmp/mcpclerk-demo-sandbox"]
        tools:
          "read_*": allow
          list_directory: allow
          search_files: { decision: allow, quota: { per_minute: 3 } }
          write_file: approve
          edit_file: approve
          create_directory: approve
          move_file: deny         # the filesystem server has no delete; move is its destructive op
  2. Mira qué ofrece el upstream y qué hace tu política con ello. Las anotaciones propias del servidor se muestran junto a tu decisión, que es como te das cuenta de que permitiste una herramienta destructiva:

    $ mcpclerk tools --policy examples/policy.filesystem.yaml
    tool                                 decision rule               read_only destructive quota
    fs.read_file                         allow    glob:read_*        True      None        -/-
    fs.write_file                        approve  exact              False     True        -/-
    fs.move_file                         deny     exact              False     True        -/-
    fs.search_files                      allow    exact              True      None        -/3
  3. Registra el proxy donde tu agente busca servidores MCP. Para Claude Code, examples/.mcp.json:

    { "mcpServers": { "fs-governed": {
        "command": "mcpclerk",
        "args": ["serve", "--policy", "examples/policy.filesystem.yaml"] } } }
  4. En una segunda terminal, espera las aprobaciones: mcpclerk approve. Cuando el agente llame a fs.write_file, verás la llamada con los secretos ya enmascarados y responderás y o n.

  5. Después: mcpclerk verify audit/mcpclerk.jsonl y mcpclerk report audit/mcpclerk.jsonl.

Los cinco controles

Control

Qué hace

Qué previene

Qué no puede prevenir

Demostrado por

Lista blanca

allow / deny / approve por herramienta, primero el nombre exacto, luego el glob más largo, luego defaults.unlisted (deny). Las herramientas denegadas y no listadas ni siquiera se muestran al agente.

Que el agente use una herramienta que nadie revisó.

Una mala decisión en la propia política. mcpclerk tools muestra las pistas de solo lectura / destructivas del upstream junto a tu decisión para dificultarlo.

test_policy.py, test_pipeline.py::test_denied_hidden_tool_called_by_name_is_refused

Aprobación

Las llamadas de clase approve se retienen. La solicitud se escribe en approvals/<id>.json con argumentos redactados; un humano responde con mcpclerk approve (o editando el archivo, o en un prompt de terminal si el proxy lo tiene). El tiempo de espera es una denegación.

Una escritura sin supervisión.

Un humano que aprueba sin leer. --approve-session existe para ese humano y se registra en cada entrada afectada.

test_approval.py, test_pipeline.py::test_approve_via_file_then_forward, test_approval_refused_and_timed_out

Cuotas

per_run y per_minute (ventana deslizante) por herramienta. Superar la cuota se deniega con el límite y los segundos hasta que la ventana se libere. Las llamadas denegadas no consumen cuota; las aprobadas y luego denegadas por humano sí.

Bucles descontrolados; una herramienta barata que se vuelve cara por volumen.

Distribuir un bucle entre muchas herramientas, o entre reinicios del proxy (per_run se reinicia con el proceso).

test_quota.py, test_pipeline.py::test_quota_exhaustion

Redacción

Las reglas de clave (api_key, token, password, authorization, ...) reemplazan todo el valor; las reglas de valor (cabeceras bearer, tokens sk-/AKIA/ghp_/xox, JWT, bloques PEM, userinfo de URL, password=...) reemplazan la coincidencia. Se aplica a lo que se registra y se muestra al humano. El upstream recibe los argumentos originales.

Que los secretos terminen en el registro o en la pantalla de un aprobador.

Un secreto con una forma que no está en la lista. Extiende redaction.extend / extend_keys para tus propias formas.

test_redact.py, test_pipeline.py::test_upstream_receives_unredacted_args

Registro de auditoría

Una entrada JSON Lines por llamada con marca de tiempo, upstream, herramienta, argumentos redactados, decisión, quién aprobó, resultado, latencia y hash = sha256(prev_hash + canonical(entry)). verify recalcula la cadena; report la resume.

Edición silenciosa, borrado o reordenamiento de entradas después del hecho; truncamiento de una ejecución completada (run-end lleva el recuento).

Un atacante que reescribe toda la cadena desde el origen (esto es una cadena, no una firma; ver más abajo). Truncamiento de una ejecución que se mató a mitad de camino.

test_audit.py (editar, borrar, reordenar, truncar)

Los resultados no se registran, solo su tamaño y tipos de contenido. El registro es una auditoría de decisiones, no una copia de los datos; almacenar resultados lo convertiría en un segundo lugar donde los secretos podrían filtrarse.

Cómo se mueve una llamada

agent ──tools/call fs.write_file──▶ mcpclerk ──▶ [namespace] ──▶ [allowlist] ──▶ [quota] ──▶ [redact for log]
                                                     │               │              │
                                                refused-unknown  refused-denied  refused-quota
                                                                                           │
                                                          ┌── decision = approve ──▶ [hold: approvals/<id>.json] ──▶ y ─┐
                                                          │                                │ n / timeout               │
                                                          │                       refused-by-human / refused-timeout   │
                                                          └── decision = allow ────────────────────────────────────────┤
                                                                                                                       ▼
                                                                          [forward with ORIGINAL args] ──▶ upstream ──▶ result
                                                                                                                       │
                                                                                          [append log entry, hash-chained]

Cada camino, incluida cada denegación, termina en una entrada de registro. Las denegaciones vuelven al agente como un resultado de herramienta normal con is_error: true y una razón de una línea: mcpclerk: refused-quota fs.search_files: 3/min exhausted; retry after 60s.

Aprobación, en detalle

El proxy normalmente lo inicia el cliente MCP del agente, y el SDK de MCP inicia servidores stdio en una nueva sesión, por lo que el proxy normalmente no tiene terminal propia. Por eso el mecanismo es una cola de archivos y el prompt de terminal es un cliente de ella:

  • approvals/<id>.json se escribe para cada llamada retenida, con los argumentos redactados, requested_at, expires_at y "approved": null.

  • mcpclerk approve (en cualquier terminal, en la misma máquina) muestra las solicitudes pendientes y escribe tu respuesta. --once responde una y sale; sin él, sigue vigilando.

  • Editar el archivo a mano para poner "approved": true también funciona, que es lo que hace un trabajo sin cabeza o un script.

  • Si el proxy resulta tener una terminal de control (lo iniciaste a mano), también pregunta allí. Ambos caminos compiten; la primera respuesta gana.

  • No responder dentro de approval_timeout_s es una denegación, registrada como refused-timeout. El silencio en una escritura significa no.

  • serve --approve-session autoaprueba cada llamada de clase approve para ese proceso. Imprime una advertencia al inicio, la entrada run-start lo registra, cada entrada afectada dice approved_by: session-flag, y report lo grita. No se puede configurar en el archivo de política; es un acto por invocación de quien inicia el proceso.

El registro de auditoría

{"kind":"call","ts":"2026-08-24T01:14:40.822Z","run_id":"20260824T011440Z-3e1c","id":"20260824T011440Z-0002",
 "name":"fs.write_file","upstream":"fs","tool":"write_file","rule":"exact",
 "args":{"content":"# notes\n[REDACTED:kv-secret]\n","path":"/tmp/mcpclerk-demo-sandbox/notes.md"},
 "decision":"approved","approved_by":"file","held_ms":253.7,"outcome":"ok","is_error":false,
 "latency_ms":7.7,"content_bytes":57,"content_types":["text"],
 "seq":4,"prev_hash":"5c0e…","hash":"b41a…"}
  • decision es uno de allowed, approved, refused-denied, refused-unknown, refused-quota, refused-timeout, refused-by-human.

  • latency_ms es solo tiempo del upstream; el tiempo de pensamiento del humano es held_ms, así que la latencia p95 en report significa la herramienta, no la persona.

  • Las entradas de evento (run-start con el SHA-256 de la política y las banderas, discover con recuentos expuestos/ocultos, run-end con el recuento de entradas) comparten la misma cadena.

  • verify sale con 0 y OK n entries, chain intact o con 1 y FAIL at line N: <what>. Pruébalo: sed -i '' 's/allowed/approved/' examples/audit.demo.jsonl && mcpclerk verify examples/audit.demo.jsonl.

El registro de ejemplo en examples/audit.demo.jsonl es la salida real de la ejecución de la demo. Es seguro publicarlo por construcción: las pruebas de redacción son las que lo demuestran, y la demo escribe una clave API falsa en un archivo precisamente para que el registro pueda mostrar [REDACTED:kv-secret] donde habría estado.

CLI

mcpclerk serve   --policy policy.yaml [--log audit/mcpclerk.jsonl] [--approvals approvals] [--approve-session] [--no-tty]
mcpclerk approve [--approvals approvals] [--once] [--wait 60]
mcpclerk tools   --policy policy.yaml [--json]
mcpclerk verify  audit/mcpclerk.jsonl
mcpclerk report  audit/mcpclerk.jsonl [--json]

Códigos de salida: 0 ok, 1 verificación fallida o política inválida, 2 uso. La política se valida al inicio y cualquier problema (clave desconocida, decisión incorrecta, ${ENV_VAR} sin definir, un upstream stdio sin command) detiene el proxy antes de que sirva nada.

Referencia de política

version: 1
namespace_separator: "."         # "__" for clients that reject dots in tool names
defaults:
  unlisted: deny                 # allow | deny | approve
  approval_timeout_s: 120
  quota: { per_run: null, per_minute: null }
redaction:
  extend: ['(?i)my[-_ ]?internal[-_ ]?token\s*[:=]\s*\S+']   # value regexes, added to the built-ins
  extend_keys: [client_secret]                                # key names, added to the built-ins
  replace_builtin: false                                      # true: only your patterns (warned about)
upstreams:
  <name>:                        # [a-z0-9_-]+ ; becomes the prefix in <name>.<tool>
    transport: stdio | http
    command: ...   args: [...]   env: { KEY: "${FROM_PROXY_ENV}" }   cwd: ...     # stdio
    url: https://...                                                                # http
    tools:
      <tool or glob>: allow | deny | approve
      <tool>: { decision: approve, quota: { per_run: 10, per_minute: 3 }, approval_timeout_s: 60 }

Trabajo previo, y qué es esto en su lugar

Existen pasarelas para MCP y hacen más que esto: mcp-gateway de Lasso Security, mcp-context-forge de IBM y MCP Gateway de Docker aportan registros, autenticación multiinquilino, pipelines de plugins y observabilidad. mcpclerk no reclama novedad. Reclama pequeñez y verificabilidad: un proxy local, legible y de un solo propósito, cuya superficie completa son los cinco controles anteriores y un registro que puedes comprobar. Son unas 1.000 líneas de Python que puedes leer en una tarde, con una dependencia más allá del SDK de MCP (un analizador YAML).

Lo que no hace (todavía)

  • Identidad y políticas por usuario. Se asume un único operador; el registro indica que un humano aprobó, no cuál humano.

  • Una interfaz web, o canales de aprobación remotos (Slack, correo electrónico). mcpclerk approve es una terminal local.

  • Herencia de políticas o plantillas entre upstreams.

  • Recursos y prompts. v0.1 solo proxifica herramientas; resources/list y prompts/list están vacíos.

  • Upstreams HTTP que necesitan cabeceras de solicitud. El transporte HTTP del SDK no acepta ninguna en esta versión; una política que establezca headers falla de forma ruidosa en lugar de enviar silenciosamente nada.

  • Windows: la cola de archivos y mcpclerk approve funcionan; el prompt de terminal en proceso no (no hay /dev/tty). CI ejecuta Windows como mejor esfuerzo.

Modelo de amenazas, honestamente

Lo que un atacante con el asiento del agente intentaría primero es llamar a una herramienta por su nombre que esté oculta de la lista. Eso se rechaza y se registra (refused-unknown o refused-denied). Lo que esto no detiene: una herramienta que está permitida siendo utilizada para algo dañino (la política es tu juicio, mcpclerk la hace cumplir), un aprobador que aprueba sin revisar, y cualquiera con acceso de escritura al archivo de registro reescribiendo toda la cadena desde la primera entrada. La cadena defiende contra ediciones silenciosas, que es la amenaza realista; las firmas o un ancla externa (publicar el hash de cabeza diario en algún lugar que no controles) serían el siguiente paso, y no están en v0.1.

Desarrollo

pip install -e ".[dev]"
pytest -q                                  # 49 tests, all in-process, no network, no subprocesses
python examples/demo_driver.py --approve-via-file   # the demo against the real filesystem server (needs npx)
vhs examples/demo.tape                     # re-record the GIF

Los tests usan el transporte en memoria del SDK de MCP en ambos lados: Client(proxy) → proxy → Client(fake_upstream). El upstream falso (tests/fake_upstream.py) tiene una herramienta secret_sink que devuelve exactamente lo que recibe, que es como la suite demuestra que el upstream ve argumentos sin redactar mientras que el registro no.

Relacionado: toilscan (el mismo instinto de seguridad de escritura aplicado a una herramienta de desarrollo), agent-slots (aislamiento en tiempo de ejecución para agentes paralelos), y agentkeel (el lado del proceso: compuertas y radio de explosión para código escrito por agentes; en progreso).

Para quien sea el próximo propietario: docs/learning/how-it-works.html es el recorrido (el código en orden de llamada, los controles, las respuestas de la entrevista); docs/spec.md es el contrato.

Licencia

MIT.

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

Maintenance

Maintainers
Response time
Release cycle
1Releases (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
    B
    quality
    C
    maintenance
    Security gateway that wraps any MCP server with per-tool policies, approval gates, and optional Ed25519-signed decision receipts. Shadow mode logs every tool call without blocking; enforce mode applies block, rate-limit, and minimum-tier rules. Receipts are independently verifiable offline with no accounts needed.
    5
    469
    9
    MIT
  • A
    license
    Not graded
    quality
    B
    maintenance
    A least-privilege enforcement proxy for MCP servers. It sits between MCP clients and upstream servers, enforcing tool policies, hiding denied tools, requiring human approval for risky actions, and providing a structured audit trail.
    MIT
  • A
    license
    A
    quality
    A
    maintenance
    An MCP proxy that enforces policy on every tool call, blocking or flagging actions before they reach downstream MCP servers.
    1
    249
    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

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.

  • A paid remote MCP for CLI tool MCP, built to return verdicts, receipts, usage logs, and audit-ready

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/hishamalward/mcpclerk'

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