Skip to main content
Glama
mcp-gatehouse

mcp-gatehouse

mcp-gatehouse

Project page & docs: nickgeorgeai.com/open-source/mcp-gatehouse · PyPI

CI PyPI Python License: MIT mcp-gatehouse MCP server

Gatehouse MCP server

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

Claude Code asks to cancel order 4418; the server's approval form appears in Claude Code with the arguments; ticking Approve lets it run; the audit log shows the call pending, then ok

The demo server in Claude Code: the model asks to cancel an order, the gate asks you in Claude Code's own UI, and the audit log records the call as asked, then done.

mcp-gatehouse demo on YouTube: the install, tiers, a cancel refused and one approved, and the audit log

The 1:21 demo runs the package's demo server in a terminal: the install, the one-decorator change, a cancel refused and one approved, and the audit log they left. No sound.

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, asked right in the MCP client (Claude Code, Cursor, VS Code) or by any callable you write: 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, failed, or cancelled — 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, client_secret, … by default) are masked before they reach the log or the approver — however they're spelled: apiKey, API-Key and api_key are the same key.

Denylist

Block a tool outright, whatever its tier.

Related MCP server: TheeDiscordMCP

Quickstart

from mcp.server.mcpserver import MCPServer
from mcp_gatehouse import (
    AccessTier, AuditLog, ElicitationApprover, Gatehouse, Policy, terminal_approver,
)

mcp = MCPServer("order-desk")
gatehouse = Gatehouse(
    mcp,
    # Ask in the client's own UI; prompt on the terminal if it can't show a form.
    policy=Policy(approver=ElicitationApprover(fallback=terminal_approver)),
    audit=AuditLog(path="~/order-desk/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 MCPServer exactly as the SDK docs show, but register tools through the gatehouse. Schema generation (including Annotated[..., Field(description=...)] parameter docs), transports, and everything else work unchanged — the guard preserves the function's signature. Sync tools still run in a worker thread, exactly as they would on a bare MCPServer.

Approve in the client

ElicitationApprover puts the question in front of the person using the MCP client, as a form with one box to tick, showing the tool, its tier and its arguments (secrets already masked). MCP calls this elicitation. Claude Code, Cursor and VS Code support it. The model can't answer the form, and you run nothing beside the server.

It works under both protocol revisions in use:

  • Up to 2025-11-25 the form goes to the client mid-call and the gate waits for it, under approval_timeout like any approver.

  • From 2026-07-28 there's no channel for that. The call hands the question back to the client and runs when the client calls again with the answer. The SDK seals that round's state and binds it to the tool and its exact arguments. The gate also checks that the answer is to the question it asked, that it arrived within approval_timeout (an hour if none is set), and that each approval runs its call only once.

Anything but a ticked box (decline, cancel, closing the form, an unticked box, a malformed answer) is a refusal. When the client can't be asked, the fallback decides; with no fallback, the call is denied. A tool that runs its own multi-round input flow, through Resolve(...) parameters or by returning InputRequiredResult, gets the fallback too, since one call has one such channel. Single-use is tracked per process.

Elicitation trusts the client to show the form to a person. A client or hook set to accept forms on its own can approve too. When the approval must come from outside the client, use an out-of-band approver instead.

Other approvers

terminal_approver asks on the controlling terminal (/dev/tty), never stdin — over stdio, stdin is the protocol pipe, so a plain input() approver would corrupt it. An approver is any callable, sync or async, that takes an ApprovalRequest and returns True to allow; sync ones run in a worker thread from a pool of their own, so a human taking their time doesn't stall other calls, and a pile of pending approvals can't take the threads your sync tools need. If the approver raises (Slack down, ticket API timing out), the call is denied and the failure is logged.

Give approvals a deadline so a sign-off nobody gives doesn't hang the call:

Policy(approver=terminal_approver, approval_timeout=120)

Past it, the call is denied and logged as approval timed out. Approvers see the deadline as request.deadline (a time.monotonic() value), and terminal_approver withdraws its prompt when it passes. A sync approver that ignores it finishes in the background and its answer is dropped. With no timeout set, the gate waits as long as the approver takes.

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}), ...)

Add your own secret-bearing keys without losing the defaults:

