Permission tiers, approval gates, and audit logging for MCP servers — the server is the gatekeeper.
Project description
mcp-gatehouse
Permission tiers, approval gates, and audit logging for MCP servers. The server is the gatekeeper: you decide what an AI can read, what it can write, and what's off-limits — and every action gets logged.
Most MCP servers hand the model every tool at full strength and keep no
record of what it did. That's fine for a demo. It's not fine the day an
agent has write access to your CRM, your books, or your order system.
mcp-gatehouse is the missing gate, enforced inside the server — no
proxy, no external policy service, no dependencies beyond the official
mcp SDK.
pip install mcp-gatehouse
What you get
| Permission tiers | Every tool is declared READ, WRITE, or DESTRUCTIVE — and the tier also emits honest spec ToolAnnotations (readOnlyHint / destructiveHint), which the wrapper won't let you override to lie. |
| Approval gates | Tiers you choose require a sign-off before the tool runs. Your approver is any callable — a terminal prompt, a Slack ping, a ticket. Fails closed: a gated tool with no approver configured is denied, not waved through. |
| Audit log | Append-only JSONL, one line per call — allowed, denied, or failed — with UTC timestamps and durations. The answer to "what did the AI actually do?" six months later. |
| Redaction | Argument keys you name (api_key, password, token, … by default) are masked before they reach the log or the approver. |
| Denylist | Block a tool outright, whatever its tier. |
Quickstart
from mcp.server.fastmcp import FastMCP
from mcp_gatehouse import AccessTier, AuditLog, Gatehouse, Policy
mcp = FastMCP("order-desk")
gatehouse = Gatehouse(
mcp,
policy=Policy(approver=lambda req: input(f"allow {req.tool}? [y/N] ") == "y"),
audit=AuditLog(path="audit.jsonl"),
)
@gatehouse.tool(tier=AccessTier.READ)
def lookup_order(order_id: str) -> str:
"""Look up an order's status."""
...
@gatehouse.tool(tier=AccessTier.DESTRUCTIVE)
def cancel_order(order_id: str) -> str:
"""Cancel an order. Runs only if the approver says yes."""
...
mcp.run()
That's the whole integration: build your FastMCP server exactly as the
SDK docs show, but register tools through the gatehouse. Schema generation,
transports, and everything else work unchanged — the guard preserves the
function's signature.
Under the default policy, DESTRUCTIVE requires approval and everything
is audited. Gate writes too with one line:
Policy(require_approval=frozenset({AccessTier.WRITE, AccessTier.DESTRUCTIVE}), ...)
What the audit trail looks like:
{"ts": "2026-07-16T14:02:11+00:00", "tool": "lookup_order", "tier": "read", "outcome": "ok", "arguments": {"order_id": "4417"}, "duration_ms": 0.42}
{"ts": "2026-07-16T14:02:38+00:00", "tool": "add_note", "tier": "write", "outcome": "ok", "arguments": {"order_id": "4417", "note": "call back", "api_key": "«redacted»"}, "duration_ms": 1.08}
{"ts": "2026-07-16T14:03:05+00:00", "tool": "cancel_order", "tier": "destructive", "outcome": "denied", "reason": "approver refused", "arguments": {"order_id": "4417"}}
Try the demo
The package ships a runnable order-desk server with all three tiers wired up and a terminal-prompt approver:
mcp-gatehouse-demo
Point any MCP client at it over stdio (Claude Desktop, etc.), ask the model
to cancel an order, and watch the approval land in your terminal — and the
verdict land in audit.jsonl either way. examples/orders_server.py is
the same server as a copyable template.
Design notes
- Enforcement lives inside the server, at the tool boundary. A proxy can't see your tools' semantics, and a policy service is one more thing to deploy. A 40-person plant doesn't have a platform team; this is a few small classes and a JSONL file.
- Fail closed. Security defaults that quietly allow are worse than none. That includes redaction: argument values the scrubber can't take apart (arbitrary objects, bytes) are replaced with an opaque placeholder rather than passed through, and exception messages stay out of the log — only the exception type is recorded, because error text loves to embed the very values you just redacted.
- The audit log records denials and errors, not just successes — the calls that didn't happen are half the story.
- A blocking terminal approver and the stdio transport don't mix —
stdout/stdin are the protocol pipe. The demo's approver prompts on
/dev/ttyfor exactly that reason (and denies when no terminal exists). Real deployments should approve out-of-band: Slack, a ticket, a queue. - What this is not: authentication, transport encryption, or a sandbox. It's a gate inside your server, not a perimeter around it. See SECURITY.md.
Compatibility
Targets the official mcp Python SDK
v1.x (mcp>=1.27,<2) and Python 3.10+. When SDK v2 ships for the
2026-07-28 spec revision, a v2-compatible release will follow — the
public API here (Gatehouse, Policy, AuditLog, AccessTier) will
not change.
Who built this
Nick George — I design and run MCP servers in production for a mid-market reverse logistics-tech company, and build them for businesses at nickgeorgeai.com. This library is the permission-and-audit discipline from those builds, extracted.
If you're an owner or operator wondering what MCP even is, start with the plain-English guide: What is an MCP server?
License
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file mcp_gatehouse-0.1.0.tar.gz.
File metadata
- Download URL: mcp_gatehouse-0.1.0.tar.gz
- Upload date:
- Size: 15.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ce78fad0525c7eba91e1d9a1fc5a2369c0e999e84e02be62321d01afd565d994
|
|
| MD5 |
00657ce2c38f115072a07efc4d806042
|
|
| BLAKE2b-256 |
807831f75617b2b9efb288854b390d5f59b2bffc6685c4a95c1b45176d51dd66
|
File details
Details for the file mcp_gatehouse-0.1.0-py3-none-any.whl.
File metadata
- Download URL: mcp_gatehouse-0.1.0-py3-none-any.whl
- Upload date:
- Size: 13.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
37fd3a7f691b62a219223e206a9554e488d66cc59792a1baf1d77fafdc693791
|
|
| MD5 |
f53640f4888ad92494ae9ecd3fe6c77e
|
|
| BLAKE2b-256 |
a376cb304206aefd00452c9f5988ec8b5528b1d11fdb09c4ab10a281ae281ef0
|