MCP Gateway · v0.1.0

MCP tools are trusted on sight. Nothing checks that trust.

A tool becomes available in your agent's toolset, and its description gets read like any other trusted context — most MCP clients never scan it. The call's own arguments go straight to your dispatcher. The result comes back and gets acted on. And the tool itself can quietly change after you approved it, with nothing to catch it. MCP Gateway is four checkpoints for that exact lifecycle — admission, argument, result, and drift — composed into one guard object you wire into your existing tool-dispatch loop. Pip-installable, zero dependencies, works as a library or a CLI.

1,399tests passing 0pip dependencies 4checkpoints — admission → argument → result → drift

Where it's unguarded

Three points in an MCP tool call, unguarded.

Admission
A tool's description can carry an attack before it's ever called. An MCP server controls the natural-language description attached to every tool it offers. An agent reads that text just by having the tool listed as available — whether or not it ever calls it. A compromised or malicious server can embed an injection payload directly in a tool's own metadata.
Call
A tool call's own arguments can carry a dangerous shape. Once a tool is admitted, its call arguments are structured data from an untrusted source — the model, or whatever's upstream of it. A chained dunder-attribute gadget, a literal eval-looking string, a serialized-object marker: if your dispatcher ever does dynamic attribute lookup or deserialization on an argument, this is how that gets reached.
Drift
What you already approved can silently change. MCP tools are typically approved once, then trusted indefinitely. If the server serving a tool changes its description or schema later, a tool that was safe at review time can become a delivery vector — or start requesting more sensitive parameters — without ever being re-reviewed.

What it does

One object for the whole tool-call lifecycle.

Admission · mcp_guard

In plain terms: before a tool is added to your agent's available toolset, its description gets scanned for injection the same way a spam filter checks an email before it hits your inbox — then checked against an allow/block policy.

Technically: evaluate_tool_admission() runs the full prompt-injection detector against a tool's description and denies on a FLAGGED verdict regardless of the allowlist — an allowlisted NAME never launders a compromised description. A process-local ToolCallLimiter is included for call-rate limiting per tool.

Argument · mcp_argument_guard

In plain terms: once a tool is admitted, every call's arguments get walked for shapes that would matter if your own dispatcher ever does anything clever with them — a review-trigger, not an automatic block, since a security-review tool's own legitimate queries about dangerous code trip the same patterns real injected code would.

Technically: scan_tool_call_arguments() recursively walks a call's argument tree for chained dunder-attribute gadgets, eval/exec/os/subprocess-looking strings, and pickle/YAML deserialization markers — checked at every protocol version, not a hand-built prefix table.

Lifecycle · mcp_lifecycle

In plain terms: McpLifecycleGuard is the object that actually ties this together — admit() once per tool, then before_call()/after_call() around every invocation, instead of remembering to call three separate functions correctly at three separate points by hand.

Also the third checkpoint: after_call() runs the tool's RETURNED text through the same detector's output scanner — the mirror image of admission, catching exfiltration-shaped results (a credential-shaped string, a data-carrying URL) the tool itself produces.

Drift · mcp_tool_pinning

In plain terms: approve a tool's description and schema once; if it silently changes later — a compromised server, a poisoned registry update — the next check flags it before your agent trusts the new content.

Technically: pin/check hash-pin a tool's name, description, and schema with sorted-key canonical JSON, so incidental whitespace never masks a real change. Exits non-zero on any CHANGED tool — usable as a CI gate.

Real runs

Real runs, not a mockup.

Every line below is an actual terminal session against the real, shipped CLI — captured directly, not staged. Want both products in one place? The combined two-minute demo →

$ mcp-gateway scan-tools tools.json
[clean] search
[flagged] send_email
  [agentic_hijack] 'AGENT INSTRUCTION:' marker - the shape of a payload smuggled into fetched content  -> 'AGENT INSTRUCTION:'
  [agentic_hijack] explicit instruction to act covertly  -> 'silently exfiltrate'
