One request, held for 00:00
It has everything it needs to issue the refund. It is not going to. May I? holds the whole request, the exact action and the evidence behind it, until one authenticated human reads it and answers.
Open source · Apache-2.0 · MCP-native · hosted or on your own metal
What she is looking at
She approves. The receipt she gets back is bound to this action, these two files, her identity, and the policy as it read at that moment. Your executor verifies the receipt and only then moves the money. If the agent edits the amount or swaps a file afterwards, the receipt stops validating, and nothing happens.
The action is exact.
You see the specific call the agent intends to make, with every argument that carries weight, as it will be made. Nobody is summarising anything for you.
The evidence is frozen.
The PDFs and images behind the case are hashed the moment the request goes pending. You review the ones the receipt will name.
The person is named.
It arrives as a notification on one approver's phone, or in the browser if they are not on the app. Same request, same review, either way.
Nothing has run.
May I? never performs the action. It is holding a question. The hand on the lever stays yours.
The whole of it
Two calls, both small
The agent side · MCP
const receipt = await mayi.ask({
action: "payments.refund",
args: { amount: 4800000, to: "acct_8841" },
because: "Duplicate capture on #99812.",
evidence: [settlementPdf, ticketPng],
approver: "dana@northwind",
deadline: "30m",
});
// blocks here. no polling, no webhook to run.
// resolves approved, denied, or expired.The executor side · verify
app.post("/refund", async (req, res) => {
const ok = await mayi.verify(req.body.receipt, {
action: "payments.refund",
args: req.body.args,
});
if (!ok) return res.status(403).end();
await stripe.refunds.create(req.body.args);
});
// the args are in the signature. lie and it fails.Verification is offline: a public key and a hash comparison. Your executor does not call us to check a receipt, so if May I? is down, work that is already approved still runs. Only new questions wait.
The receipt
payments.refund
The call and every argument that carries weight.
2 files · sha256
The exact bytes she opened. Filenames are easy to reuse; bytes are not.
dana@northwind
Authenticated at the moment of the answer.
rev 41
The rules as they read on the day she answered.
An audit log says an approval happened. A receipt says what was approved, and refuses to say it about anything else. Most of the value is in that second half.
Try to move a decimal
The digest is over the action and its arguments. Change one character and every executor holding the public key knows independently.
Three ways it goes wrong without one
The quiet edit
The agent gets approval for USD 480, then issues USD 48,000. The amount is inside the signature. Your executor rejects the receipt before the payment processor ever hears about it.
The swapped file
Approval was granted against one settlement report. A different PDF shows up at execution time. The hash in the receipt names the first one, so the second one has no approval attached to it.
The replay
A receipt for last Tuesday's refund is presented again this Tuesday. It is single-use and scoped to one request id. The second attempt is not an approval, it is a duplicate.
Move money
Refunds, payouts, credits, anything with a settlement on the other side.
Ship code
Merges to protected branches, production deploys, migrations that drop things.
Touch a customer
Account closures, plan changes, anything that sends mail in your name.
Grant access
Role changes, key issuance, adding a principal to something that matters.
Delete
Records, buckets, tenants. The category where undo is a support ticket.
Sign
Contracts, invoices, anything where your name goes on the bottom of the page.
The rule of thumb: if a mistake there costs you a phone call, an apology, or a lawyer, it is worth thirty seconds of someone's attention first.
Hosted
Start asking in an afternoon.
Push notifications, the apps, key rotation, and the pager are ours. Free while you are small enough that a person can still read every request.
Start freeSelf-hosted
One service and a database.
Same code, your infrastructure, your signing keys, no request ever leaving your network. Apache-2.0, and the receipts verify the same either way.
Read the sourceAsked before
No. It holds requests and issues receipts. Your executor already has the credentials and keeps them; it just checks a signature before it runs. May I? never gets a key to the thing it is protecting.
The request expires on the deadline the agent asked for, and expiry is a denial. Silence never becomes a yes.
The agent writes the because line, and that is the one field it fully controls. The call, the arguments and the file hashes are all rendered from the request itself, so the persuasion has nowhere to reach. She reads the action, not the argument for it.
The request is pending within a few hundred milliseconds and the notification lands about as fast as any other push. The rest of the latency is a human reading, which is the part you are paying for.
Run it there. Apache-2.0, one service and a Postgres database. The hosted version is the same code with the pager on our side.
Hosted, or self-hosted on your own infrastructure. Either way, May I? holds the question and keeps the proof, and never touches the thing itself.