Skip to main content
Glama
hishamalward

mcpclerk

by hishamalward

mcpclerk

ci python license

MCPサーバー用のガバナンスプロキシ: 任意のMCPサーバーの前に立ち、ツールごとの許可リストを強制し、書き込みクラスのツールを人間の承認のために保留し、ツールごとのクォータを適用し、秘密情報に見える引数をマスクし、すべての呼び出しのハッシュチェーン監査ログを書き込みます。

MCPサーバー上のAIエージェントは、公開されている任意のツールを、好きなだけ、任意の引数で呼び出すことができ、誰も監査できる形で何をしたかを記録するものはありません。エンタープライズにおいて問題となるのは「エージェントが仕事をできるか」ではなく、何を許可されているか、危険な部分を誰が承認したか、そして実際に何をしたかです。

mcpclerkはこれら3つの問いにコードで答えます。それ自体がMCPサーバーであり、エージェントはそれに接続し、mcpclerkは実際のサーバーに接続して、それらのツールをupstream.toolとして再公開します。すべての呼び出しは、許可リスト、クォータ、マスキング、承認、転送、ログという1つのパイプラインを通過します。リストにないツールは拒否されます。書き込みクラスのツールは、人間がyと答えるのを待ちます。拒否は読みやすいエラーとして返されます。ログは追記専用のJSON Linesで、各エントリは前のエントリとハッシュチェーンで結ばれているため、どこかを編集するとチェーンが壊れます。

デモは公式のファイルシステムサーバーをラップしています: 読み取りは通過し、書き込みは保留されて承認され、移動は拒否され、1分以内の4回目の検索はクォータで拒否され、ログは検証されます。49のテストが、偽のアップストリームに対して各コントロールを証明しており、アップストリームが常にマスクされていない引数を受け取ることも含まれます。

demo

Install

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: mcp-policy-gateway

Five minutes

  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. アップストリームが提供するものと、ポリシーがそれに対して何をするかを確認します。サーバー自身の注釈があなたの決定の隣に表示され、破壊的なツールを許可してしまったことに気づくことができます:

    $ 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. 2番目のターミナルで承認を待ちます: mcpclerk approve。エージェントがfs.write_fileを呼び出すと、シークレットがすでにマスクされた呼び出しが表示され、yまたはnで答えます。

  5. その後: mcpclerk verify audit/mcpclerk.jsonl と mcpclerk report audit/mcpclerk.jsonl。

The five controls

コントロール

機能

防止するもの

防止できないもの

証明方法

許可リスト

ツールごとにallow / deny / approve。最初に完全一致名、次に最長のグロブ、最後にdefaults.unlisted(拒否)。拒否されたツールとリストにないツールはエージェントにすら表示されません。

誰もレビューしていないツールをエージェントが使うこと。

ポリシー自体の誤った決定。mcpclerk toolsは、あなたの決定の隣にアップストリームの読み取り専用/破壊的ヒントを表示して、それを難しくします。

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ブロック、URLユーザー情報、password=...など)は一致部分を置き換えます。ログに記録され、人間に表示されるものに適用されます。アップストリームは元の引数を受け取ります。

シークレットがログや承認者の画面に表示されること。

リストにない形のシークレット。独自の形のためにredaction.extend / extend_keysを拡張してください。

test_redact.py、test_pipeline.py::test_upstream_receives_unredacted_args

監査ログ

呼び出しごとに1つのJSON Linesエントリ。タイムスタンプ、アップストリーム、ツール、マスクされた引数、決定、承認者、結果、レイテンシ、そしてhash = sha256(prev_hash + canonical(entry))。verifyはチェーンを再計算し、reportはそれを要約します。

事後のエントリの静かな編集、削除、並べ替え。完了した実行の切り詰め(run-endが件数を保持)。

ジェネシスからチェーン全体を書き換える攻撃者(これはチェーンであり署名ではありません。下記参照)。途中で強制終了された実行の切り詰め。

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

結果はログに記録されず、サイズとコンテンツタイプのみが記録されます。ログは決定の監査であり、データのコピーではありません。結果を保存すると、シークレットが漏れる2番目の場所になります。

How a call moves

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と1行の理由を伴う通常のツール結果としてエージェントに返されます: mcpclerk: refused-quota fs.search_files: 3/min exhausted; retry after 60s。

Approval, in detail

