Skip to main content
Glama
hishamalward

mcpclerk

by hishamalward

mcpclerk

ci python license

Управляющий прокси для MCP-серверов: встаёт перед любым MCP-сервером, применяет разрешённый список инструментов, удерживает инструменты класса записи для одобрения человеком, применяет квоты на каждый инструмент, редактирует аргументы, похожие на секреты, и пишет хэш-цепочный журнал аудита каждого вызова.

ИИ-агент на MCP-серверах может вызывать любой инструмент, который они предоставляют, сколько угодно раз, с любыми аргументами, и ничто не фиксирует, что он делал, в форме, которую можно проверить. В корпоративной среде вопрос не в том, «может ли агент выполнить работу», а в том, что ему разрешено делать, кто одобрил опасные части и что он фактически сделал?

mcpclerk отвечает на эти три вопроса кодом. Он сам является MCP-сервером: агент подключается к нему, он подключается к реальным серверам и повторно предоставляет их инструменты как upstream.tool. Каждый вызов проходит через один конвейер: разрешённый список, квота, редактирование, одобрение, пересылка, журнал. Неперечисленный инструмент отклоняется. Инструмент класса записи ждёт, пока человек ответит y. Отказы возвращаются как читаемые ошибки. Журнал — это append-only JSON Lines, каждая запись хэшируется с предыдущей, так что любое изменение где-либо разрывает цепочку.

Демонстрация оборачивает официальный файловый сервер: чтение проходит, запись удерживается и одобряется, перемещение отклоняется, четвёртый поиск в минуту отклоняется по квоте, и журнал проверяется. 49 тестов доказывают каждый контроль против фейкового upstream, включая то, что upstream всегда получает нередактированные аргументы.

demo

Установка

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

Из исходников: git clone https://github.com/hishamalward/mcpclerk && cd mcpclerk && pip install -e ".[dev]" && pytest.

Related MCP server: Agentrim MCP

Пять минут

  1. Напишите политику. Вот та, что из демонстрации (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. Посмотрите, что предлагает upstream и что ваша политика с ним делает. Собственные аннотации сервера показаны рядом с вашим решением, что позволяет заметить, что вы разрешили разрушительный инструмент:

    $ 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. Зарегистрируйте прокси там, где ваш агент ищет MCP-серверы. Для Claude Code — examples/.mcp.json:

    { "mcpServers": { "fs-governed": {
        "command": "mcpclerk",
        "args": ["serve", "--policy", "examples/policy.filesystem.yaml"] } } }
  4. Во втором терминале ждите одобрений: mcpclerk approve. Когда агент вызывает fs.write_file, вы видите вызов с уже замаскированными секретами и отвечаете y или n.

  5. После: mcpclerk verify audit/mcpclerk.jsonl и mcpclerk report audit/mcpclerk.jsonl.

Пять контролей

Контроль

Что делает

Что предотвращает

Что не может предотвратить

Доказано

Разрешённый список

allow / deny / approve для каждого инструмента: сначала точное имя, затем самый длинный glob, затем defaults.unlisted (deny). Отклонённые и неперечисленные инструменты даже не показываются агенту.

Использование агентом инструмента, который никто не проверял.

Плохое решение в самой политике. mcpclerk tools показывает подсказки upstream о read-only / разрушительных инструментах рядом с вашим решением, чтобы это было сложнее.

test_policy.py, test_pipeline.py::test_denied_hidden_tool_called_by_name_is_refused

Одобрение

Вызовы класса approve удерживаются. Запрос записывается в approvals/<id>.json с редактированными аргументами; человек отвечает с помощью mcpclerk approve (или редактируя файл, или в терминальном приглашении, если у прокси оно есть). Тайм-аут — это отказ.

Несанкционированная запись.

Человек, который одобряет, не читая. --approve-session существует для такого человека и записывается в каждой затронутой записи.

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

Квоты

per_run и per_minute (скользящее окно) для каждого инструмента. Превышение квоты отклоняется с указанием лимита и секунд до освобождения окна. Отклонённые вызовы не расходуют квоту; одобренные, но отклонённые человеком — расходуют.

Бесконечные циклы; дешёвый инструмент, становящийся дорогим по объёму.

Распределение цикла по многим инструментам или по перезапускам прокси (per_run сбрасывается вместе с процессом).

test_quota.py, test_pipeline.py::test_quota_exhaustion

Редактирование

Правила ключей (api_key, token, password, authorization, ...) заменяют всё значение; правила значений (bearer-заголовки, токены sk-/AKIA/ghp_/xox, JWT, PEM-блоки, userinfo в URL, password=...) заменяют совпадение. Применяется к тому, что записывается в журнал и показывается человеку. Upstream получает исходные аргументы.

Попадание секретов в журнал или на экран утверждающего.

Секрет, не похожий ни на что из списка. Расширьте redaction.extend / extend_keys для своих форм.

test_redact.py, test_pipeline.py::test_upstream_receives_unredacted_args

Журнал аудита

Одна запись JSON Lines на вызов с временной меткой, upstream, инструментом, редактированными аргументами, решением, кто одобрил, результатом, задержкой и hash = sha256(prev_hash + canonical(entry)). verify пересчитывает цепочку; report суммирует её.

Тихое редактирование, удаление или переупорядочивание записей после факта; усечение завершённого запуска (run-end содержит счётчик).

Атакующий, который переписывает всю цепочку с начала (это цепочка, а не подпись; см. ниже). Усечение запуска, убитого на полпути.

test_audit.py (edit, delete, reorder, truncate)

Результаты не записываются, только их размер и типы содержимого. Журнал — это аудит решений, а не копия данных; хранение результатов сделало бы его вторым местом утечки секретов.

Как движется вызов

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]

