Skip to main content

MCP Zero-Trust Gateway

A security gateway between an LLM agent and the MCP (Model Context Protocol) tool servers it uses, built on one principle: an MCP server's manifest is attacker-controlled and therefore unverifiable, but its syscalls are not.

Every tool is inspected before admission, profiled under strace in a hardened container to see what it actually does, checked against what it claimed, and then confined per call at the kernel level. It runs entirely on your machine — the trust store is a local SQLite file and no telemetry leaves the host.


The problem

An agent discovers tools by reading each server's self-description (tools/list), then calls them with its own privileges. It treats that description as true.

Three things follow, and each is a real attack:

  • A description is instruction-adjacent text inside the model's context, read on every turn. Hide instructions there and the agent obeys them.
  • A tool response re-enters the agent's reasoning as a trusted observation. Whoever controls retrieved content controls the agent's next move.
  • Tools are installed like packages but run with ambient host authority — your files, your environment variables, your network.

What this does about it

Declare → verify → confine. A tool publishes a capability declaration; kernel syscall profiling verifies it before admission; per-tool resource grants enforce it at runtime. An attacker must either declare the attack — and be confined to it — or be caught exceeding the declaration.

Comparable systems each hold one half of that loop: sandlock.mcp confines against declarations it never checks, SandScope observes flows it cannot stop, mcp-sec-audit inspects source it never runs, ClawGuard authorises calls it cannot contain.


Results

Measured against real containers, real MCP servers and real strace. Every figure is traceable to a committed row — eval_results.csv, evidence/.

Corpus Result n
Runtime DSR (Appsecco + rug-pull + evasion) 84.6% (11/13) 13
↳ Phase 1 alone (Appsecco) 81.8% (9/11) 11
Containment (purpose-built corpus) 87.5% (7/8) 8
Declaration verification 100% / 100% (detection / fidelity) 5
MCPTox (detection only — see caveat) 88.0% (425/483) 483
MSB (detection only, small n) 100% (5/5) 5
False positives 25–50% (varies by run) 4
Inconclusive 0 —
Unit tests 395 passing, 29 skipped —

Reference points: ClawGuard 89.50% MCPTox / 94.21% MSB; mcp-sec-audit 74.7% MCPTox and 0% on JavaScript servers.

Read the caveats before quoting any of this. MCPTox is not measured the way ClawGuard measures it — we start no agent and no container, and 346 of our 425 defences there are regex over manifest text. The false-positive rate is not stable. Two of the three runtime phases are n = 1. All of it is spelled out in MD_files/Paper/.

Determinism: across five consecutive runs from a wiped trust store, 503 of 503 scored rows produced identical verdicts. The one varying metric is the false-positive rate, and the cause is a tool whose own probe fires inconsistently — not the gateway.


Architecture

Two phases sharing one SQLite trust store; they never call each other.

Onboarding — POST /register, once per server

  1. L1 static scan — 43 rules over names, descriptions and schemas. Three-valued: an attack denies, something merely suspicious quarantines.
  2. L2 live profiling — the server runs under strace -f -yy -ttt inside docker run --cap-drop ALL --read-only, driven over real MCP. Up to 8 tools are exercised with arguments synthesised from each tool's own inputSchema. strace is injected automatically if the image lacks it, so Node, Python and Go servers all profile without special-casing.
  3. Capability mapping — syscalls → {network, filesystem, subprocess, env}, split at the MCP initialize boundary so package-manager bootstrap is not blamed on the tool.
  4. L3 comparator + declaration verification — observed vs declared. A tool that exceeds its own published grant is denied, and no renaming evades it.
  5. Seccomp compilation — the capability verdict (not the raw trace) is compiled into a syscall deny-list by subtraction, then smoke-tested: the tool must complete a real MCP initialize under it, or the gateway degrades a tier and says so.

Runtime — POST /tool_call, every call

Inbound DLP → trust-store lookup → L4 policy (three-valued: allow / deny / hold for approval) → response-provenance gate → fresh confined container → manifest re-attestation → outbound DLP → log.

Each call gets only the paths its verified grant names, only the environment variables it declared, --network none unless it declared hosts, and its own seccomp profile.

What is novel here

  • Response-provenance gating — asks where an argument came from rather than what it means, so it is paraphrase- and encoding-proof.
  • Continuous manifest attestation — catches a description that changes after approval (CVE-2025-54136 class), separating "changed" from "changed into an attack".
  • Declaration verification as a measurable axis — comparable systems score 0% on it by construction.

Scope: process ownership

Kernel-level protection requires the gateway to be the process that spawns the tool.

  • Supported — any MCP server the gateway launches as a child process: effectively all stdio tools (npx, python, compiled binaries).
  • Unsupported — anything running remotely, or launched by another application. A native desktop app exposing SSE on 127.0.0.1 cannot be profiled, because we did not spawn it.

This is the inverse of protocol-layer defences like ClawGuard, which inspect JSON-RPC and work on servers they never own but cannot constrain execution. The two are complementary, with inverse blind spots.


Install

Python 3.11+ and Docker Desktop, running. Docker is what makes L2 profiling and runtime confinement possible; without it you get a static scan and nothing else. You do not need to install strace — it is injected into the tool's image when missing.

mkdir ztg-workspace && cd ztg-workspace     # the gateway writes its DB and
python -m venv .venv                        # profiles relative to this directory
.venv\Scripts\Activate.ps1                  # Windows  (source .venv/bin/activate elsewhere)
pip install mcp-ztgateway

mcp-ztgateway doctor                        # check the machine
mcp-ztgateway serve                         # http://127.0.0.1:8000