from mcp_gatehouse import DEFAULT_REDACT
Policy(redact=DEFAULT_REDACT | {"card_pin"}, ...)

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"}}
{"ts": "2026-07-16T14:05:40+00:00", "tool": "cancel_order", "tier": "destructive", "outcome": "cancelled", "reason": "awaiting approval", "arguments": {"order_id": "4418"}}

outcome is ok, denied (reason says why), error (the tool raised; reason is the exception type), pending, or cancelled. pending is a call whose approval was asked in the client over the 2026-07-28 round trip: the verdict follows on a line of its own when the client comes back, so a pending line with nothing after it is a question nobody answered. cancelled means the client gave up, or the server shut down, before the call finished. reason says when. awaiting approval means the tool never ran. tool running comes with duration_ms, and the tool may have done part of its work. A sync tool can't be stopped partway: its worker thread runs to the end, and the line says ok or error as usual. The gate only writes the line; the cancellation goes on as it would without it.

Try the demo

The package ships a runnable order-desk server with all three tiers wired up. Approvals are asked in the client when it can show a form, with a terminal prompt as the fallback. Add it to any MCP client that speaks stdio. For Claude Code:

claude mcp add gatehouse-demo -- uvx mcp-gatehouse --audit-log /tmp/gatehouse-audit.jsonl

For Claude Desktop, in claude_desktop_config.json:

{
  "mcpServers": {
    "gatehouse-demo": {
      "command": "uvx",
      "args": ["mcp-gatehouse", "--audit-log", "/tmp/gatehouse-audit.jsonl"]
    }
  }
}

Or run mcp-gatehouse-demo yourself after pip install mcp-gatehouse. Ask the model to list the orders and cancel one, then watch the verdict land in the audit log either way. The audit log goes to --audit-log, else $MCP_GATEHOUSE_AUDIT_LOG, else ./audit.jsonl — falling back to ~/.mcp-gatehouse/audit.jsonl when the working directory isn't writable (desktop clients often launch servers from /). The server prints the path it chose to stderr.

In a client that supports elicitation (Claude Code, Cursor, VS Code), cancel_order brings up an approval form in the client: tick the box to let it run. An approval nobody gives is denied after two minutes.

Other clients get the terminal fallback, which needs the server to have a terminal it can ask on. Whether it gets one depends on how the client starts it:

  • TypeScript SDK clients (the StdioClientTransport in 1.x and 2.x) keep the server in the client's terminal session. Start such a client from an ordinary terminal, one not running a full-screen program, and the prompt appears there. An unanswered prompt is denied after two minutes.

  • Python SDK clients (stdio_client in 1.x and 2.x), and anything built on them, start every server in a session of its own. The server has no controlling terminal even when you run the client from one, so it can't prompt: cancel_order is denied, and stderr says why.

  • Desktop apps that launch servers in the background give them no terminal, and full-screen terminal UIs are using theirs. Denied too.

Every denial is the gate failing closed, as designed. Where the prompt can't reach you, approve out-of-band instead: an approver is any callable, so it can post to Slack, open a ticket or wait on a queue. 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, errors and cancellations, not just successes — the calls that didn't happen are half the story, and a call cut off halfway may still have done something.

  • A blocking terminal approver and the stdio transport don't mix — stdout/stdin are the protocol pipe. terminal_approver prompts on /dev/tty for exactly that reason, and denies when there's no terminal or a full-screen program has it in raw mode. Asking in the client (elicitation) sidesteps the terminal entirely. When the approval must not come from the client, 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 v2.x (mcp>=2,<3) and Python 3.10–3.14. Ships type information (py.typed). Release notes: CHANGELOG.md.

mcp-gatehouse

SDK

Server class

0.5.x

mcp>=2,<3

MCPServer

0.4.x

mcp>=2,<3

MCPServer

0.3.x

mcp>=2,<3

MCPServer

0.2.x

mcp>=2,<3

MCPServer

0.1.x

mcp>=1.27,<2

FastMCP

The public API (Gatehouse, Policy, AuditLog, AccessTier) is unchanged across that line, as promised. Porting a v1 server is two import edits — FastMCP became MCPServer and moved to mcp.server.mcpserver; see the SDK's migration guide.

Staying on SDK v1 needs no action: 0.1.x pins mcp<2, so pip keeps resolving it. That line is closed to features but still gets security fixes.

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. Why I built it, and how it works in plain words, is on the project page.

If you're an owner or operator wondering what MCP even is, start with the plain-English guide: What is an MCP server?