Каждый путь, включая каждый отказ, заканчивается записью в журнале. Отказы возвращаются агенту как обычный результат инструмента с is_error: true и однострочной причиной: mcpclerk: refused-quota fs.search_files: 3/min exhausted; retry after 60s.

Одобрение, подробно

Прокси обычно запускается MCP-клиентом агента, а MCP SDK запускает stdio-серверы в новой сессии, поэтому у прокси обычно нет собственного терминала. Именно поэтому механизм — это файловая очередь, а терминальное приглашение — её клиент:

  • approvals/<id>.json записывается для каждого удержанного вызова, с редактированными аргументами, requested_at, expires_at и "approved": null.

  • mcpclerk approve (в любом терминале, на той же машине) показывает ожидающие запросы и записывает ваш ответ. --once отвечает на один и выходит; без него продолжает следить.

  • Ручное редактирование файла до "approved": true тоже работает — так делают headless-задачи или скрипты.

  • Если у прокси всё же есть управляющий терминал (вы запустили его вручную), он также запрашивает там. Оба пути соревнуются; первый ответ побеждает.

  • Отсутствие ответа в течение approval_timeout_s — это отказ, записываемый как refused-timeout. Тишина на запись означает «нет».

  • serve --approve-session автоматически одобряет все вызовы класса approve для этого процесса. Он печатает предупреждение при запуске, запись run-start фиксирует это, каждая затронутая запись говорит approved_by: session-flag, а report кричит об этом. Это нельзя задать в файле политики; это акт при каждом вызове того, кто запускает процесс.

Журнал аудита