serve is a subcommand — mcp-ztgateway --port 8000 on its own is an error. It binds localhost with no API key by default; set GATEWAY_API_KEY before binding anything else.

From source:

git clone https://github.com/nabrahma/MCP_Zero-Trust_Gateway_BTP.git
cd MCP_Zero-Trust_Gateway_BTP
python -m venv venv && .\venv\Scripts\Activate.ps1
pip install -e ".[dev]"
mcp-ztgateway serve

New here? MD_files/GETTING_STARTED.md walks a blank machine through to a confined tool call, with the real outputs at each step and a troubleshooting table.


Usage

Onboard a tool

POST /register
{
  "name": "read_file",
  "command": ["docker","run","-i","--rm","real-mcp-image","mcp-server-filesystem","/app"],
  "image": "real-mcp-image",
  "test_tool": "read_file",
  "test_args": {"path": "/app/package.json"}
}

Returns a verdict — allow, allow_with_warning, needs_declaration, quarantine or deny — plus the observed capabilities, the compiled seccomp profile, and a paste-ready declaration drafted from what the tool was actually seen doing.

needs_declaration means "we could not classify this tool, so we have no basis to judge it". It is non-runnable like quarantine, but it is not an accusation.

Declare capabilities (recommended)

"annotations": {
  "mcpzt:fs_read":         ["/workspace"],
  "mcpzt:net_allow_hosts": ["api.example.com"],
  "mcpzt:subprocess":      ["node"],
  "mcpzt:env":             ["WORKSPACE"]
}

Standard MCP hints (readOnlyHint, openWorldHint) are deliberately ignored — they are claims by the tool author, and honouring them would let a malicious server grant itself anything.

Scope a session to a task (optional)

POST /session   { "session_id": "s1", "objective": "Read ./report.md and summarise it" }

Calls outside that objective are held for approval, not denied. Approve with POST /approvals/{id}; the approval authorises the exact arguments a human saw and is consumed on use.

Other endpoints

/logs · /verdicts · /profiles · /enforcement · /egress · /confinement · /approvals

Connecting an agent

The gateway is an HTTP service, not an MCP server, so there is nothing to paste into claude_desktop_config.json or VS Code's mcp.json — no stdio shim ships in this release. An agent integrates by sending its tools/call envelope to POST /tool_call instead of connecting to the MCP server directly, which is what the evaluation harness does. A drop-in adapter for MCP clients is a small, self-contained piece of work that has not been built yet.

Docker Compose

docker-compose up --build     # gateway :8000, dashboard :5173

Live profiling needs the host Docker daemon. The compose image deliberately has no Docker socket, so run the gateway on the host for evaluation.


Limitations

Stated properly in MD_files/Paper/LIMITATIONS.md. The ones that matter most:

  • Response-content attacks are not covered. A tool that does its advertised job and returns fabricated data crosses no boundary here. This is the open frontier, and every comparable system shares it.
  • Dynamic profiling is evadable and non-deterministic. A dormant payload is invisible at onboarding; containment is what makes that survivable.
  • Corpora are small. No statistical significance is claimed anywhere.

Documentation

Start at MD_files/README.md.

Want to… Read
Join the project TEAMMATE_HANDOVER.md
Write the paper MD_files/Paper/
Check a number Paper/RESULTS.md
Review the design ARCHITECTURE.md
Reproduce results Paper/REPRODUCIBILITY.md

How to read our numbers. Every defence names the layer that produced it. Crashes and timeouts are INCONCLUSIVE and stay in the denominator — never counted as defences. DSR is never quoted without the false-positive rate. All raw data is committed, so if a number looks too good, open the row and check it.

That discipline exists because this project once reported a 100% Defence Success Rate that was an artefact of two measurement bugs cancelling out — a seccomp compiler that made every container fail to boot, and a harness that scored the crashes as defences. The post-mortem is GROUND_TRUTH_AUDIT.md.


Tech stack

Python · FastAPI · official MCP SDK (stdio) · Docker · strace · seccomp · Landlock (opt-in) · SQLite · React dashboard

License

MIT — see LICENSE.

Metadata

Release files for mcp-ztgateway 0.3.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for mcp-ztgateway 0.3.1
File Size Uploaded
mcp_ztgateway-0.3.1.tar.gz 172.0 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for mcp-ztgateway 0.3.1
File Interpreter ABI Platform
mcp_ztgateway-0.3.1-py3-none-any.whl Python 3 none any Details

Total release size: 314.6 kB

Release files / mcp_ztgateway-0.3.1.tar.gz

Download URL mcp_ztgateway-0.3.1.tar.gz
Size 172.0 kB
Tags Source
SHA-256 checksum
How to use checksums
2a9e2536c0aadad3a72ffb199d3f0a14d4617e983b5a68d0e9d6e4846bc1bfd2
BLAKE2b-256 checksum
How to use checksums
713923fda23a18fd392a80507f4cc0525a60e0ba4da09dbda7b0845b60b7a769
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.12.5

Release files / mcp_ztgateway-0.3.1-py3-none-any.whl

Download URL mcp_ztgateway-0.3.1-py3-none-any.whl
Size 142.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
361a9a69b9cc2b1cab58feb0d01c5e121cb82948f15a81eb417c3e2d292a563d
BLAKE2b-256 checksum
How to use checksums
ebf4a0a4fc6bfa61430ade3de662e21ac5ef89986f1857ce65a655290db1d6d5
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.12.5

Release history Release notifications | RSS feed

0.5.1

2 release files

0.5.0

2 release files

This release

0.3.1 This release

2 release files

0.3.0

2 release files

0.2.2

2 release files

0.2.1

2 release files

0.2.0

2 release files

0.1.1

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page