License

MIT

Available Tools

4 tools
add_noteA

Append a note to an order. Existing notes are kept — this never overwrites or cancels anything. Runs without approval, and every call is recorded in the audit log. To actually stop an order, use cancel_order.

NameRequiredDescriptionDefault
noteYesFree-text note to append, e.g. 'customer asked for a callback'.
api_keyNoOptional credential for the order system. Its value is masked in the audit log.
order_idYesThe order's ID, e.g. '4417'. Use list_orders to find one.

Output Schema

NameRequiredDescription
resultYes

TDQS

A4.5/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Beyond the annotations (readOnlyHint=false, destructiveHint=false), it discloses non-obvious traits: existing notes are preserved and nothing is overwritten or cancelled, no approval workflow is triggered, and every call is audit-logged. These are exactly the behaviors an agent cannot infer from the annotation flags alone.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Three short sentences, front-loaded with the core action, then the safety guarantee, then the alternative. Every sentence carries distinct information with no repetition of the name or schema.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With an output schema present, return values need no explanation, and annotations plus the schema cover the structured contract. The description supplies the remaining behavioral context (non-destructive append, no approval, audited) so nothing an agent needs to invoke this correctly is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100% — note, order_id and api_key are all documented in the schema, including the masking behavior of api_key and a pointer to list_orders for order_id. The description adds no parameter detail beyond that, so the baseline 3 applies.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description opens with a specific verb+resource ('Append a note to an order') and immediately scopes the operation against the sibling 'cancel_order'. An agent can distinguish it from cancel_order/lookup_order without opening any schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It explicitly routes the agent away from this tool when the goal is to stop an order ('To actually stop an order, use cancel_order'), and notes the operation needs no approval. It does not state when this tool is the right choice versus merely adding a note through another path, but the boundary against the closest sibling is clear.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

cancel_orderA
Destructive

Cancel an order. Irreversible, so it is gated: a human must approve before it runs, in this client's approval form if it shows one, else on the server's terminal. If they refuse, don't answer within two minutes, or there is nowhere to ask, the call is denied and the refusal is logged. To record information without cancelling, use add_note.

NameRequiredDescriptionDefault
order_idYesThe order's ID, e.g. '4417'. Use list_orders to find one.

Output Schema

NameRequiredDescription
resultYes

TDQS

A4.6/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations only declare destructiveHint=true/readOnlyHint=false; the description adds that the operation is irreversible, that execution is gated on human approval, which channel the approval uses, the two-minute timeout, and that refusals are logged. That is exactly the kind of consequence and side-effect detail annotations cannot express.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The purpose and the irreversible/gated nature are front-loaded, and the alternative route to add_note closes the paragraph. The approval-channel sentence is long, but each clause (refusal, timeout, no channel) carries distinct operational information.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a one-parameter destructive tool with an output schema, the description covers purpose, gating, fallback channel, timeout and failure logging. Nothing an agent needs in order to decide whether and how to call it is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100% and the single order_id parameter already carries an example and a pointer to list_orders, so the description adds nothing about parameters. The baseline of 3 applies when the schema does all the work.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The first sentence states a specific verb and resource ('Cancel an order'), and the closing sentence names the sibling add_note with the condition that selects it, so an agent can separate this from the other tools without opening any schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It gives an explicit alternative ('To record information without cancelling, use add_note') plus the conditions under which the tool will not run (refused, no response within two minutes, no approval channel). That is when-to-use, when-not-to-use and what happens otherwise in one short paragraph.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

list_ordersA
Read-only

List every order on the desk as id · customer · status, one per line. Read-only. Start here to find the order ID that lookup_order, add_note and cancel_order need.

NameRequiredDescriptionDefault

No parameters

Output Schema

NameRequiredDescription
resultYes

TDQS

A4.7/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnlyHint=true and destructiveHint=false, so the 'Read-only' line merely confirms that safety profile. However, the description adds real value by disclosing the exact rendered output format, which is behavioral information beyond the annotations.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Three short sentences, zero waste. The purpose and output format are front-loaded, followed by the safety hint and the routing guidance.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Output schema exists so return values need not be explained, yet the description still notes the rendering format. For a zero-param read tool, nothing an agent needs in order to call it correctly is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The tool takes zero parameters, so the baseline is 4. There are no parameter semantics to clarify, and the description correctly says nothing misleading about inputs.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb (List) and resource (orders), plus the exact output shape `id · customer · status`, one per line. Clearly distinguishable from the sibling operations that consume the IDs it produces.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

