The ZIFFER MCP server
Who this is for: the developer whose AI assistant helps set ZIFFER up: it scans the code, writes the integration, checks the policy and tries it in a sandbox.
Who this is for: the developer whose AI assistant helps set ZIFFER up: it scans the code, writes the integration, checks the policy and tries it in a sandbox.
The ZIFFER MCP server is a program your AI assistant starts on your machine, and it gives the
assistant ZIFFER's tools. It is a helper for setting ZIFFER up and checking it: it is not what
decides whether an AI agent's tool call runs, which ZIFFER's service decides and the verify line
in your own code enforces. Nothing ZIFFER guarantees depends on your AI assistant behaving well.
This page describes @ziffer-io/mcp version 0.3.0. explain_scan_finding needs version 0.3.0
or later.
1. What you need
- Node.js 22 or later. Your AI assistant starts the server with
npx. - An AI assistant that starts MCP servers over standard input and output. Section 2 shows two.
- Nothing else for most of the tools. No account and no key: section 3 says which tools need more, and what.
2. Install it in your AI assistant
Claude Code:
claude mcp add ziffer -- npx -y @ziffer-io/mcp@0.3.0If you use Claude Code, the ZIFFER plugin starts this same server for you and adds written procedures on top of it.
Cursor, or any AI assistant that starts an MCP server over standard input and output, takes this in its MCP configuration:
{
"mcpServers": {
"ziffer": { "command": "npx", "args": ["-y", "@ziffer-io/mcp@0.3.0"] }
}
}The server starts without any configuration. To see it work, ask your assistant how to add ZIFFER
to your project: it calls get_started, which needs nothing.
3. Its tools, and what each one needs
It has 21 tools. Most need nothing at all: no account, no key, no network. The tools that call
ZIFFER need an API key, and while you set up that is a sandbox key (what a sandbox gives you, and
what you receive). "Sends to ZIFFER" means the
tool opens a connection to the ZIFFER service at ZIFFER_API_URL; every other tool works on your
machine alone.
| You are | Tool | What it answers | Needs | Sends to ZIFFER |
|---|---|---|---|---|
| Scanning the code | scan | Every tool your code, and the AI tools installed on your machine, give a model; ZIFFER's decision for each under a draft policy; and the one place to put ZIFFER. | Nothing. Python on your PATH to read Python files. | No |
explain_scan_finding | Why ZIFFER decided what it did about one tool from the last scan, the draft policy entry that decided it, and how to change it. | A scan earlier in the same session. | No | |
| Putting ZIFFER in the code | get_started | The steps from nothing, in order: the scan, where an account comes from, the install command per language, the values to set, and which tool to call next. | Nothing. | No |
get_integration_guide | The SDK guide for one language. | Nothing. | No | |
check_integration | For each place your code proposes an action, whether a verify runs beside it. | Your code on disk. | No | |
lint_proposal | Whether one proposal has the shape ZIFFER accepts, naming the field that is wrong. | Nothing. | No | |
| Writing and checking the policy | get_policy_repo_guide | The policy repository template's instructions and the first-hour guide, word for word. | Nothing. | No |
check_policy_repo | What is left to do in your clone of the policy repository, one line and one fix per check. | The policy repository on disk, and the ziffer command line tool. | No | |
explain_policy | For each action in your policy, whether it runs alone, runs with somebody told, is held for approval, or is refused, and by which rule. | The policy repository on disk, and the ziffer command line tool. | No | |
simulate_decision | What your policy decides about one proposal, graded on your machine. | The policy repository on disk, and the ziffer command line tool. | No | |
| Publishing | explain_publish_failure | From a failed publishing run's log: the step that failed, the refusal and what to do. | The run's log, pasted. | No |
| Trying it against a sandbox | whoami | Which tenant your API key belongs to, and until when the key is accepted. | An API key. | Yes |
sandbox_status | Whether a tenant is a sandbox and what that means; with a decision_id, whether that decision is decided or still waiting. | Nothing. An API key when you pass a decision_id. | Only with a decision_id | |
propose | Sends one proposal and returns ZIFFER's answer as it came. | An API key. | Yes | |
check_decision | One decision by id, with its receipt when there is one, not verified. | An API key. | Yes | |
| Reading a decision, a receipt, a refusal | get_decision | One decision by id, its receipt verified on your machine before you see it, or the rule that refused it. | An API key, and your trust anchor file and suite floor. | Yes |
list_decisions | Every decision for your key, and every request still waiting for approval, newest first. | An API key. | Yes | |
explain_receipt | Whether a receipt is valid for a proposal, or the rule that refused it. | Your trust anchor file and suite floor. | No | |
explain_refusal | What one refusal name means, who fixes it and what to do now. | Nothing. | No | |
| Documentation and feedback | search_docs | The sections of the ZIFFER guides that use your words, whole. | Nothing. | No |
send_feedback | Nothing back: it sends us a question the documentation did not answer, to be read by a person. | An API key. | Yes |
send_feedback sends text your assistant wrote. What goes into its question and context
is stored under your tenant and read by a person at ZIFFER, as written. Nothing in it is filtered
or removed on the way, so it must never carry a key, a token, a customer's name or anything from a
private file.
simulate_decision and explain_policy read your policy without checking its signature. They
answer what your rules decide. A policy can read clean there and still be refused when it is
published, for example because its signature does not verify or it has expired.
4. The values it reads
It reads four environment variables of its own. A tool reads a value when it is called, never before, so the server starts without any of them.
| Variable | What it is | Where the value comes from | Read by |
|---|---|---|---|
ZIFFER_API_URL | The address of the ZIFFER service you call. It must start with https://; http:// is accepted only for your own machine. | Your hand-over sheet (where every value comes from); for a sandbox, what ZIFFER hands you with its key. | The tools marked "Yes" in section 3 |
ZIFFER_API_KEY | Your API key. It carries your tenant, so no request names one. | Your hand-over sheet; for a sandbox, the sandbox key. | The tools marked "Yes" in section 3 |
ZIFFER_TRUST_ANCHOR | The path to the public key file your receipts are signed under. | A file written by ziffer pubkey and handed to you out of band, never fetched from the service it checks (your trust anchor file). A sandbox has its own. | get_decision, explain_receipt |
ZIFFER_SUITE_FLOOR | The weakest signature you accept. There is no default. | Your hand-over sheet. | get_decision, explain_receipt |
Set them in the environment your AI assistant starts in, or in its own MCP settings. Never type a key into the conversation with your assistant, and never put one in a file inside your repository. If your assistant keeps its MCP settings in a file inside your project, that file is part of your repository: keep the key out of it.
When the server starts, it writes one line to your assistant's log saying which of the four are set. It never writes their values.
explain_receipt takes a trust_anchor_path for one call, to check a receipt against a second
key file without restarting your assistant. A key file that holds a private key is refused, and
nothing is read from it.
5. What it writes, and where
Two tools write files. Neither writes inside your project or your repository unless you name the folder.
scancreates a new folder under your system's temporary folder, namedziffer-scan-followed by random characters, and writes into it the draft policy folderziffer-policy-<date>and, beside it, the same files the scan writes from a terminal (what the scan writes). The HTML report and the review archive are included unless your assistant passesreport: false. The files stay there for you to read and delete.- The one exception is a folder you name. Once you have agreed to start the installed tool
servers (section 6), your assistant can pass
outto name the draft policy folder. The files then go there, including inside your project if that is where the folder is. The folder must not exist yet. A relativeoutis taken from the folder your assistant started the server in. The first call, and a scan of your code only, always write into a new temporary folder. simulate_decisionwrites the proposal it grades to a file in a new temporary folder, because theziffercommand line tool reads a proposal from a file, and removes that folder when it is done.
No other tool creates, changes or deletes a file.
6. What it starts
The first scan call starts no tool server. It reads your code, and it lists the tool servers
your installed AI tools are configured to start, each with the command it would run and with
credentials shown as [redacted]. To read Python it runs your machine's own Python on the reader
shipped in the package.
A second call with confirm: true starts the servers on that list, asks each one for its list
of tools and nothing else, and writes one draft policy covering your code and those tools. With
include_installed: false the scan reads your code only, in one call, and starts nothing.
The confirmation is an instruction to your assistant, not a lock. The tool tells your
assistant to show you the list and to make the second call only once you agree. The server cannot
tell whether you agreed: an assistant that calls with confirm: true straight away starts the
servers. The servers are programs your own AI tools already start, and they do what they do when
those tools start them (what the scan starts).
Three tools run the ziffer command line tool. check_policy_repo runs ziffer list and
ziffer decide --unsigned, the two commands your policy repository's own pull request check runs;
explain_policy and simulate_decision run ziffer decide --unsigned. They run it directly,
never through a shell, so that every verdict comes from the same program your pipeline uses. If
ziffer is not on your PATH, those tools say NOT CHECKED and name it (installing the ziffer
command line tool).
7. What it never does
These are properties of the server's code. An AI assistant can be talked into calling a tool, but no tool here can do any of the following.
- It never creates, asks for or keeps your policy signing key. The scan signs its draft policy
with a key made for that run inside the scan, never written to disk and dropped at the end;
nothing of yours or ZIFFER's trusts it.
check_policy_reporeports a failure if the private key file is inside your policy repository. - It never publishes a policy, and never commits or merges anything. No tool calls the service
that publishes policy, and the only program it runs besides the scan is the
ziffercommand in section 6. - It never approves anything and never names an approver. There is no approve tool.
sandbox_statusreports and approves nothing.simulate_decisiongrades and signs nothing, and a PASSED from it is a grade, never permission to act. - It sends nothing to ZIFFER except through the seven tools marked "Yes" in section 3. The scan sends nothing anywhere, and no tool asks an AI model anything.
8. When a tool answers "not set", "not found" or NOT CHECKED
- Not set. A tool that needs a value you have not set answers with a name ending in
Unconfigured, and the variable to set:ApiUrlUnconfigured,ApiKeyUnconfigured,TrustAnchorUnconfiguredorSuiteFloorUnconfigured. Nothing was sent and nothing was verified. There is no default address and no default key. - Not found. What you asked about is not where the tool looked, and the tool does not guess.
explain_scan_findinganswersToolNotInScanwith the names the last scan did find.explain_refusalanswersRefusalUnknownrather than the row for the nearest name. A decision id that belongs to another tenant reads asDecisionUnknown, exactly like one that does not exist. - NOT CHECKED. The tool could not look:
zifferis not on your PATH, or averifysits in another file where a text reading cannot follow it. NOT CHECKED is never a pass. A FAIL is different: the tool looked and found something, which is an answer and not a tool error.
Every other refusal is named; the refusal table says what each name means and what to do. A refusal is the same every time for the same input: fix what it names rather than retrying.
9. Where to go next
- Running the scan, reading what it wrote and what it reads and does not.
- The ZIFFER plugin for Claude Code, which starts this server for you.
- The SDK guide: the
verifyline that enforces ZIFFER's decision in your code. - Sandbox tenants: where the tools that send to ZIFFER should point while you set up.
Every refusal, and what it means
The names the receipt verifier raises, the names the decision API sends, and the classes the executor alerts on - with what to do about each.
The ZIFFER plugin for Claude Code
A plugin for the Claude Code AI assistant that helps a developer set ZIFFER up, installed once.