ExtMCP server for mcp guardrails
See the codeAn agentgateway ExtMcp guardrail sidecar for MCP traffic: ONNX PromptGuard-2 prompt-injection scanning, deterministic regex scanning (hidden Unicode / PII / secrets), an Invariant Guardrails-style rule engine (cross-call toxic-flow / loop / rate detection), structural secret/PII redaction, and a tamper-evident JSONL audit log — all behind the agentgateway ExtMcp gRPC contract, fail-closed by default.
Full documentation: https://soulwhisper.github.io/mcp-guardrails/
agentgateway invokes the sidecar twice per MCP exchange via the ExtMcp
gRPC service (CheckRequest before forwarding upstream, CheckResponse
before returning to the agent); each call returns pass, mutated
(redacted payload), or error (deny → JSON-RPC -32001):
sequenceDiagram
autonumber
participant Agent as MCP Agent (LLM)
participant GW as agentgateway
participant Sidecar as ExtMcp Guardrail (sidecar)
participant PG as PromptGuard ONNX
participant INV as Invariant Engine
participant MCP as Upstream MCP Server
Agent->>GW: tools/call (JSON-RPC)
GW->>Sidecar: CheckRequest(McpRequest)
Sidecar->>PG: scan(params, role=TOOL)
Sidecar->>INV: record(tool, args) + evaluate()
PG-->>Sidecar: ScanDecision
INV-->>Sidecar: ScanResult
Sidecar-->>GW: McpRequestResult{pass|mutated|error}
alt allowed / mutated
GW->>MCP: forward JSON-RPC
MCP-->>GW: result
GW->>Sidecar: CheckResponse(McpResponse)
Sidecar->>PG: scan(result, role=ASSISTANT)
PG-->>Sidecar: ScanDecision
Sidecar-->>GW: McpResponseResult{pass|mutated|error}
alt allowed / mutated
GW-->>Agent: result
else error
GW-->>Agent: JSON-RPC -32001
end
else error
GW-->>Agent: JSON-RPC -32001
end
Run the sidecar (image pre-bakes the PromptGuard-2 ONNX model — no HF token, no runtime download):
docker run --rm -p 9001:9001 \
--env-file examples/docker-run.env \
-v $(pwd)/examples/rules.policy:/etc/guardrails/rules.policy:ro \
ghcr.io/soulwhisper/mcp-guardrails:0.4.0
Deploy on Kubernetes (Deployment + Service + ConfigMap rule pack +
AgentgatewayPolicy CRD, Kustomize bundle included):
kubectl apply -k deploy/k8s/
Wire a standalone agentgateway at the sidecar (minimal
examples/agentgateway.standalone.yaml
excerpt):
policies:
mcpGuardrails:
processors:
- kind: remote
host: "localhost:9001"
failureMode: failClosed # sidecar unreachable -> deny, never forward unguarded
methods:
tools/call: full # request + response double gate
tools/list: response # scan tool descriptions (indirect injection)
Step-by-step guide with verification: Getting started.
ToxicFlowRule / LoopRule /
RateLimitRule / AggregateRule over a bounded trace window;
hot-reloadable rule packs via SIGHUP.
Docs[REDACTED:<TYPE>]) forwarded via the proto mutated oneof, including
review-grade payloads.
DocsALLOW_TOOLS / DENY_TOOLS allow/deny lists with
prefix/* wildcards, evaluated before any content scanner.
DocsHUMAN_REVIEW, with pre-egress redaction.
Docs-32001.
Docsguardrail_ctl audit verify), OTel metrics/traces on top.
DocsEvery knob is an environment variable. The most commonly tuned ones:
| Env var | Default | Description |
|---|---|---|
FAILURE_MODE | failClosed | failClosed denies on scanner failure/timeout; failOpen allows with a review flag. |
HUMAN_REVIEW_MODE | pass | pass forwards grey-zone verdicts + audit warning; deny escalates to a hard deny. |
ENABLE_PROMPTGUARD | true | ONNX PromptGuard semantic scanner (regex-only fallback when ML deps absent). |
ENABLE_REGEX_SCANNER | true | Deterministic pattern scanner. Zero ML deps. |
ENABLE_REDACTION | true | Structural secret/PII redaction on allowed payloads. |
ENABLE_AGENT_ALIGNMENT | false | Opt-in second-stage LLM review gate. |
ALLOW_TOOLS / DENY_TOOLS | (unset) | Tool allow/deny lists (prefix/* wildcards); DENY wins. |
MAX_CONTENT_BYTES | 32768 | Head-window scan budget; larger payloads get mid/tail windows + truncated=true. |
SCANNER_TIMEOUT_MS | 500 | Per-scanner deadline; overruns handled per FAILURE_MODE. |
AUDIT_LOG_PATH | stdout | Append-only JSONL audit log (-/unset → stdout, always on). |
Full reference (~40 variables, incl. PromptGuard thresholds, Invariant trace bounds, OTel, health): Configuration.
| Section | Contents |
|---|---|
| Getting started | Run / deploy / wire / verify in four steps. |
| Guardrails | Decision pipeline and every guardrail layer in detail. |
| Configuration | Complete environment-variable reference. |
| Operations | Auditing, metrics, health & shutdown, review webhook. |
| Deployment | Kubernetes manifests, agentgateway wiring, multi-replica, e2e. |
| Security & Compliance | Threat model, failure modes, limitations, data classification. |
| Development | Tests, proto sync, supply chain, release process. |
In-repo deep dives: ARCHITECTURE.md (proto contract and
engine internals), CHANGELOG.md.
make dev # pip install -e ".[dev]" (no ML stack — fast iteration)
make test # 320+ unit tests, ~2s
make lint # ruff
python3 tests/e2e_smoke.py # live-server gRPC smoke (regex-only)
See CONTRIBUTING.md for the dev workflow, proto-stub
sync rule, how to add a scanner or Invariant rule, DCO signoff, and the
release-please process.
Fail-closed posture, threat model and failure modes:
Security model.
To report a vulnerability, do not open a public issue — see
SECURITY.md.
Licensed under the Apache License, Version 2.0 — see LICENSE.
The bundled PromptGuard-2 model weights are under the Llama 4 Community
License — see NOTICE.
ExtMcp contract this sidecar implements.ToxicFlowRule / LoopRule shapes.ExtMCP server for mcp guardrails
See the codeAn agentgateway ExtMcp guardrail sidecar for MCP traffic: ONNX PromptGuard-2 prompt-injection scanning, deterministic regex scanning (hidden Unicode / PII / secrets), an Invariant Guardrails-style rule engine (cross-call toxic-flow / loop / rate detection), structural secret/PII redaction, and a tamper-evident JSONL audit log — all behind the agentgateway ExtMcp gRPC contract, fail-closed by default.
Full documentation: https://soulwhisper.github.io/mcp-guardrails/
agentgateway invokes the sidecar twice per MCP exchange via the ExtMcp
gRPC service (CheckRequest before forwarding upstream, CheckResponse
before returning to the agent); each call returns pass, mutated
(redacted payload), or error (deny → JSON-RPC -32001):
sequenceDiagram
autonumber
participant Agent as MCP Agent (LLM)
participant GW as agentgateway
participant Sidecar as ExtMcp Guardrail (sidecar)
participant PG as PromptGuard ONNX
participant INV as Invariant Engine
participant MCP as Upstream MCP Server
Agent->>GW: tools/call (JSON-RPC)
GW->>Sidecar: CheckRequest(McpRequest)
Sidecar->>PG: scan(params, role=TOOL)
Sidecar->>INV: record(tool, args) + evaluate()
PG-->>Sidecar: ScanDecision
INV-->>Sidecar: ScanResult
Sidecar-->>GW: McpRequestResult{pass|mutated|error}
alt allowed / mutated
GW->>MCP: forward JSON-RPC
MCP-->>GW: result
GW->>Sidecar: CheckResponse(McpResponse)
Sidecar->>PG: scan(result, role=ASSISTANT)
PG-->>Sidecar: ScanDecision
Sidecar-->>GW: McpResponseResult{pass|mutated|error}
alt allowed / mutated
GW-->>Agent: result
else error
GW-->>Agent: JSON-RPC -32001
end
else error
GW-->>Agent: JSON-RPC -32001
end
Run the sidecar (image pre-bakes the PromptGuard-2 ONNX model — no HF token, no runtime download):
docker run --rm -p 9001:9001 \
--env-file examples/docker-run.env \
-v $(pwd)/examples/rules.policy:/etc/guardrails/rules.policy:ro \
ghcr.io/soulwhisper/mcp-guardrails:0.4.0
Deploy on Kubernetes (Deployment + Service + ConfigMap rule pack +
AgentgatewayPolicy CRD, Kustomize bundle included):
kubectl apply -k deploy/k8s/
Wire a standalone agentgateway at the sidecar (minimal
examples/agentgateway.standalone.yaml
excerpt):
policies:
mcpGuardrails:
processors:
- kind: remote
host: "localhost:9001"
failureMode: failClosed # sidecar unreachable -> deny, never forward unguarded
methods:
tools/call: full # request + response double gate
tools/list: response # scan tool descriptions (indirect injection)
Step-by-step guide with verification: Getting started.
ToxicFlowRule / LoopRule /
RateLimitRule / AggregateRule over a bounded trace window;
hot-reloadable rule packs via SIGHUP.
Docs[REDACTED:<TYPE>]) forwarded via the proto mutated oneof, including
review-grade payloads.
DocsALLOW_TOOLS / DENY_TOOLS allow/deny lists with
prefix/* wildcards, evaluated before any content scanner.
DocsHUMAN_REVIEW, with pre-egress redaction.
Docs-32001.
Docsguardrail_ctl audit verify), OTel metrics/traces on top.
DocsEvery knob is an environment variable. The most commonly tuned ones:
| Env var | Default | Description |
|---|---|---|
FAILURE_MODE | failClosed | failClosed denies on scanner failure/timeout; failOpen allows with a review flag. |
HUMAN_REVIEW_MODE | pass | pass forwards grey-zone verdicts + audit warning; deny escalates to a hard deny. |
ENABLE_PROMPTGUARD | true | ONNX PromptGuard semantic scanner (regex-only fallback when ML deps absent). |
ENABLE_REGEX_SCANNER | true | Deterministic pattern scanner. Zero ML deps. |
ENABLE_REDACTION | true | Structural secret/PII redaction on allowed payloads. |
ENABLE_AGENT_ALIGNMENT | false | Opt-in second-stage LLM review gate. |
ALLOW_TOOLS / DENY_TOOLS | (unset) | Tool allow/deny lists (prefix/* wildcards); DENY wins. |
MAX_CONTENT_BYTES | 32768 | Head-window scan budget; larger payloads get mid/tail windows + truncated=true. |
SCANNER_TIMEOUT_MS | 500 | Per-scanner deadline; overruns handled per FAILURE_MODE. |
AUDIT_LOG_PATH | stdout | Append-only JSONL audit log (-/unset → stdout, always on). |
Full reference (~40 variables, incl. PromptGuard thresholds, Invariant trace bounds, OTel, health): Configuration.
| Section | Contents |
|---|---|
| Getting started | Run / deploy / wire / verify in four steps. |
| Guardrails | Decision pipeline and every guardrail layer in detail. |
| Configuration | Complete environment-variable reference. |
| Operations | Auditing, metrics, health & shutdown, review webhook. |
| Deployment | Kubernetes manifests, agentgateway wiring, multi-replica, e2e. |
| Security & Compliance | Threat model, failure modes, limitations, data classification. |
| Development | Tests, proto sync, supply chain, release process. |
In-repo deep dives: ARCHITECTURE.md (proto contract and
engine internals), CHANGELOG.md.
make dev # pip install -e ".[dev]" (no ML stack — fast iteration)
make test # 320+ unit tests, ~2s
make lint # ruff
python3 tests/e2e_smoke.py # live-server gRPC smoke (regex-only)
See CONTRIBUTING.md for the dev workflow, proto-stub
sync rule, how to add a scanner or Invariant rule, DCO signoff, and the
release-please process.
Fail-closed posture, threat model and failure modes:
Security model.
To report a vulnerability, do not open a public issue — see
SECURITY.md.
Licensed under the Apache License, Version 2.0 — see LICENSE.
The bundled PromptGuard-2 model weights are under the Llama 4 Community
License — see NOTICE.
ExtMcp contract this sidecar implements.ToxicFlowRule / LoopRule shapes.