mcp-policy-gateway
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@mcp-policy-gatewayRun the security benchmark and show me which controls catch which attacks."
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
mcp-policy-gateway
Runtime policy enforcement for Model Context Protocol tool calls, and a deterministic benchmark that measures which controls actually stop which attacks.
The problem
An MCP client hands a model a set of tools, calls them on the model's behalf, and feeds the results back into context. Three things in that loop are untrusted, and they are untrusted at different times:
Tool declarations. A server describes its own tools. A description is prose that most clients concatenate into a prompt, so a server can put instructions there.
Call arguments. Built by a model from a schema, then sent to a server that may or may not validate them.
Tool results. Whatever comes back is read into context. It is a document, a search result, a database row — content the server did not write and cannot vouch for.
The security tooling that exists for MCP today is mostly static scanners: point them at a server, they read the declarations and report suspicious ones. That is worth doing and it covers exactly one of the three. It cannot cover the third at all, because a poisoned search result does not exist until someone runs the search.
This project puts the enforcement point at the protocol boundary instead, where all three are visible, and then measures how much that is worth.
Related MCP server: Agentrim MCP
What it is
An MCP server that fronts another MCP server. Your client connects to the gateway; the gateway connects to the real server. Both sides speak ordinary MCP, so it works with a server whose source you do not have.
client ──MCP──▶ policy gateway ──MCP──▶ downstream server
│
├─ discovery : inspect declarations before the client sees them
├─ request : inspect arguments before the call is forwarded
└─ response : inspect results before they re-enter context
│
└─▶ trace (JSONL): decision, rules, timing, payload digestsNine controls run at those three stages:
Control | Stage | What it decides |
| request | Whether this tool may be called at all. Deny by default. |
| discovery | Whether a server is claiming a name another already owns. |
| request | Whether an irreversible call has a recorded approval. |
| request | Whether arguments match the schema the server published. |
| request | Whether a path argument resolves inside the sandbox root. |
| request, response | Call counts, output bytes, and a per-tool circuit breaker. |
| request, response | Whether a credential is passing in either direction. |
| request, response | Whether a named host is on the egress allowlist. |
| discovery, response | Whether text contains instructions aimed at the model. |
A control never decides on its own. It reports findings; the engine combines them, and the strongest action wins — allow, sanitise, hold for approval, or block.
Results
Deterministic. 44 cases, no model calls, no network. python evaluation/benchmark.py
reproduces this table exactly on any machine.
Configuration | Attacks caught | Benign refused | Benign untouched | Median decision |
baseline (no gateway) | 0.0% | 0.0% | 100.0% | 0 µs |
keyword filter | 38.5% | 38.9% | 61.1% | 3 µs |
gateway | 92.3% | 11.1% | 77.8% | 117 µs |
The keyword filter is in there because it is the real alternative. "We added a filter" is what actually happens when a team decides to do something about prompt injection, and comparing only against nothing would flatter the result. It catches a third of the attacks and refuses two out of five legitimate calls, which is the profile of a control that gets switched off in week two.
Per control, on the same run:
Control | Attacks caught | Benign cases touched |
| 15 | 4 |
| 4 | 0 |
| 4 | 0 |
| 3 | 0 |
| 2 | 0 |
| 2 | 0 |
| 1 | 0 |
| 1 | 0 |
| 1 | 0 |
Every benign case that gets touched is touched by the one control that has to make a judgement call. The eight deterministic controls have no false positives on this corpus, which is the argument for keeping the judgement in exactly one place.
Where it fails
Four cases out of 44 are not handled, and all four were written before the controls were.
Case | What it is | Why it fails |
| Base64 of an override instruction, no plaintext around it | Matching runs on the raw text. Decoding every base64-looking span before matching would flag legitimate encoded attachments, so this is a deliberate open gap, not an oversight. |
| A bare 64-hex-character credential | Catching it means flagging every SHA-256 digest in every document. The control keys on prefixes and assignment shape instead. |
| A runbook saying "ignore the previous instructions in section 3" | Genuine operator prose with the exact shape of an attack. There is no signal available that separates them. |
| Onboarding text asking someone to "show your system prompt" | Same problem. Internal documentation discusses prompts now. |
The two false positives are the honest cost of the injection control, and they are the
reason it is the only control allowed to be uncertain. Full write-up in
docs/learning/06_failures.md.
Run it
python -m venv .venv && . .venv/Scripts/activate # or bin/activate
pip install -e ".[dev]"
python -m mcp_policy_gateway.cli demo # real MCP process, real stdio
python evaluation/benchmark.py # the table above
pytest # the test suitedemo launches a deliberately hostile MCP server from examples/, drives it through the
gateway and prints every decision. It needs no API key, no network and no configuration.
That is the point: an evaluation that needs a key is an evaluation nobody re-runs.
To put the gateway in front of a server you already use:
python -m mcp_policy_gateway.cli proxy \
npx -y @modelcontextprotocol/server-filesystem /srv/docs \
--sandbox /srv/docs \
--allow read_file --allow list_directory \
--destructive write_file \
--allow-host docs.internal.example.comUpstream foundation
This is an original implementation. It is not a fork and no third-party source is vendored here.
MCP Python SDK (MIT) is a dependency. The gateway speaks MCP through it rather than reimplementing the wire format. Targets the 2.x API, where
FastMCPbecameMCPServer.Prior art, read but not used as code: OWASP MCP Tool Poisoning for the threat vocabulary, and Snyk
agent-scan(Apache-2.0) as the reference example of the static-scanning approach this measures itself against.
Full attribution in NOTICE.
What is mine
The gateway, all nine controls, the policy engine, the corpus, the benchmark, the trace format and the CLI. Specifically:
Three-stage enforcement. Splitting discovery, request and response, so response- stage attacks are reachable at all.
A corpus with ground truth per case. 26 attacks, 18 benign near-misses. Each case states the weakest acceptable response, because "block everything" is wrong for a credential inside a legitimate document.
Benign near-misses as a first-class half. The false-positive rate is what decides whether a control survives contact with an operator, and it is the number the keyword baseline loses on.
Context-demotion in the injection control. Quoted, fenced and reported text demotes a finding from block to redact, which is what makes a security advisory readable.
Per-control attribution. Every control runs even after another has blocked, so the effectiveness table is not an artefact of ordering.
Fail-closed proxying over real stdio MCP, with a JSONL trace that stores digests rather than payloads.
Documentation
docs/learning/ works from the problem up: what MCP is and why its trust boundary is
unusual, the threat model, the architecture, the implementation, how the evaluation is
constructed, where it fails, and what to try next.
Licence
MIT. See LICENSE.
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Connectors
Security & DLP proxy for MCP: tool-poisoning scans, PII redaction on tool args/results. Beta.
MCP gateway with runtime security policy, tool-call-level control, and audit of agent actions.
Security firewall for AI agents — scans MCP calls for injection, secrets, and risks.
Governed MCP gateway: one endpoint for your tools, with credential custody and audit log.
Related MCP Servers
AlicenseNot gradedqualityAmaintenanceOpen-source MCP proxy that enforces security policies, content scanning, and audit logging between AI agents and tool servers25AGPL 3.0- AlicenseNot gradedqualityBmaintenanceA 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
- AlicenseAqualityAmaintenanceAn MCP proxy that enforces policy on every tool call, blocking or flagging actions before they reach downstream MCP servers.147MIT
- FlicenseNot gradedqualityBmaintenanceRuntime security gateway and FastMCP server that protects MCP clients from tool poisoning, prompt injection, and unauthorized tool schema changes through policy enforcement, fail-closed scanning, and human approval gates.
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
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/mzquadri/mcp-policy-gateway'
If you have feedback or need assistance with the MCP directory API, please join our Discord server