mcp-gateway scan-tools: 2 tool(s) scanned, FLAGGED description(s) found
$ echo $?
1
$ mcp-gateway scan-call search arguments.json
[flagged] search
  [dunder_chain] arguments.query: chained dunder-attribute access (SSTI/sandbox-escape gadget shape) -> 'x().__class__.__mro__[1].__subclasses__()'
$ echo $?
1
$ mcp-gateway pin tools.json tools.lock.json
mcp-gateway pin: pinned 2 tool(s) to tools.lock.json
$ mcp-gateway check tools.json tools.lock.json
mcp-pin: 2 tool(s), all unchanged.
$ echo $?
0
$ # ...the MCP server's own description for `search` changes later, unreviewed...

$ mcp-gateway check tools.json tools.lock.json
mcp-pin: 0 unchanged, 0 new, 1 CHANGED, 0 missing
  CHANGED [search] pinned=f433516f9102... now=5b736edb6806... - definition differs
  from what was approved at 2026-08-24T01:27:54Z. Re-review before trusting.
$ echo $?
1

Try it

Now run it on your own tool description.

Paste an MCP tool's description and scan it — live, in your browser, right now. A right-sized client-side subset of the real admission scanner's agentic-hijack patterns, not a canned response.

This runs a small subset of the real admission scanner's agentic-hijack pattern family client-side — the shape that matters most for a tool DESCRIPTION specifically (the surface admission scanning exists to cover). The full engine (mcp_gateway.mcp_guard.scan_tool_description) checks 30+ categories across every kind of untrusted text this package handles, not just this one family — this is a taste of the mechanism, not the whole product.

What's actually in it

What's actually in it.

  • Pure standard library — zero pip dependencies to audit or update.
  • Works as an importable library or a standalone mcp-gateway CLI.
  • McpLifecycleGuard — one object, admit() → before_call() → after_call(), wired into your existing tool-dispatch loop.
  • mcp-gateway pin/check hash-pin an MCP tool list and flag any silent post-approval change.
  • mcp-gateway scan-tools — a zero-setup first look at an unfamiliar MCP server's tool list, before you ever connect to it.
  • mcp-gateway scan-call — one-off argument-shape scan for testing/demo use.
  • Fails closed on the one genuinely hard stop available: rate limiting. Argument findings are a review-trigger, never a silent auto-block.
  • Ships the same adversarial self-red-team harness that guards the flagship detector — mutates known-caught attacks and fails CI on any new evasion.
  • 1,399 tests, including a real standalone install-and-run check from outside the package's own source tree.

Pricing

One license, your whole org.

No per-seat fees. Unlimited use across your whole organization.

A
MCP Gateway license annual

Flat. Unlimited environments and seats within your org. Covers unlimited use across your org: the library, the CLI, and updates for the year — admission + argument + result scanning, McpLifecycleGuard for your dispatch loop, drift pinning + CLI for CI gating.

$4,000/yr

Card checkout: clicking "Get it" opens Stripe's own hosted checkout page, and you're invited as a GitHub collaborator automatically on payment.

Trust & scope

What's real, and what this isn't.

1,399/1,399Tests passing, verified today
0Pip dependencies
4Checkpoints: admission, argument, result, drift
0.1.0Current version
Argument scanning is a structural smell detector, not a sandbox — a tool dispatcher that never does dynamic attribute lookup, eval, or unsafe deserialization on its arguments has nothing to worry about even if this fires. Admission scanning is heuristic, static pattern-matching — the same detector, and the same honest limits, as the flagship prompt-injection scanner it delegates to: intentionally over-inclusive, and novel phrasings will slip past it like any signature-based defense. Drift pinning detects that a definition CHANGED, not whether the new one is malicious — a flagged change still needs a human to look at it. None of this implements an MCP client, verifies a tool server's identity or TLS certificate, or provides cross-process rate limiting — the call limiter is single-process, in-memory state, reset on restart. We run the vendored red-team harness against the detector ourselves rather than just asserting it works: as of today it catches 148/160 mutated attack variants (92%) across eight evasion techniques. The 12 remaining gaps are disclosed, not hidden — the same known, tracked gap set the underlying detector already discloses everywhere else it ships.

Wiring an MCP client of your own?

No sales call needed to get started.