{"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 — одно из allowed, approved, refused-denied, refused-unknown, refused-quota, refused-timeout, refused-by-human.

  • latency_ms — только время upstream; время размышлений человека — это held_ms, так что p95 задержки в report означает инструмент, а не человека.

  • Событийные записи (run-start с SHA-256 политики и флагами, discover со счётчиками exposed/hidden, run-end со счётчиком записей) используют ту же цепочку.

  • verify завершается с кодом 0 и OK n entries, chain intact или с кодом 1 и FAIL at line N: <what>. Попробуйте: sed -i '' 's/allowed/approved/' examples/audit.demo.jsonl && mcpclerk verify examples/audit.demo.jsonl.

Пример журнала в examples/audit.demo.jsonl — это реальный вывод демонстрационного запуска. Его безопасно публиковать по построению: тесты редактирования это доказывают, а демонстрация записывает фейковый API-ключ в файл именно для того, чтобы журнал мог показать [REDACTED:kv-secret] там, где он был бы.

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]

Коды выхода: 0 — ок, 1 — verify не прошёл или политика недействительна, 2 — использование. Политика проверяется при запуске, и любая проблема (неизвестный ключ, плохое решение, неустановленный ${ENV_VAR}, stdio-upstream без command) останавливает прокси до того, как он начнёт обслуживать что-либо.

Справочник по политике

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 }

Предшественники и чем это является вместо них

Шлюзы для MCP существуют и делают больше: Lasso Security's mcp-gateway, IBM's mcp-context-forge и Docker's MCP Gateway приносят реестры, мультитенантную аутентификацию, плагинные конвейеры и наблюдаемость. mcpclerk не претендует на новизну. Он претендует на малость и проверяемость: одноцелевой, читаемый, локальный прокси, вся поверхность которого — пять контролей выше и журнал, который можно проверить. Это около 1000 строк Python, которые можно прочитать за вечер, с одной зависимостью помимо MCP SDK (парсер YAML).

Что он (пока) не делает

  • Идентичность и политики для каждого пользователя. Предполагается один оператор; журнал фиксирует что человек одобрил, а не какой человек.

  • Веб-интерфейс или удалённые каналы одобрения (Slack, email). mcpclerk approve — это локальный терминал.

  • Наследование политик или шаблонизация для вышестоящих систем.

  • Ресурсы и промпты. v0.1 проксирует только инструменты; resources/list и prompts/list пусты.

  • HTTP-апстримы, которым нужны заголовки запросов. HTTP-транспорт SDK в этой версии не принимает их; политика, устанавливающая headers, громко завершается с ошибкой, а не молча ничего не отправляет.

  • Windows: файловая очередь и mcpclerk approve работают; внутрипроцессный терминальный промпт — нет (нет /dev/tty). CI запускает Windows по принципу best-effort.

Модель угроз, честно

Что атакующий, находящийся на месте агента, попробует в первую очередь — вызвать инструмент по имени, скрытый из списка. Это отклоняется и записывается в журнал (refused-unknown или refused-denied). Что это не останавливает: инструмент, который разрешён, используемый во вред (политика — ваше суждение, mcpclerk её обеспечивает), одобряющий, который штампует без проверки, и любой, у кого есть доступ на запись к файлу журнала, переписывающий всю цепочку с первой записи. Цепочка защищает от тихих правок, что является реалистичной угрозой; подписи или внешний якорь (публикация ежедневного хэша головы там, где вы не контролируете) были бы следующим шагом, но их нет в v0.1.

Разработка

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

Тесты используют внутрипроцессный транспорт MCP SDK с обеих сторон: Client(proxy) → proxy → Client(fake_upstream). Фейковый апстрим (tests/fake_upstream.py) имеет инструмент secret_sink, который возвращает ровно то, что получил, — так набор тестов доказывает, что апстрим видит незамаскированные аргументы, а журнал — нет.

Связанное: toilscan (тот же инстинкт безопасности записи, применённый к инструменту разработчика), agent-slots (изоляция времени выполнения для параллельных агентов) и agentkeel (процессная сторона: шлюзы и радиус поражения для кода, написанного агентом; в разработке).

Для того, кто будет владеть этим дальше: docs/learning/how-it-works.html — это экскурсия (код в порядке вызовов, элементы управления, ответы на интервью); docs/spec.md — контракт.

Лицензия

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