pypi mcp-gatehousestdioupdated 1mo ago
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.
What can you do with mcp gatehouse?
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
Install
Add mcp gatehouse to your client. Pick the one you use.
claude mcp add mcp-gatehouse -- uvx mcp-gatehousecodex mcp add mcp-gatehouse -- uvx mcp-gatehouseamp mcp add mcp-gatehouse -- uvx mcp-gatehouse{
"mcpServers": {
"mcp-gatehouse": {
"command": "uvx",
"args": [
"mcp-gatehouse"
]
}
}
}Add to `claude_desktop_config.json`, then restart Claude Desktop.
{
"mcpServers": {
"mcp-gatehouse": {
"command": "uvx",
"args": [
"mcp-gatehouse"
]
}
}
}Add to `~/.cursor/mcp.json`, or `.cursor/mcp.json` for a single project.
code --add-mcp '{"name":"mcp-gatehouse","command":"uvx","args":["mcp-gatehouse"]}'Or add the block manually to `.vscode/mcp.json` under `servers`.
{
"mcpServers": {
"mcp-gatehouse": {
"command": "uvx",
"args": [
"mcp-gatehouse"
]
}
}
}Add to `~/.codeium/windsurf/mcp_config.json`.
{
"mcpServers": {
"mcp-gatehouse": {
"command": "uvx",
"args": [
"mcp-gatehouse"
]
}
}
}Add to `cline_mcp_settings.json` via the MCP Servers panel.
{
"mcpServers": {
"mcp-gatehouse": {
"command": "uvx",
"args": [
"mcp-gatehouse"
]
}
}
}Add to `~/.gemini/settings.json`.
{
"mcpServers": {
"mcp-gatehouse": {
"type": "local",
"command": "uvx",
"args": [
"mcp-gatehouse"
],
"tools": [
"*"
]
}
}
}Add to `~/.copilot/mcp-config.json`, or run `/mcp add` inside the CLI.
{
"context_servers": {
"mcp-gatehouse": {
"command": {
"path": "uvx",
"args": [
"mcp-gatehouse"
]
}
}
}
}Add to your Zed `settings.json`.
uvx mcp-gatehouseRun `goose configure`, choose **Add Extension β Command-line Extension**, and paste this command.
Score
39 / 100
Incomplete
- Documentation25/25
- Maintenance16/25
- Trust6/20
- Capability0/15
- Install experience12/15
- Documents what it does and how to connect
- Has a resolvable package or endpoint
- Exposes at least one tool, prompt or resource
- README has substantive content
- Includes a code example
- Documents its configuration
- Mentions credentials or security posture
- Last commit 50 days ago
- Has a release history
- Repository is not archived
- No licence detected
- Namespace verified in the official MCP registry
- Claimed by its owner
- Published under an organisation
- 0 tool(s) documented
- Provides prompt templates
- Provides resources
- 12 documented install method(s)
- Published to a package registry
- Offers a hosted endpoint β no local install
Version history
| Versions | Published |
|---|---|
| 0.1.0Latest | Jul 13, 2026 |