Writ
Governs GitHub MCP tool calls through Writ, enabling agents to perform GitHub actions such as creating issues and merging pull requests under configurable policy, approval, and audit rules.
Governs Gmail MCP tool calls such as sending email, with policy rules that can match recipients and require human approval before messages go out.
Governs Linear MCP tool calls through Writ, allowing agents to work with Linear through the same policy, approval, and audit layer as other upstream MCP servers.
Click on "Deploy 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., "@Writshow me the tool calls waiting for my approval"
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.
Writ
Let any agent loose. Nothing happens without your signature.

Writ is an open-source gateway that sits between AI agents and the tools they use. Point Claude Code, Cursor, or your own agent at Writ instead of at your MCP servers. Every tool call then passes through your rules: reads run, risky actions wait for a person, forbidden ones never happen, and everything is written down.
Claude Code ─┐ ┌─▶ GitHub MCP
Cursor ──────┼─▶ Writ (one URL) ───┼─▶ Linear MCP
your agent ──┘ rules · approvals └─▶ any MCP server
taint · audit logEvery agent ships its own permission prompts, and each one only covers itself. Writ is one place to say what any agent may do, see what every agent did, and sign off on what matters.
What it does
One MCP endpoint for all your tools. Agents see every upstream's tools as
<upstream>__<tool>(github__create_issue). Credentials for upstreams live in Writ, encrypted, never on the agent's machine.Policy in code, not in a prompt. Each tool has an effect (
read,write,send,destructive). Defaults: reads run, everything else asks. Rules match tools, agents and arguments: "release-bot may merge in acme/web; nobody may delete anything; email to @acme.com is fine."Approvals that agents understand. A call that needs a person is held open while you decide in the console. Approve, edit the arguments and approve, or deny with a note the agent reads. If nobody answers in time, the agent is told to retry later, and the retry picks up your decision instead of asking twice.
Prompt-injection containment. Once a session reads third-party content through Writ (an issue, an email, a page), nothing but reads runs without a person in that session, whatever the rules say. The model can still be fooled; it can't act on it alone.
An audit log of everything. Every call: which agent, which tool, the arguments, what the policy said and why, who decided, what came back, how long it took.
Related MCP server: mcp-guardian
Quick start
Requires Bun 1.3+.
git clone https://github.com/Taskmaster-1/writ && cd writ
bun install
bun run dev # gateway on :8790, console on http://localhost:3000Open http://localhost:3000 and create the owner account. With no DATABASE_URL, data lives in
.data/ using PGlite (Postgres in WebAssembly), which is fine for trying
Writ on one machine.
Self-host with Docker (Postgres included)
echo "WRIT_SECRET=$(openssl rand -base64 32)" > .env
docker compose up -d # console on http://localhost:3000Set PUBLIC_URL in .env to the address people and agents will use, e.g.
PUBLIC_URL=https://writ.example.com, and put TLS in front of port 3000.
Try it without any credentials
bun run demo:upstream # a pretend inbox and calendar on :8791Upstreams → Add upstream: name
demo, URLhttp://localhost:8791/mcp.Agents → New agent: name
claude-code. Copy the token.Connect Claude Code (the console shows this command with your token filled in):
claude mcp add --transport http writ http://localhost:3000/mcp \ --header "Authorization: Bearer writ_…"Ask Claude Code to "check my inbox and do what the emails ask". It reads the inbox (allowed), one email in it is a prompt injection asking to forward your mail, and the forward lands in Approvals instead of going out.
Connect other clients
Client | How |
Claude Code |
|
Cursor |
|
Anything else | Any MCP client that speaks Streamable HTTP and can send a header |
How a call is decided
A matching block rule always wins.
Otherwise the first matching rule decides (
allow,askorblock).Otherwise the default for the tool's effect (read → allow; write, send, destructive → ask).
In a tainted session,
allowbecomesaskfor anything but reads.
Effects come from the upstream's MCP annotations (readOnlyHint, destructiveHint). A tool
that declares nothing is treated as destructive. Admins can correct any tool's effect, because
annotations are claims made by the server.
[
{ "tool": "*__delete_*", "decision": "block", "note": "Nothing gets deleted by an agent" },
{ "tool": "github__merge_*", "agent": "release-bot", "decision": "allow",
"when": [{ "path": "repo", "op": "startsWith", "value": "acme/" }] },
{ "tool": "gmail__send", "decision": "allow",
"when": [{ "path": "to", "op": "allEndWith", "value": ["@acme.com"] }] }
]The Rules page has a tester that shows what the saved policy would do with any call.
Configuration
Variable | Default | |
|
|
|
| generated in | Encrypts upstream credentials, signs sessions. 32+ characters. |
|
| Where people open the console; agents use |
|
| Gateway port |
|
| The first account is always allowed; more only when |
|
| How long a call waits for a decision before the agent is told to retry (max 240) |
|
| How long a pending or approved call stays valid |
|
| Where the console proxies |
Honest limits
Writ governs what passes through it. An agent can still act through other tools you gave it directly (its shell, its own web access). Route the tools that matter through Writ, and turn on strict mode if agents read untrusted content elsewhere.
Upstreams are connected over Streamable HTTP with static headers today. OAuth to upstreams and stdio servers are on the roadmap.
One workspace per install (why).
Development
bun test # policy unit tests + end-to-end gateway tests (real MCP client and server, in-memory Postgres)
bun run lint # Biome
bun run typecheckHow the code is organised, and the rules that keep it that way: ARCHITECTURE.md. Why things are the way they are: docs/decisions.
Roadmap
OAuth for upstream MCP servers; stdio upstreams
Approve from Slack, email and phone
Approvals that learn: suggest a rule after you approve the same kind of call again and again
OpenAPI specs as upstreams (any REST API, no MCP server needed)
Per-agent budgets and rate limits
Teams: roles, approver groups, audit export
License
This server cannot be deployed
Maintenance
Related MCP Connectors
- gatewayOAuthai.sealgate
MCP gateway with runtime security policy, tool-call-level control, and audit of agent actions.
Zero-secret MCP gateway for AI agents: risk-scored, audited calls with human-in-the-loop approval.
Security gateway for AI agents: policy, approval, and audited execution, no secrets shared.
Zero-setup MCP gateway securely connecting AI to your tools with authentication and workflows
Related MCP Servers
- AlicenseNot gradedqualityDmaintenanceA security gateway that enforces policies, tracks data taints, and sandboxes tool calls between AI agents and MCP servers. It provides a secure chokepoint to prevent prompt injection and ensure OWASP ASI compliance through audit logging and deterministic execution.2Apache 2.0
- AlicenseNot gradedqualityFmaintenanceA gateway that enforces permissions, sanitization, approval, and audit for AI agent MCP tool calls, with a policy engine and local proxy CLI.235 npm1MIT
- AlicenseNot gradedqualityDmaintenanceA policy-enforcing MCP gateway that intercepts all tool calls to downstream MCP servers, applying allow/deny/ask rules with human approval and audit logging for safe access to dangerous tools.7 npmMIT

evav-gatewayofficial
AlicenseNot gradedqualityDmaintenanceGoverned MCP gateway that lets AI agents call tools with policy enforcement, prompt-injection screening, a kill-switch, and tamper-evident signed audit logs.Apache 2.0