docs / guide

view as .md

First denial in 60 seconds#

Reading about permission boundaries proves nothing. Watching your own agent get told no — by a grant, not by a prompt — is the whole product in one moment. This page gets you there in about a minute, then shows the second moment: pulling the plug from outside the process.

1. Watch a denial happen (~30 seconds, zero setup)#

npx -y @grantor/mcp demo

This wraps a bundled toy MCP server behind a grant for one tool (search), while the server also exposes delete_everything. A scripted agent calls both. The granted call is served. The other one produces this:

┌─ DENIED ───────────────────────────────────────────────────┐
│  requested      delete_everything                          │
│  grant allows   search                                     │
│  refused as     CapabilityDenied — no effective grant …    │
│                                                            │
│  Denied by the grant, not by a prompt —                    │
│  the server never saw the call.                            │
│  revoke everything:   grantor-mcp revoke --child …         │
└────────────────────────────────────────────────────────────┘

Only the agent is scripted. The enforcement is the real thing: a real grant minted by the broker, the real wrap relay, and a live on-chain revocation read. The denied request is answered by the broker itself — the wrapped server never receives it, so there is no prompt, system message, or clever jailbreak on the other side to talk past.

2. Now your own server (~30 seconds more)#

You don't modify the server. First see what it exposes:

npx -y @grantor/mcp tools -- npx -y @modelcontextprotocol/server-filesystem /tmp

This lists the server's tools, marks the write-shaped ones, and prints a ready-to-paste wrap command granting only the read-like names — a name heuristic, stated as such: review the suggestion before trusting it. Then run the suggestion, or write your own grant:

npx -y @grantor/mcp wrap --tools read_file,list_directory --max-uses 50 --ttl-secs 3600 -- \
  npx -y @modelcontextprotocol/server-filesystem /tmp

Point your agent at the wrapped command instead of the bare server — in a Claude Code / MCP client config, the wrap line simply replaces the server line:

{
  "mcpServers": {
    "filesystem": {
      "command": "npx",
      "args": ["-y", "@grantor/mcp", "wrap",
               "--tools", "read_file,list_directory",
               "--max-uses", "50", "--ttl-secs", "3600",
               "--", "npx", "-y", "@modelcontextprotocol/server-filesystem", "/tmp"]
    }
  }
}

The first time the agent reaches for write_file, you get your denial — in your own session, on your own server.

3. The second moment: revoke from outside the process#

A boundary you can set is good. A boundary you can pull while the agent is mid-flight is the part credentials can't do. The wrap's startup log and every DENIED box name the child id; revoke it:

npx -y @grantor/mcp revoke --child <child-id>

The revocation is an on-chain epoch bump — the next call the agent makes, including the previously allowed ones, is denied with EpochRevoked. The agent's process was never touched; its authority just stopped existing, from outside, with a receipt anyone can verify on a public chain.

What you just used#

The wrap is the permission broker's enforcing mode: named tools, a use budget, an expiry, delegation that can only narrow, and on-chain revocation — the same capability model that runs everything here. The demo's sandbox rides the public mainnet tenant; production use is your own tenant, and everything above is the same commands with your own config.