granted
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., "@grantedCheck grant status for report 2026-09-30"
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.
granted
A time-bound, single-use grant that lets an agent carry out a decision a human already made — on a clock, with a receipt from the substrate, and never silently.
"you give your agent a task, then walk away and get a coffee, only to come back and find the agent got stuck on an approval on the first step and has made no progress. As a result, people often give in and set their agents to 'auto-approve', or
--dangerously-skip-permissions, which is, obviously, unsafe."— the cloudflare-os README
Cloudflare's answer is deferred approval: the agent proceeds against simulated results and the human approves later, in bulk. That fits a company where the human is back in minutes. granted is the answer for the owner-operator whose median time-to-keystroke is measured in days: approve once, in advance, with a deadline; the machine discharges it on the clock; the ledger — not the tool — confirms it; and silence is not a permitted outcome.
Not affiliated with Cloudflare. Python 3.11+, standard library only.
30-second setup
pip install git+https://github.com/wayneColt/granted
granted mint --type report --id 2026-09-30 --by "owner keystroke" \
--not-after 2026-10-01T00:00:00Z --allow send
granted status --type report --id 2026-09-30
granted discharge --type report --id 2026-09-30 --action send --exec ./send_report.shThe last line prints one of discharged, refused, uncertain, expired with a reason, and exits 0, 3, 4 or 5 to match. Run it again: refused: consumed. Grants are files in ~/.granted/grants/; the ledger is ~/.granted/ledger.jsonl. GRANTED_HOME, GRANTED_STORE, GRANTED_LEDGER move them.
Related MCP server: MCP Customer Support Demo
Five clauses
A decision is a grant. One object, once. The human's decision becomes one file with the object's type and id in it. There is no grant for "things like this".
A grant carries its deadline.
not_afteris required;not_beforeis optional. A grant with no deadline is a standing permission, and the validator refuses it.The executor discharges inside the grant and never outside it. It may never mint. The library has no path from an executor, an MCP client, or a runner to a new grant. Minting is a CLI verb, run by a hand.
Every outcome is loud.
discharged | refused | uncertain | expired, each with a reason from a closed vocabulary, each written to the ledger as anoutcomerow, each returned — never raised — to the caller. A refusal leaves a row. An expiry leaves a row.Confirmation comes from the substrate. An
intentrow before the action, aresultrow the executor writes after it, and the grant's ownconsumedflag and receipt, re-read from disk. The executor's return value alone confirms nothing: success with no result row isuncertain.
What stays human
The primitive is for decisions already made. It does not make them, rank them, or infer them. These verbs never ride a grant by default, and nothing here will discharge them unless a human mints for that exact object, on purpose:
hire
negotiate
sign
pay
first contact with no prior thread
not_scope exists so the grant can say so in words the approver reads.
The grant
{
"kind": "granted/1",
"decision": "APPROVE_ONCE",
"object": {"type": "report", "id": "2026-09-30"},
"scope": "send the weekly report to the list it went to last week",
"not_scope": ["any other recipient", "any attachment not already in the draft"],
"allowed_actions": ["send"],
"not_before": null,
"not_after": "2026-10-01T00:00:00Z",
"minted_by": "owner keystroke",
"minted_at": "2026-09-20T12:00:00Z",
"consumed": false,
"consumed_at": null,
"receipt": null
}field | meaning |
|
|
|
|
| words for the human; the library does not parse them |
| if non-empty, a discharge must name one of them |
| ISO 8601 with a zone. Naive stamps are refused. |
| the hand. A string, not an identity system. |
| written by the store; the receipt names the discharge that spent it and its outcome |
The JSON Schema is at docs/grant.schema.json. One file per grant; the store finds the live one for an object and reports none | consumed | not_yet | expired | unreadable when there is none.
The state machine
find grant ──none/consumed/not_yet/unreadable──▶ refused
│ └──expired──────────────────────────▶ expired
▼
write intent row ──fails──▶ refused (ledger_unwritable; nothing runs)
▼
claim grant (receipt: pending) ──lost──▶ refused
▼
executor(grant)
├─ raises Uncertain ─────────────────────▶ uncertain (spent)
├─ raises ExecutorFailed / anything ────▶ refused (spent: effects unknown)
├─ returns falsy ───────────────────────▶ refused (executor_declined; grant stays live)
└─ returns truthy
▼
ledger.confirms(object)? no ─────────▶ uncertain (no_result_row; spent)
▼ yes, and the row says ok
settle receipt, re-read grant ──mismatch─▶ uncertain (spent)
▼
discharged (APPROVE_ONCE: spent)Once the executor has been called, the grant is spent whatever happens next — except when the executor positively declines, which means "nothing done, try me later". Retrying anything else is a human act: mint again. The full reason vocabulary is granted.discharge.REASONS.
The executor
The library never knows how to send an email or deploy anything. The host supplies executor(grant):
it | means |
returns truthy | claims success — and must have written a |
returns falsy | did nothing; the grant stays live |
raises | cannot say whether it happened |
raises | failed part-way; effects unknown |
raises anything else | a bug; treated like a failure |
from granted import Store, Ledger, discharge
store, ledger = Store("~/.granted/grants"), Ledger("~/.granted/ledger.jsonl")
def send_report(grant):
message_id = mailer.send(draft_for(grant.object_id)) # the host's code
ledger.result(grant.object, ok=True, receipt={"message_id": message_id})
return True
outcome = discharge(store, ledger, {"type": "report", "id": "2026-09-30"}, send_report, action="send")
print(outcome) # discharged: confirmed (report/2026-09-30) -- result row line 42The shell executor behind granted discharge --exec writes the result row itself (exit status, stdout and stderr tails). Exit 0 claims success; exit 75 (EX_TEMPFAIL) declines; any other exit is a failure; a --timeout is uncertain. The command sees GRANTED_OBJECT_TYPE, GRANTED_OBJECT_ID, GRANTED_DISCHARGE_ID, GRANTED_GRANT_PATH, GRANTED_LEDGER, GRANTED_NOT_AFTER in its environment.
CLI
granted mint --type T --id I --by "who" --not-after ISO [--not-before ISO] [--decision D] [--scope ..] [--not-scope ..] [--allow ..]
granted status --type T --id I
granted discharge --type T --id I --exec "cmd" [--action A] [--timeout S]
granted ledger [--tail N] [--id I]
granted lag [FILE|-]
granted mcp [--executor NAME=COMMAND ...]--json on any of them. python -m granted works from a clone without installing.
MCP server
granted mcp --executor send_report=./send_report.shstdio, JSON-RPC 2.0, no third-party code. Implements initialize, ping, tools/list, tools/call. Three tools:
tool | does |
|
|
| discharge through an executor the host registered by name; returns one of the four outcomes |
| the last N rows |
There is deliberately no mint. Minting is the human's act and lives in the CLI. An agent on this server can learn, discharge, and read back; it cannot widen its own permissions and cannot hand the executor a command of its own.
Client configuration, for a host that takes the usual shape:
{"mcpServers": {"granted": {"command": "granted", "args": ["mcp", "--executor", "send_report=./send_report.sh"]}}}The clock
adapters/systemd/ is a granted-discharge@.timer + .service pair and a runner. The timer ticks every 15 minutes; the grant's own window decides; the runner stops the timer once there is nothing left to ask. uncertain is left as a failed unit on purpose.
Measuring the lag
The motivation is a number. Keep a JSONL of {"raised": ISO, "answered": ISO} for every time an agent waited on a keystroke, then:
$ granted lag asks.jsonl
n=10 open=1 bad=1 p50=10.0h p80=72.0h max=240.0hNearest-rank percentiles. open is the count still waiting. If p50 is minutes, use deferred approval. If it is days, mint.
Status
Surface | Status |
Core: | yes; 63 tests under |
CLI: | yes |
MCP server (stdio; | yes; no |
systemd adapter | yes (unit, timer, runner) |
cloudflare-os Gatekeeper | design note + TypeScript skeleton; not compiled or deployed |
Door (signed admission endpoint) | |
Signed intents in the CLI / MCP path | not in 0.1.0; both trust the process boundary |
Store across hosts | not in 0.1.0; a directory on one disk, one lock file per grant |
Windows | untested |
Build
git clone https://github.com/wayneColt/granted
cd granted
python -m unittest -v
python -m granted --helpNo dependencies. License: Apache-2.0 OR MIT.
What this is not
Not an approval queue. Nothing waits here; a grant exists or it does not.
Not an identity system.
minted_byis a string the human types. The door contract indocs/DOOR.mdsays where signatures go; this release has none.Not a scheduler. The systemd timer is a tick; the deadline is the grant's.
Not a policy engine. It cannot tell a safe action from a dangerous one. That is what the human did when they minted, and what
not_scopeis for.
Release drafts (X, blog) live in RELEASE_DRAFTS.md and wait for an operator keystroke.
Available Tools
3 toolsgrant_dischargeA
Carry out the decision a human already recorded for this object, through an executor the host registered by name. Returns one of discharged | refused | uncertain | expired with a reason. Cannot mint; cannot run anything the host did not register.
| Name | Required | Description | Default |
|---|---|---|---|
| id | Yes | object id, e.g. 2026-09-30 | |
| type | Yes | object type, e.g. report | |
| action | No | which allowed action, if the grant names any | |
| executor | Yes | a registered executor name |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations provided, the description carries the behavioral disclosure burden. It discloses the full outcome set ('discharged | refused | uncertain | expired with a reason') and major invariants ('Cannot mint; cannot run anything the host did not register'), which is substantially more transparent than typical mutation tools.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Three tight sentences with no filler: purpose is front-loaded, return values are enumerated, and hard constraints are stated. Every sentence adds information an agent needs to decide whether and how to call the tool.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Despite having no output schema and no annotations, the description supplies the outcome vocabulary, the precondition, and the tool's boundaries. It is adequate for a small 4-parameter tool, though it does not explicitly describe error or not-found behavior.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The schema already has 100% description coverage for all four parameters, so the baseline is 3. The description reinforces that executor must be a registered name and links type/id to a recorded decision, but it does not add meaningful format or value-level semantics beyond what the schema provides.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description names a specific verb ('carry out'), a specific resource ('the decision a human already recorded for this object'), and the mechanism ('executor the host registered by name'). It also distinguishes the tool from status/list siblings by enumerating possible return statuses and stating 'Cannot mint.'
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The usage context is clear: this tool executes an already-recorded human decision through a registered executor. It also gives explicit exclusions ('Cannot mint; cannot run anything the host did not register'), but it does not explicitly point to sibling tools such as grant_status or ledger_tail as alternatives.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
grant_statusB
Is there a live grant for this object? Returns live | none | consumed | not_yet | expired | unreadable, the grant on file, and the last ledger rows.
| Name | Required | Description | Default |
|---|---|---|---|
| id | Yes | object id, e.g. 2026-09-30 | |
| type | Yes | object type, e.g. report |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the full burden of behavioral disclosure. It does disclose the possible status values and the returned components, including an 'unreadable' case, which is useful. However, it does not explicitly state read-only behavior, permission implications, or other edge conditions beyond the status list.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is compact and front-loaded: a clear question followed by a concise return-value list. It contains no filler or schema repetition, though the dense status enumeration could be slightly better structured.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a low-complexity two-parameter tool with no output schema, the description is mostly complete: it names the returned statuses and the accompanying grant/ledger data. It is not fully complete because it omits sibling routing and behavioral caveats, but an agent can reasonably invoke it from this description.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so the schema already documents the two required parameters with examples. The tool description adds only a generic 'this object' reference and no additional parameter semantics, formats, or constraints.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly identifies the tool's goal: checking whether a live grant exists for an object, and it lists the exact statuses returned. It does not explicitly contrast itself with sibling tools grant_discharge or ledger_tail, so it misses the highest-level sibling differentiation.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
No guidance is given about when to use this tool versus grant_discharge or ledger_tail. An agent must infer when checking grant status is appropriate, and there are no exclusions or alternative conditions stated.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
ledger_tailC
The last N ledger rows (intent, result, outcome), optionally for one object id.
| Name | Required | Description | Default |
|---|---|---|---|
| n | No | ||
| object_id | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
No annotations are provided, so the description carries the full burden of behavioral disclosure. It does not state whether the operation is read-only, how rows are ordered, whether there are side effects, or what happens with an invalid object id. It only describes the shape of the returned data.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is a single sentence that front-loads the main output (last N ledger rows) and then states the optional filter. Every word contributes meaning, and there is no redundancy.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a tool with no annotationsinations and no output schema, the description is too thin. It does not mention ordering, return shape beyond field names, potential errors, or how this tool relates to the sibling grant_status and grant_discharge tools, leaving an agent without enough context to invoke it confidently.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 0%, but the description does clarify that n refers to the number of rows and object_id is an optional filter for a single object. It does not explain what kind of object_id is expected or how filtering interacts with the last N rows, leaving some ambiguity.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description identifies the resource (ledger rows), the fields included (intent, result, outcome), and an optional filter (object id), which makes the core purpose clear. However, it lacks an explicit verb like 'retrieve' or 'list', and does not distinguish itself from the sibling tools by name.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
There is no guidance on when to use this tool versus grant_status or grant_dischargeaging. The phrase 'optionally for one object id' describes a parameter behavior, not a use case or exclusion, leaving tool selection to inference.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
3 tool updates
v0.1.0- First observed
grant_discharge - First observed
grant_status - First observed
ledger_tail
TDQS
Scored across 3 tools
Each tool has a clearly distinct purpose: grant_status checks the current state of a grant, grant_discharge executes a recorded human decision, and ledger_tail provides raw ledger history. Even though status and ledger_tail both touch ledger rows, their focus and output differ enough to avoid confusion.
All tool names use lowercase snake_case and follow a consistent 'domain_action/noun' style, such as grant_status, grant_discharge, and ledger_tail. They deviate from a strict verb_noun pattern but are still predictable and internally consistent.
With only three tools, the surface is tightly scoped to the server's apparent purpose: checking grant state, discharging grants, and auditing the ledger. Each tool covers a distinct operation and none feels redundant or unnecessary.
The server intentionally does not create or mint grants—those decisions are recorded by humans and executed via grant_discharge. Within that scope, status checking, execution, and ledger auditing form a complete workflow with no obvious dead ends.
Maintenance
Related MCP Connectors
Workflow diagnostics, capability routing, and x402 settlement for MCP-compatible agents.
Remote MCP for A2A caller identity, scope policy, verdict receipts, and audit history.
MCP Server for agents to onboard, pay, and provision services autonomously with InFlow
Agent-native insurance quoting protocol — sandbox, MCP + REST, eligibility pre-flight
Related MCP Servers
- FlicenseAqualityDmaintenanceProvides double-entry accounting ledger creation, transaction recording, and financial reporting capabilities via MCP.7-
- FlicenseNot gradedqualityCmaintenanceEnables customer support operations such as order lookup, store credit, refunds, and audit log review through an agent using safe, typed MCP tools.-
- FlicenseNot gradedqualityDmaintenanceProvides MCP tools to enforce spend policies (allow, deny, step-up, allowlist) on agent wallets with an immutable audit trail.-

vantic-mcpofficial
AlicenseNot gradedqualityBmaintenanceEnables MCP hosts to verify agent spending mandates and receipts, providing stateless tools for authorization, chain verification, credential verification, and DID resolution.Apache 2.0