warrant-mcp
With this server, you can pre-check intended actions against a security policy before executing them. It provides a single MCP tool, check_action, that evaluates a proposed action and returns an ALLOW or DENY verdict along with the governing policy clause when denied. The tool supports three action types:
file_delete: check deletion of a file specified bypath(e.g., protects.env,.git).shell_command: check execution of a command string (e.g., blocksrm -rf, forbidden tokens).http_request: check an HTTP request given aurlandmethod(e.g., enforce host allowlists, method restrictions). The tool is purely advisory and never performs the action itself; in Claude Code a separate hook can enforce denials as hard blocks. It fails closed—malformed input or a missing policy results in DENY.
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@warrant-mcpupdate my policy to block deleting .env files"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
warrant-mcp
Write the rules for your AI agent once, in plain English. Every tool call is checked against them, and the ones your rules forbid do not run.
An agent can already decide what to do. It cannot prove it was allowed to. Today that leaves two options: approve every tool call by hand, or trust the agent. warrant-mcp is the layer in between.
Here for the Claude Skill? It lives in this repo: Writing a policy with Claude — two commands to install, no npm needed.
Sixty seconds
npm install -g warrant-mcp
cd your-project
warrant-mcp initinit returns enforcing. No API key, nothing compiled, nothing to paste.
Now watch it refuse something:
warrant-mcp test "delete .env" DENY clause W2
W2 — Do not touch the .env file or anything inside the .git directory.
Refused under clause W2: the file is named ".env", which is protected.
Dry run: nothing was enforced, executed, or written.Then open Claude Code in that directory and ask it to delete .env. The agent
will genuinely try; the hook blocks the tool call before it executes, and the
file is still there afterwards.
Around a minute from an empty directory to that first refusal, and almost
all of it is npm install. Four runs on the same Windows laptop came in between
43 and 71 seconds end to end, with the install accounting for 37 to 60 of that;
init and the check are a few seconds each. Your machine will differ. Nothing
after the install waits on a network or a model.
Changed your mind?
warrant-mcp removeYour settings file comes back byte-for-byte and everything init created is
deleted. Try things you can undo.
Related MCP server: Stage0 Authorization MCP Server
What just happened
You write rules in plain English —
.warrant/policy.md.Claude compiles them once, off stage, into numbered clauses backed by structured rules, and refuses to guess where a sentence is ambiguous.
Deterministic code evaluates every proposed action against those clauses and returns allow or deny, naming the clause that decided.
A Claude Code
PreToolUsehook turns a deny into a hard block — the tool call never runs, and the deny overrides even an--allowedToolsallowlist.The model never makes the runtime call. Claude compiles the policy; code decides. The evaluator's input type does not even carry the clause text, so a model-written sentence structurally cannot reach a decision — that is a compile error, not a convention.
The compiled policy lives outside your project — ~/.warrant/projects/<project>/,
read-only — because an agent that can delete the policy governing it can disarm
the thing that stops it, and out there the policy's own "stay inside the
project" clause guards it.
Changing the rules
Edit .warrant/policy.md in your own words, then:
warrant-mcp review # compiles, shows every clause and what changes in behaviour
warrant-mcp accept # adopt itreview is the only command that calls the model, and it is the only one
that needs ANTHROPIC_API_KEY. Enforcement never compiles — not at startup,
not per call, not ever. It shows you each clause in plain English, and then
what actually changes: which previously-allowed actions are now refused and
which refusals are now permitted, derived by running a fixed corpus through
both policies rather than by diffing text. Nothing the hook reads is written
until you accept.
If a sentence cannot be expressed as an enforceable rule, the compiler refuses the whole policy and tells you what it can express nearby. A sentence that silently compiled to nothing would read as protection you do not have.
What a policy can say
Eight closed rule types — pure data, no free text, no model-supplied patterns:
Rule | The sentence it exists for |
| "Stay inside the project." |
| "Leave my .env alone." · "Never touch .pem or .key files." |
| "Only write inside src/ and tests/." |
| "Never run anything as root." |
| "No rm -rf." · "Don't pipe downloads into a shell." |
| "Never force-push." · "Don't push to main." · "Don't install dependencies." |
| "Only talk to these hosts." |
| "GET and HEAD only." |
Some things people write cannot be decided from the action alone, and the compiler refuses them rather than approximating: "don't delete anything you didn't create" (needs provenance the evaluator does not have), "don't do anything expensive" (needs world knowledge — the model deciding at runtime), "ask me first" (needs a third verdict this deliberately does not have), "don't change more than ten files" (needs state across calls). The ten sentences that shaped this vocabulary are in demo/ten-sentences.md.
The format is specified separately
SPEC.md — version 0.1.0, versioned independently of this package — defines the compiled artifact, the exact matching semantics of all eight rule types, clause precedence, the fail-closed requirements, and what the format deliberately cannot express. Enforcement is explicitly out of its scope: a hook is one client's mechanism, not part of the format.
This repository is the reference implementation, not the definition. Where
the spec and this code disagree, the spec is what an implementer should trust
and the disagreement is a bug here. spec/corpus.json is the
arbiter — 76 language-agnostic checks, run by this repo's own test suite, so
this implementation cannot drift from the document without going red. A second
implementation would be welcome: it is the only real evidence that the spec is
precise enough to deserve the name, and the corpus is the only referee either
side needs. spec/README.md is the porting guide.
Limitations — read this before relying on it
This is a policy layer, not a sandbox. It should be deployed inside one.
Nine adversarial sessions were run against its own hook, sandbox reset between
attempts, every result from an actual attempt rather than from reasoning about
one. Six got through. Five are closed, each with a regression test named for
the attack that opened it. The sixth is open by construction: rm -f *
deleted the compiled policy itself and took enforcement off duty. It is
mitigated, not fixed — the compiled policy now lives in a read-only vault
outside the project, where the policy's own "stay inside the project" clause
guards it, so the same attack is refused and cites that clause. The glob is
still expanded after the decision, and that has no fix here.
A seventh was found later, while writing SPEC.md rather than by attacking anything, and is deliberately still open — see the last bullet below.
These classes remain open by construction, and are properties of the architecture rather than bugs awaiting a patch:
Shell glob and variable expansion. The hook sees
rm -f *; the shell expands it after the decision. Same for$VAR, command substitution andxargs. Any command with implicit targets —git clean -fdx,make clean— is in this class. Deciding what a glob will match means reading the filesystem, and the deciding path deliberately imports no filesystem capability at all.Obfuscation. Base64, string concatenation inside an interpreter (
'.'+'env'), homoglyphs and self-writing scripts defeat a tokenizer. The checks raise the cost; they do not close the class.Symlinks. Path text is compared, never resolved, so a symlink inside the project pointing out of it passes the workspace clause.
Coverage is per-tool and per-client. Only Claude Code tool calls are hooked. A new tool, another MCP client, an unusual field name, or a process that outlives the session are all outside. Two of the six routes found were exactly this shape, which is the best evidence that the list above is not exhaustive.
Network egress is only as good as the mapping. Tool-driven fetches are covered; an MCP server's own outbound calls are not.
TOCTOU. The check runs before execution; the world can change in between.
Enforcement is a Claude Code hook. Any other MCP client gets the
check_actiontool, which advises and does not enforce — an agent that does not call it is not constrained by it.It costs a process per matched tool call. The decision itself is about 0.01ms, but the hook is a separate Node process, so end to end it measured 220–430ms median across three runs on a busy Windows laptop — roughly half of that being Node starting at all, and a p95 tail into the seconds when the machine is loaded. Measure your own with
node demo/bench.mjs.The model's own refusals are not enforcement. A route the model declines is untested, not safe.
The hook configuration is a file in your project, so an agent with write access can edit it. Org-managed settings are the real answer.
A global flag with a separate-word value displaces the subcommand.
git -c core.pager=cat push --forceslips a rule that deniesgit push --force, becausecore.pager=catlands where the subcommand was expected;git --no-pager push --forceis denied, because that flag consumes nothing. Closing it needs per-command knowledge of which flags take values — knowledge this format does not carry and a model may not supply at runtime — so it is specified in SPEC.md §3.3.5 and pinned by a conformance case, making any fix a deliberate version bump rather than a silent change.
A real deployment wants OS-level confinement, an egress proxy enforcing the
host list at the network layer, hook settings the agent cannot edit, and a
verdict trail that survives an interested party. That last one is now partly
here — every checked tool call is written to a local record, and warrant-mcp report reads it — but only partly: it is a plain file with no integrity check,
and what it is and is not says so in
detail. The full attack log and reasoning are in
SECURITY-SURFACE.md, unsoftened. How the six routes
were actually found, session by session — and how the seventh was found the
next day by writing the spec instead:
writing/bypass-hunt.md.
The record, and reading it
Every tool call the hook checks appends one line to a record that lives beside the compiled policy — outside your project, so the clause that protects the policy protects its record too. Then:
warrant-mcp reportOne .html file. Open it offline: the stylesheet, the script and the data are
all inside it, and the page makes no network request at all when it opens.
Nothing is uploaded, no server starts, and the record itself is only ever read.
It is built around the four questions an auditor actually asks — what was attempted and what happened, what was refused and by which clause, what is repeating, and when behaviour changed because the policy did. A dense sortable table is the centre of it; filters AND together and every active one shows as a chip you can remove, with a running count of what they hide, so a filtered view can never be mistaken for the whole picture. The filter state lives in the URL fragment, so you can send someone exactly what you were looking at. Clauses that never fired are listed too — a rule nobody has tripped is either dead weight or untested.
--since 7d narrows the window and the page says how much it excluded.
--out <path> puts it somewhere else.
Treated as public. The rendered bytes are scanned for credential shapes,
home directories and login names before anything is written; if it would leak,
the command refuses and no file is created. Paths are rewritten first — your
workspace to ., your home to ~, anyone else's login name to <user>.
What the record is, and what it is not
Read this before treating it as an audit trail, because it is not one.
It is a plain text file, and anyone who can reach it can rewrite it.
decisions.jsonl is one JSON object per line. It is append-only by
convention, not in fact: the only code that writes it appends, and nothing
enforces that. There is no append-only filesystem mode, no lock, and no
permission that would stop an edit. The compiled policy beside it is made
read-only; the record cannot be, because it has to stay writable to be written
to.
There is no integrity check of any kind. No hash, no chain linking one line to the next, no signature. Delete a line, reorder the file, or write a convincing line by hand, and nothing detects it — the reader validates that a line is well-formed, never that it is genuine. A forged entry that parses is indistinguishable from a real one.
Nothing outside the machine can verify it. There is no external witness, no notarisation, and nothing is transmitted anywhere. A report built from it is a rendering of a local file, and it is exactly as trustworthy as the machine and the person handing it to you.
A missing line does not mean the action did not happen. Recording is deliberately best-effort: it runs after the verdict and swallows every failure, because a record that can fail a tool call has stopped being a record and become a new way to break your work. A full disk or a read-only vault costs you lines, silently.
What it is, then: a local, human-readable log of what this machine decided, good for review, for noticing a clause that fires constantly, and for seeing when a policy edit changed behaviour. It is evidence, not proof.
If you need an audit trail that survives an interested party — append-only storage, hash chaining, signing, or shipping lines off the machine as they are written — none of that is here, and the honest place to build it is your logging infrastructure rather than this file.
The MCP tool
Beyond the hook, warrant exposes one tool, check_action, so an agent can ask
before acting:
kind | fields |
|
|
|
|
|
|
It returns ALLOW or DENY with the governing clause and a one-sentence
reason. It only checks — there is no code path from any verdict to an
execution, because the deciding modules import nothing that could touch a file,
spawn a process, or open a connection. Malformed input fails closed. The hook
is what makes a refusal binding; the tool is how an agent can ask politely.
Writing a policy with Claude
The package ships a Claude Skill, skills/warrant-policy-author,
that teaches Claude the authoring craft: a short interview, sentences shaped
for the closed rule set, and the failure shapes above explained rather than
just avoided. init offers to install it into your project's
.claude/skills/ (opt-in — say yes at the prompt, or pass init --skill;
an existing folder of the same name is never overwritten, and remove takes
away exactly what was installed). You can also copy or link the folder by
hand. The skill writes policy text only — it never enforces anything,
and it never claims a sentence will compile. warrant-mcp review is the
authority; if review refuses, the refusal is right.
What that looks like in practice. The sentence people write first:
Don't touch secrets, never rewrite history, and don't install anything.
Three real intents, and the compiler refuses the whole policy: "secrets" is a judgement call, and neither "history" nor "anything" names a command it is allowed to guess. The same intents as the skill writes them, after one question about your stack:
Leave my .env alone, and never touch anything ending in .pem or .key.
Never rewrite history: no git rebase, no git commit --amend, no git reset --hard.
Don't install new dependencies with npm — no npm install, no npm i, no npm add.
Same protections, now decidable from the words alone. That is the craft the skill packages: naming things is the human's authority to exercise, and the skill's job is to ask for the names — and to explain, when a sentence cannot work, why the boundary is where it is.
Or install it as a plugin
This repository is also a Claude Code plugin marketplace. Two commands, no npm install:
/plugin marketplace add rmanish2000-del/warrant-mcp
/plugin install warrant-policy-author@warrant-mcpYou get exactly the skill above — the same folder this repo ships, no
separate copy to drift. The plugin deliberately does not wire the MCP
server or the enforcement hook: both need a per-project compiled policy that
only warrant-mcp init can set up (vault, settings backup, exact undo), and
a hook without a policy would deny everything. Write the policy with the
skill; enforce it with warrant-mcp init.
Where the policy is looked for
WARRANT_MCP_POLICY— an absolute path.initwrites this into both generated configs, so a client spawning the server from any directory finds the right policy..warrant/config.json— the pointerinitwrites, naming the vault..warrant/policy-compiled.json— a simple in-project layout, if you prefer.The package's own copy — only present in a source checkout; never shipped, so an installed copy cannot silently enforce the sample.
If none resolve, the server refuses to start and the hook denies. A missing policy is a refusal, never a pass.
What init touches
Path | |
| your policy — edit this |
| pointer to the compiled policy |
| hook appended, merged — your other settings survive |
|
|
| the compiled policy (read-only), your settings backup, the undo record |
A settings file it cannot parse is refused, not rewritten.
Commands
| wire up this project — no API key; |
| undo it, restoring settings byte-for-byte |
| dry-run one action; nothing is enforced or written |
| render the record as one local HTML file — |
| compile the policy and show what changes (needs an API key) |
| adopt the reviewed draft — never compiles |
| the MCP server on stdio (a client spawns this) |
| the PreToolUse entry (a hook config spawns this) |
Requires Node ≥ 22.6. npx warrant-mcp init works without installing.
Development
npm test # 227 tests, including the SPEC.md conformance corpus
npm run typecheck
npm run demo # the canonical checks with verdict banners, fully offlineTypeScript, strict, no build step for development — the source runs directly
under --experimental-strip-types. The published package ships compiled
JavaScript in dist/, because Node refuses to strip types under
node_modules; the emit is pure type erasure, so it cannot change a verdict.
Prior work
The deterministic authorization engine and the plain-English-to-clauses approach come from an earlier project of mine, warrant, built for a payments hackathon on 1–2 August 2026. warrant-mcp is a separate repository that applies that thinking to agent tool calls; it does not fork or vendor that codebase.
The core build — M1 through M7, ending with 77 tests, the MCP server, hook
enforcement, the adversarial audit and the authoring loop — is tagged
m7-complete-2026-08-03. Everything after it is packaging and documentation:
git log m7-complete-2026-08-03..HEADLicense
MIT — see LICENSE.
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Tools
Related MCP Servers
- AlicenseAqualityBmaintenanceMCP server to dynamically load Claude Code skills into AI agentsLast updated57914MIT
- Alicense-qualityCmaintenanceAn MCP server that enforces runtime authorization for tool calls using Stage0 policy validation, preventing AI agents from executing unauthorized actions before they happen.Last updatedMIT
- Alicense-qualityCmaintenanceAn MCP server that enforces runtime governance on AI agent actions — file access, command execution, delegation chains, and permission escalation.Last updatedMIT
- Alicense-qualityAmaintenanceMCP server that vets package installations and shell commands to block dangerous actions by AI coding agents.Last updated221MIT
Related MCP Connectors
Hosted MCP server connecting claude.ai, ChatGPT and other AI apps to your own computer
MCP server for AI agents to plan, verify, and deploy Cloudflare-native apps.
MCP server teaching AI agents to implement TideCloak: auth, E2EE, IGA, security analysis
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/rmanish2000-del/warrant-mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server