'Start here to find the order ID that lookup_order, add_note and cancel_order need' explicitly positions this as the entry point and names the alternatives that depend on its output. No inference required.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

lookup_orderA
Read-only

Look up one order's customer, status and notes. Read-only; changes nothing. Returns no order <id> if the ID doesn't exist.

NameRequiredDescriptionDefault
order_idYesThe order's ID, e.g. '4417'. Use list_orders to find one.

Output Schema

NameRequiredDescription
resultYes

TDQS

A3.9/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnlyHint=true and destructiveHint=false, so 'Read-only; changes nothing' is largely redundant. What genuinely adds value is the disclosed failure behavior: it returns `no order <id>` for a missing ID, which tells the agent how to interpret an empty result.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two short sentences, no filler, with the primary purpose front-loaded and the error behavior follow-up. Every clause carries information the agent needs.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

An output schema exists, so return-field explanation is unnecessary; the description still covers the one return nuance an agent must know (the 'no order <id>' sentinel). Nothing needed to invoke this correctly is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100% and the single order_id parameter is documented with an example ('4417') and a pointer to list_orders. The description adds no parameter semantics beyond that, so the baseline of 3 applies.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb (look up) and resource (one order) and enumerates the returned fields (customer, status, notes). The word 'one' cleanly separates it from list_orders, though the description never names that sibling explicitly.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Usage is implied by the read-only framing and the singular 'one order', and the schema description points to list_orders for ID discovery. But the description itself gives no explicit when-to-use vs alternatives and no exclusions.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

  1. 4 tool updatesv0.3.0
    • Changedadd_note3 fields changed
      • addedInput schema / properties / api_key / description
        Added value: +"Optional credential for the order system. Its value is masked in the audit log."
      • addedInput schema / properties / note / description
        Added value: +"Free-text note to append, e.g. 'customer asked for a callback'."
      • addedInput schema / properties / order_id / description
        Added value: +"The order's ID, e.g. '4417'. Use list_orders to find one."
    • Changedcancel_order1 field changed
      • addedInput schema / properties / order_id / description
        Added value: +"The order's ID, e.g. '4417'. Use list_orders to find one."
    • Addedlist_orders
    • Changedlookup_order1 field changed
      • addedInput schema / properties / order_id / description
        Added value: +"The order's ID, e.g. '4417'. Use list_orders to find one."
  2. 3 tool updatesv1.0.0
    • First observedadd_note
    • First observedcancel_order
    • First observedlookup_order

TDQS

A4.3/5.0

Scored across 4 tools

Disambiguation5/5

Each tool has a clearly distinct action and target: list all orders, look up one order, append a note, or cancel an order. The descriptions explicitly cross-reference similar tools (e.g., add_note vs. cancel_order), removing ambiguity.

Naming Consistency5/5

All four tools follow a consistent verb_noun snake_case pattern: list_orders, lookup_order, add_note, cancel_order. There is no mixing of conventions or vague standalone verbs.

Tool Count5/5

Four tools is a well-scoped set for an order-desk workflow focused on reading, annotating, and cancellation. Each tool has a clear purpose and none appears redundant.

Completeness3/5

The surface covers listing, lookup, notes, and irreversible cancellation, but omits obvious order lifecycle operations such as creating orders or updating status/details. These gaps are workable for a narrow cancellation/note workflow but notable for general order management.

Maintenance

ActivityActive
ResponsivenessSyncing

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    D
    maintenance
    A 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
  • A
    license
    B
    quality
    B
    maintenance
    Local MCP server for inspecting and managing an allowlisted Discord server via Discord's REST API, with safety modes, idempotent JSON blueprints, and destructive-operation safeguards.
    27
    1
    MIT
  • A
    license
    A
    quality
    D
    maintenance
    A security-first MCP server for managing Whatbox slots with structured read-only inspection and approval-gated mutations, including storage, services, website deployment, and torrent control.
    30
    MIT
  • A
    license
    Not graded
    quality
    C
    maintenance
    Enforces identity-based access control and audit logging for MCP servers, letting you grant fine-grained tool permissions to users and systems while failing secure by default.
    MIT