プロキシは通常、エージェントのMCPクライアントによって起動され、MCP SDKはstdioサーバーを新しいセッションで起動するため、プロキシは通常独自のターミナルを持ちません。そのため、メカニズムはファイルキューであり、ターミナルプロンプトはそのクライアントです:

  • 保留されたすべての呼び出しについて、マスクされた引数、requested_at、expires_at、"approved": nullを含むapprovals/<id>.jsonが書き込まれます。

  • mcpclerk approve(同じマシンの任意のターミナルで)は保留中のリクエストを表示し、あなたの回答を書き込みます。--onceは1つに答えて終了します。それがないと、監視を続けます。

  • ファイルを手動で"approved": trueに編集することも機能します。これはヘッドレスジョブやスクリプトが行うことです。

  • プロキシが制御端末を持っている場合(手動で起動した場合)、そこでもプロンプトが表示されます。両方の経路が競合し、最初の回答が優先されます。

  • approval_timeout_s以内に回答がない場合は拒否となり、refused-timeoutとして記録されます。書き込みに対する沈黙は「いいえ」を意味します。

  • serve --approve-sessionは、そのプロセスのすべてのapproveクラスの呼び出しを自動承認します。起動時に警告を出力し、run-startエントリがそれを記録し、影響を受けるすべてのエントリにapproved_by: session-flagと表示され、reportがそれについて強調表示します。ポリシーファイルで設定することはできません。プロセスを起動する人による呼び出しごとの行為です。

The audit log

{"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はアップストリームの時間のみです。人間の思考時間はheld_msであり、reportのp95レイテンシはツールを意味し、人間を意味しません。

  • イベントエントリ(ポリシーのSHA-256とフラグを持つrun-start、公開/非公開のカウントを持つdiscover、エントリ数を持つrun-end)は同じチェーンを共有します。

  • verifyはOK n entries, chain intactで終了コード0、またはFAIL at line N: <what>で1を返します。試してみてください: 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はOK、1は検証失敗またはポリシー無効、2は使用方法の誤り。ポリシーは起動時に検証され、問題(不明なキー、不正な決定、未設定の${ENV_VAR}、commandのないstdioアップストリーム)があると、プロキシは何も提供する前に停止します。

Policy reference

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 }

Prior art, and what this is instead

MCP用のゲートウェイは存在し、これ以上のことを行います: Lasso Securityのmcp-gateway、IBMのmcp-context-forge、DockerのMCP Gatewayは、レジストリ、マルチテナント認証、プラグインパイプライン、可観測性を提供します。mcpclerkは新規性を主張しません。それは小ささと検証可能性を主張します: 単一目的で読みやすく、ローカルなプロキシであり、その全体の表面は上記の5つのコントロールと、確認できるログです。MCP SDK以外に1つの依存関係(YAMLパーサー)を持つ、約1,000行のPythonで、午後で読めます。

What it does not do (yet)

  • アイデンティティとユーザー別ポリシー。オペレーターは1人と想定される。ログは「人間が承認した」ことだけを記録し、「どの人間が」は記録しない。

  • Web UI、またはリモート承認チャネル(Slack、メール)。mcpclerk approveはローカルターミナルである。

  • アップストリーム間でのポリシーの継承またはテンプレート化。

  • リソースとプロンプト。v0.1はツールのみをプロキシする。resources/listとprompts/listは空である。

  • リクエストヘッダーを必要とするHTTPアップストリーム。このバージョンのSDKのHTTPトランスポートはヘッダーを一切受け取らない。headersを設定するポリシーは、何も送信せずに黙って通すのではなく、明示的に失敗する。

  • Windows:ファイルキューとmcpclerk approveは動作する。インプロセスターミナルプロンプトは動作しない(/dev/ttyがない)。CIはWindowsをベストエフォートで実行する。

脅威モデル(正直に言うと)

エージェントの座を奪った攻撃者が最初に試みるのは、リストから隠されたツールを名前で呼び出すことだ。これは拒否され、ログに記録される(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.

Related MCP Connectors

Related MCP Servers

  • 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
    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
    A
    quality
    C
    maintenance
    Provides a security governance layer for AI agents to safely access upstream MCP servers, enforcing tool-level RBAC, parameter constraints, authentication via static tokens or OIDC, and tamper-evident audit logging.
    2
    1
    Apache 2.0
  • F
    license
    Not graded
    quality
    B
    maintenance
    Enforces default-deny policies, budgets, and tamper-evident audit logging for MCP tool calls before they reach upstream servers.
    -