Policy cookbook

Worked examples for common mcpgw policy configurations. All examples are complete policy: blocks suitable for pasting into mcpgw.yaml.

Rules are evaluated in YAML order. The first matching rule wins; subsequent rules are not evaluated.


Example 1 — Deny dangerous tools by name

Block specific tool names entirely. The request receives HTTP 403 and JSON-RPC error -32001 policy_denied.

policy:
  rules:
    - id: deny-shell
      action: deny
      when: { tool_name: "shell_exec" }
    - id: deny-system
      action: deny
      when: { tool_name: "system_command" }

Example 2 — Redact bearer tokens in any tool’s arguments

Strip Bearer <token> patterns from all tool call payloads before forwarding to upstream. The original value is replaced inline; the upstream never sees the token.

policy:
  rules:
    - id: redact-bearer
      action: redact
      when: { tool_name: "*" }
      redact:
        # v1: regex applies globally over the raw request body. jsonpath
        # scoping is reserved for a future release — setting it now is rejected at startup.
        - regex: 'Bearer [A-Za-z0-9._-]+'
          replacement: "[REDACTED]"

Example 3 — Redact email addresses

Replace email addresses across all tool arguments. Useful when tools accept user-supplied input that may contain PII.

policy:
  rules:
    - id: redact-emails
      action: redact
      when: { tool_name: "*" }
      redact:
        # v1: global regex over the raw body; jsonpath reserved for a future release.
        - regex: '[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}'
          replacement: "[EMAIL]"

Example 4 — Rate-limit a write tool

Allow at most 10 calls per second to fs_write, with a burst allowance of 20. Requests that exceed the limit receive HTTP 429 and JSON-RPC error -32003 rate_limited. Rate-limit buckets are per-(rule, session): two sessions hitting the same rule have independent buckets.

policy:
  rules:
    - id: rl-fs-write
      action: rate_limit
      when: { tool_name: "fs_write" }
      tokens_per_second: 10
      burst: 20

Example 5 — Allow only a specific tool prefix

Use default_action: deny with explicit allow rules to run in allowlist mode. Any tool call that does not match one of the allow rules is denied with rule_id: "default_deny".

policy:
  default_action: deny
  rules:
    - id: allow-fs-tools
      action: allow
      when: { tool_glob: "fs_*" }

For a fixed list of read-only tools, use tool_name_in:

policy:
  default_action: deny
  rules:
    - id: allow-git-readonly
      action: allow
      when: { tool_name_in: [git_log, git_diff, git_show] }

Caveats

Redact is regex-over-body. The regex matches content anywhere in the raw JSON-RPC request body and is replaced regardless of which JSON path the matching text lives at. The jsonpath field is rejected at startup to prevent operators from believing their redaction is path-scoped when it is not. A future release will introduce true JSONPath scoping and re-accept the field; until then, prefer regexes anchored to identifiable prefixes (Bearer , sk-, AKIA).

Deny returns -32001. Rules with action: deny produce HTTP 403 and JSON-RPC error code -32001 with message policy_denied. The audit.jsonl entry will contain "decision":"deny" and the rule_id that fired.

Rate-limit returns -32003. Rules with action: rate_limit that block a request produce HTTP 429 and JSON-RPC error code -32003 with message rate_limited.

Rate-limit buckets are per-(rule, session). Two sessions calling the same rate-limited tool have independent token buckets. A burst from one session does not consume the other’s allowance.

Rule ordering matters. The first matching rule wins. Place deny rules before redact rules if you want to hard-block certain tools rather than pass them through with redacted content.