MCP gateway for warrantd — earned autonomy in front of any MCP server.
Project description
warrantd-gateway
The warrantd MCP gateway: a proxy that sits between any agent and any MCP server and wraps every downstream tool with risk classes, caps, approval routing, and earned autonomy — zero changes to the agent.
Put earned autonomy in front of any MCP server in five minutes.
Five-minute quickstart
pip install warrantd # meta-package: core + gateway
warrantd init # writes a commented warrantd.yaml skeleton
$EDITOR warrantd.yaml # add your MCP servers under 'servers:'
warrantd init # discovers tools; each lands consequential/manual
warrantd run --shadow # observe-only; point your agent at the gateway
# ... let the agent work for a while ...
warrantd report # "warrantd would have gated N of M actions"
warrantd verify-audit # the evidence chain is intact
Point your MCP client at the gateway instead of the downstream server, e.g. in a Claude Desktop-style config:
{
"mcpServers": {
"gated-tools": { "command": "warrantd", "args": ["run", "--shadow"] }
}
}
When the would-have report looks right, flip mode: enforce (or run with
--enforce).
Safety invariants (non-negotiable)
- Safe-by-default tool surface. Any unclassified tool — including tools
that appear downstream mid-session — is treated as
CONSEQUENTIAL+MANUAL. A compromised or updated MCP server expanding its tool surface can never grant un-vetted capability. (This is the gateway's answer to the OWASP Agentic AI tool-misuse class: the tool surface an agent can exercise is pinned by your policy, not by whatever the downstream server announces.) - Ceilings never move.
hard_cap,auto_cap, andmax_stateare enforced insidewarrantd-coreon every call; no metric, signal, or score can raise them. - Shadow mode never blocks.
mode: shadowforwards everything — even calls warrantd would BLOCK and calls under a tripped kill switch — and only records what it would have done. The gateway earns its own place in your stack before it enforces anything. - Earned autonomy stays earned, verifiably. Approval evidence and
decisions live in one hash-chained, append-only SQLite log;
warrantd run --enforcerefuses to start on a broken chain.
warrantd.yaml reference
version: 1 # required
mode: shadow # shadow | enforce
store: .warrantd/state.db # SQLite path, relative to this file
tenant_id: default # optional; stamped on every request
servers: # downstream MCP servers (stdio in this release)
github:
command: uvx
args: [mcp-server-github]
env:
GITHUB_TOKEN: "${GITHUB_TOKEN}" # ${VAR} reads your environment
prefix: "" # optional; prepended to exposed tool names
# (required if two servers expose the same name)
graduation: # optional; defaults shown
supervised: { pass_rate: 0.95, adversarial_pass_rate: 0.0, min_samples: 10 }
autonomous: { pass_rate: 0.99, adversarial_pass_rate: 0.90, min_samples: 50 }
approval_window: 20 # trailing approvals considered as evidence
tools: # per-server classification
github:
create_issue:
risk: reversible_write # read | reversible_write | consequential
max_state: supervised # manual | supervised | autonomous
auto_cap: "100" # quoted decimals; auto-approve at/below
hard_cap: "1000" # never auto-approve above
value_param: amount # argument that carries the request's value
delete_repo:
risk: consequential
max_state: manual
# Anything not listed here is consequential/manual. Always.
How a call flows
tools/call → resolve against the registry → TrustLayer.evaluate():
- ALLOW → forwarded downstream, result returned unchanged.
- BLOCK → an in-band
isErrortool result carrying the reason (your agent's model sees why and can adapt) — never a protocol error. - REQUIRE_APPROVAL → routed to the configured approval gate. This release
ships no approval surface yet (dashboard/Slack/webhook gates are the next
phase); the request is declined with an explanatory result and recorded as
pending_no_surface.
Every human approval recorded against an action class is graduation evidence:
clean approvals inside the window move the class toward SUPERVISED per your
thresholds, and one override or error drops it back. AUTONOMOUS on a
CONSEQUENTIAL class additionally requires an adversarial eval signal —
approval history alone can never unlock the top tier.
Approval surfaces
When a dashboard: section is configured, warrantd run serves the approval
inbox next to the proxy and holds approval-required calls open (default
300 s, approval.timeout_seconds) while approvers decide — in the dashboard,
from a Slack ping, or via a webhook integration. Every request shows its
graduation context: "this class is 2/5 approvals from SUPERVISED — your
decision feeds its trust record." Approval is investment, not interruption —
that is this project's answer to consent fatigue.
- A timeout returns an error inviting the agent to retry; a human decision
arriving after the timeout still becomes chained trust evidence, so
nothing is lost. Set your MCP client's tool timeout (e.g. Claude Code's
MCP_TOOL_TIMEOUT) at or above the approval timeout for the smoothest UX. - A denial is evidence too: it drags the class's pass rate down.
- Graduation quorum: a CONSEQUENTIAL class graduates only when its
evidence qualifies and
approval.graduation_quorumdistinct approvers (default 2) ratify the unlock — the two-person rule at the exact high-stakes choke point. Ratification only ever releases headroom already configured inmax_state; nothing can raise a ceiling above the YAML. warrantd dashboardserves a standalone read-only view of autonomy states, graduations, and the audit log.
Security model, honestly
- Named approvers: every approval, denial, and ratification vote is
chained under the authenticated approver's name. Tokens live in env vars
(
token_env) or as committed sha256 digests (token_sha256) — never plaintext in config. - Bearer tokens, no rotation/expiry in this release: rotate by editing config and restarting. The login cookie carries the raw token (HttpOnly, SameSite=Lax) — keep the dashboard on localhost or behind TLS termination.
- Identities are attributed, not signed: the gateway process writes the chain after authenticating the token; entries are not cryptographically signed by the approver. Signed entries remain roadmapped.
- Dev mode (
dashboard.auth: none, localhost only) attributes every decision to the synthetic approverdev— unattributed evidence, dev only. - Slack resolution happens in the dashboard (the message links to it), so
approval identity never depends on trusting Slack's sender. Webhook
deliveries are HMAC-signed when
secret_envis set; callbacks authenticate with a registered approver's bearer token.
Audit integrity, honestly
Each audit row hashes its payload together with the previous row's hash;
warrantd verify-audit walks the chain and reports the first edit, deletion,
insertion, or reordering. The chain proves sequence integrity, not writer
authenticity — it cannot detect the writer itself truncating the tail.
Signed entries (non-repudiation) are on the roadmap. Decision rows store the
full tool-call arguments locally for the report; argument redaction hooks are
likewise roadmapped.
Full documentation lives in the repository README.
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 warrantd_gateway-0.2.0.tar.gz.
File metadata
- Download URL: warrantd_gateway-0.2.0.tar.gz
- Upload date:
- Size: 66.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0a9c8d6432b4703442a1a941484b7301e8654be6761b0e345f3d32aa96952c76
|
|
| MD5 |
b8cabaf4769a892164728de9152b961e
|
|
| BLAKE2b-256 |
3b16a604fe9c79862ac018d217cb7f3de24c84ea8051dcb66cadcd6f2824e5b2
|
Provenance
The following attestation bundles were made for warrantd_gateway-0.2.0.tar.gz:
Publisher:
release.yml on moritzkazooba-wq/warrantd
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
warrantd_gateway-0.2.0.tar.gz -
Subject digest:
0a9c8d6432b4703442a1a941484b7301e8654be6761b0e345f3d32aa96952c76 - Sigstore transparency entry: 1790242531
- Sigstore integration time:
-
Permalink:
moritzkazooba-wq/warrantd@6311176a3146a09ec5b8aba52ba5e8f236a627d3 -
Branch / Tag:
refs/tags/warrantd-gateway-v0.2.0 - Owner: https://github.com/moritzkazooba-wq
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@6311176a3146a09ec5b8aba52ba5e8f236a627d3 -
Trigger Event:
release
-
Statement type:
File details
Details for the file warrantd_gateway-0.2.0-py3-none-any.whl.
File metadata
- Download URL: warrantd_gateway-0.2.0-py3-none-any.whl
- Upload date:
- Size: 55.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
af70f8de9cb7e9ab1aca7f31ec20367e7249f68293b3b3ba29f7a102376a4a3c
|
|
| MD5 |
fe8b531c09f704d08f917dc89ed3dea9
|
|
| BLAKE2b-256 |
9f7198b66d04d016d06f77dd3e08b7a5cfa70bf2fbdcddf1c1cc9b8bd749ef13
|
Provenance
The following attestation bundles were made for warrantd_gateway-0.2.0-py3-none-any.whl:
Publisher:
release.yml on moritzkazooba-wq/warrantd
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
warrantd_gateway-0.2.0-py3-none-any.whl -
Subject digest:
af70f8de9cb7e9ab1aca7f31ec20367e7249f68293b3b3ba29f7a102376a4a3c - Sigstore transparency entry: 1790242544
- Sigstore integration time:
-
Permalink:
moritzkazooba-wq/warrantd@6311176a3146a09ec5b8aba52ba5e8f236a627d3 -
Branch / Tag:
refs/tags/warrantd-gateway-v0.2.0 - Owner: https://github.com/moritzkazooba-wq
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@6311176a3146a09ec5b8aba52ba5e8f236a627d3 -
Trigger Event:
release
-
Statement type: