phygital-key-mcp
OfficialPhygital Key MCP is an MCP server that lets AI agents request, manage, and record human-approved actions signed with a passkey, while never allowing the agent to approve itself.
Request approval for one exact action from a person using a passkey, fingerprint, face, or hardware key.
Wait for and retrieve the person's decision, including the signed approval string when approved.
Cancel a pending approval request or revoke an approved standing approval.
Request a standing approval for repeated actions within fixed limits such as payee, amount, max uses, and days.
Use a standing approval to get a one-minute use token for one exact request inside its limits.
Record a service's signed receipt after an action so the person can see it was done.
Optionally bind an approval to the agent's own key so only that agent can use it.
The agent cannot approve, sign, or change a request after approval; only the person's key can approve.
What it is
Phygital Key MCP is a Phygital Key product for AI apps that use MCP (Model Context Protocol, the common way AI apps plug in tools): Claude Code, Claude Desktop and any other MCP app. It is one program, phygital-key-mcp. It gives your AI app its tools and runs the approval page, the page where you say yes or no.
Before an AI agent deploys code, pays, deletes or sends something, it calls request_approval with the exact thing it wants to do. You read it on the approval page and approve with a passkey: your fingerprint, face, PIN or a hardware security key. Your key signs that exact request (WebAuthn, ES256 on P-256). The agent passes the signed approval to the service that does the work, and the service checks it itself, offline, before it acts.
The agent has no tool that can approve. If the request changes by even one character, the service refuses it with REQUEST_MISMATCH.
The approval page, the keys, standing approvals, receipts, the audit log, the secure install and signed updates come from the shared base, Phygital-Key/phygital-key. This repository adds the MCP part. For agents that use A2A, see Phygital Key A2A.
Related MCP server: Witnessed
Why it exists
An AI agent with tools can deploy code, move money and send messages. Most AI apps ask "Allow?" with a button inside the app. Nobody signs that click, so the service on the other end can't tell whether a person said yes, or what they said yes to.
Phygital Key MCP gives the service proof it can check:
Promise | How |
Can't be faked | Only your key can sign (ES256 on P-256). The secret part of the key never leaves your phone, your laptop's security chip or your security key. The AI never holds a key, none of the six tools can approve, and the gate (this program) has no key that can sign approvals. |
Can't be changed | Your signature covers the exact request, written in one fixed form (RFC 8785). The service rebuilds the request it is about to run and compares the two, character by character. One changed character gives |
Can't be reused | Each approval names one service ( |
Can't be phished | Passkeys only sign for the real address of your approval page. A fake copy on another address gets nothing it can use. |
Can't be copied and used by someone else | An AI app can ask for an approval tied to its own key ( |
Out of the AI's reach | With the secure install, the gate runs as its own locked-down user on your computer ( |
Checked offline | The service only needs your public keys and a few lines of code. Nothing calls home. No account, no cloud. |
How it works
sequenceDiagram
participant A as AI app (MCP client)
participant G as Gate (phygital-key-mcp)
participant P as You + passkey
participant S as Service (verifier)
A->>G: request_approval(request, aud, level)
G-->>A: id, approve_url, status pending
G->>P: approval page shows every detail
P->>G: Approve with fingerprint, face, PIN or touch
A->>G: get_approval(id)
G-->>A: status approved, signed approval (pka.1)
A->>S: action + Phygital-Approval header
S->>S: check audience, time, exact request, level, key, signature, first use
S-->>A: done (and a signed receipt), or refused with a code
A->>G: record_receipt(id, receipt)Four roles, and what each one can and can't do:
Who | Decides | Can never |
You (the person who approves) | Yes or no, on the exact request shown | Be skipped: no key, no approval |
The agent (your AI app) | What to ask for, using the six tools | Approve, sign, or change a request after you signed it |
The gate ( | Shows the request and collects your signature | Sign an approval by itself |
The service (the one that checks, also called the verifier) | Its own name (audience), the request it runs, how strong a yes each action needs, which keys it trusts | Accept a request you did not sign |
More: how it works, rules for AI agents, the open spec.
What you see
The gate builds the approval page from the request itself, never from text the agent wrote. The page:
shows every field;
warns you about look-alike letters, invisible characters and extra spaces;
shows a short fingerprint (SHA-256) of the exact text you sign;
checks that what it shows is what will be signed, before it asks your key.
Text inside a request, like "already approved" or "SYSTEM: ...", is shown as plain text and changes nothing. How to read the page: approving. How the page protects you: approval page.
Install
Where | Command |
macOS, Linux (script) |
|
macOS, Linux (Homebrew) |
|
Windows (Scoop) |
|
Anywhere with Rust |
|
Server (Docker) |
|
Secure install (macOS, Linux) | |
By hand | Download from Releases. Each file has a |
The first release under this name was v0.4.0. Each release has files for Linux, macOS and Windows, the public container image ghcr.io/phygital-key/phygital-key-mcp (linux/amd64, linux/arm64), the Homebrew formula and the Scoop manifest.
The script downloads the latest release for your computer and checks two things before it installs anything. First, that the release's file list (release2.txt) is signed by a release key built into the script, and is still in date. Second, that the download matches its SHA-256. Then it writes one file, ~/.local/bin/phygital-key-mcp. It needs curl, tar and openssl. To update later: phygital-key-mcp update.
The container runs phygital-key-mcp --bind 0.0.0.0 serve as user 65532, with its data in /data (PHYGITAL_KEY_MCP_HOME=/data). Add your key with docker exec -it phygital-key-mcp phygital-key-mcp enroll, and get the access token with docker exec -it phygital-key-mcp phygital-key-mcp token. Only open the port on 127.0.0.1, or put an HTTPS proxy in front and pass --origin https://your.host (your own server).
Every way to install and update, and which user each one runs as: getting started, updates.
Try it in 2 minutes
phygital-key-mcp demoThe demo starts the gate on http://localhost:7468, and a pretend service on http://localhost:7469 that only acts when it gets a valid approval. It prints two things:
a one-time link (good for 10 minutes) to add your passkey: Touch ID, Windows Hello, your phone or a security key;
the line that connects Claude Code, with the access token filled in:
claude mcp add --transport http phygital-key-mcp http://localhost:7468/mcp --header "Authorization: Bearer <token>"Then ask your AI app: "Deploy api 2.4.1 to production through the demo service at http://localhost:7469." An approval page opens. Approve, and the demo service "deploys" (nothing real happens). Deny, and it refuses. Ask it to deploy 2.4.2 with the same approval, and the service refuses with REQUEST_MISMATCH.
More things to try, from payments to standing approvals: things to ask your AI.
Connect your AI app
There are two ways to connect. Both give the same six tools.
If your app | Do this |
Starts tools on your computer itself (Claude Code, Claude Desktop and most others) | Use the command |
Connects to a web address | Run |
The first way (stdio), in Claude Code:
claude mcp add phygital-key-mcp -- phygital-key-mcp stdioClaude Desktop (claude_desktop_config.json) and most other apps use this shape:
{ "mcpServers": { "phygital-key-mcp": { "command": "phygital-key-mcp", "args": ["stdio"] } } }The second way (HTTP), in Claude Code (phygital-key-mcp token prints this line with your token filled in):
claude mcp add --transport http phygital-key-mcp http://localhost:7468/mcp --header "Authorization: Bearer <token>"Add your key once, from your own terminal, with phygital-key-mcp enroll. It gives a one-time link, good for 10 minutes. In stdio mode the gate never prints that link itself, because AI apps keep that output in logs the AI could read.
The HTTP address refuses calls without the token, calls from web pages, and calls with a wrong Host header (a trick called DNS rebinding). The server calls itself phygital-key-mcp (title Phygital Key MCP) with the program's version. Every app, header, option and refusal: connect your AI app.
The six tools
The agent can ask, wait, cancel and report back. It can never approve.
Tool | What it does |
| Asks you to approve one exact request for one service, at level |
| Waits for your answer and gets the signed approval |
| Withdraws a request that is still waiting, or cancels a standing approval |
| Asks for a limited, repeated permission (up to 1000 uses, up to 30 days) |
| Gets a one-minute use token for one action inside those limits |
| Passes back the service's signed "done", so you see it on the approval page |
Each answer has a status (pending, approved, denied, cancelled, expired) and a next line that tells the agent what to do. Only approved with an approval string means yes.
When the AI app supports it, request_approval asks the app to open the approval page for you (MCP URL elicitation). Saying yes to that prompt only opens the page. It approves nothing.
For people building agents: rules for AI agents, tool reference (every input, answer and error text), prompt snippets, things to ask your AI.
Check approvals in your service
This part is for whoever runs the service. The service does the checking. It compares the approval with the request it is about to run, built from its own data. The check is the same no matter which product made the approval.
Rust, with the phygital-key-verify library from the base:
use phygital_key_verify::{Config, Keyring, Level, Verifier};
let verifier = Verifier::new(
"https://deploy.example.com", // your audience
Keyring::from_json(&std::fs::read_to_string("keyring.json")?)?, // trusted keys: `phygital-key-mcp keys`
Config { origins: vec!["https://approve.example.com".into()], rp_ids: vec!["approve.example.com".into()] },
);
let request = serde_json::json!({"type": "deploy", "params": {"service": "api", "env": "production", "version": "2.4.1"}});
verifier.verify(&approval, &request, Level::High)?; // refuses on any doubt
// ...now deploy.Any language: run the command line and look at the exit code.
phygital-key-mcp verify --approval "$APPROVAL" \
--request '{"type":"deploy","params":{"service":"api","env":"production","version":"2.4.1"}}' \
--aud https://deploy.example.com --level highExit 0 with {"ok": true, ...} means go ahead. Exit 1 with {"ok": false, "error": "REQUEST_MISMATCH"} means no. Used approvals are saved in a local file, so each one works only once. There is also a JavaScript checker with no dependencies, and it passes the same 141 shared test cases (JavaScript).
Full guide: integrate. By language: Rust, JavaScript, command line.
Safety, verification and authority
What the service checks, in order
When in doubt, the checker says no. The first check that fails refuses the approval, with a code that says why.
Step | What is checked | Refused with |
1 | The approval is there, is clean JSON, has only known fields, and isn't too big or too deep |
|
2 | It was approved for this service ( |
|
3 | It is in time: not before |
|
4 | It is the exact request, byte for byte, in the fixed form |
|
5 | It is a strong enough yes for this action ( |
|
6 | It was signed by a key in your keyring (your list of trusted keys), for the right site |
|
7 | The passkey's signed answer (WebAuthn assertion): type, challenge, origin, site hash and ES256 signature |
|
8 | The "person was there" and "person was verified" flags match the level |
|
9 | It is the first use (used approvals are remembered) |
|
Every code, why it happens and what to do: error codes. What an agent should do with each one: when the service refuses.
The cryptography
Your key signs a challenge that locks in every detail of the approval:
$$\text{challenge} = \mathrm{SHA256}\big(\texttt{"phygital-key/approval/v1"} ,|, \texttt{0x00} ,|, \mathrm{JCS}(\text{claims})\big)$$
The signature is ES256 (ECDSA on P-256 with SHA-256), made by your passkey (a WebAuthn assertion).
Hashes are SHA-256.
The fixed form is RFC 8785 (JCS): strict JSON, only safe whole numbers, no decimals, and text in Unicode NFC. Other text is refused, never rewritten.
Receipts and use tokens are ES256 JWS. The audit log is a SHA-256 hash chain, so a changed entry shows. Release lists are signed with ECDSA P-256, and also with ML-DSA-65, a signature built to resist future quantum computers.
Deep dives: canonical form, WebAuthn checks, key history.
Approvals aresigned, not encrypted. The gate, the service and anyone holding an approval can read the request. Keep passwords and card numbers out of requests. Use HTTPS for any gate that isn't on localhost. Why: decision 33.
Every key you add or remove is itself an approval, signed by a key you already trust. So a service can check your whole key history starting from your first key's fingerprint (phygital-key-mcp keys --check <fingerprint>).
Out of the AI's reach
An AI agent on your computer runs as you, so it could change files you own, including a gate you installed yourself. The secure install puts the gate where the AI can't reach it (macOS and Linux). Open a new terminal and run:
curl -q -fsSL https://raw.githubusercontent.com/Phygital-Key/phygital-key-mcp/v0.6.1/install-service.sh | sudo bashIt downloads the official release itself and checks it against the signed release list. It installs it into folders only the administrator can change, and runs it as its own user. If any other program is already using port 7468, it refuses to run. Then:
phygital-key-mcp-admin enroll # add your key
phygital-key-mcp-admin token # the line to paste into your AI app
phygital-key-mcp-admin status # check that the real gate is the one answeringmacOS | Linux | |
Gate user |
|
|
Program |
|
|
Data |
|
|
Background services | launchd | systemd |
On Linux, systemd holds the gate's port while the gate restarts for an update, so no other program can grab it in between.
The secure install only protects the gate if the AI can't get your administrator password. Run it from a terminal you trust, don't let your user runsudo without a password, and never type that password where the AI can see it. Each admin command asks for it.
Details, every folder and every refusal: secure install.
Signed automatic updates
The secure install checks for updates once a day. It installs a new release only if all of this is true:
its file list (
release2.txt) is signed by the release keys built into this program and its installers (ECDSA P-256, plus ML-DSA-65 once that key exists);the list is still in date, and not older than one this computer already saw;
it is newer than the version you have.
The signing key is a secret kept in this repository's GitHub environment release, and only a release run started from a v* tag can use it.
The update waits while a standing approval is active (7 days at most), because a restart would end it. If the new gate doesn't start and pass its checks, the previous version comes back. The update runs as the administrator with a clean setup, so your own settings like ~/.curlrc and PATH are never used.
phygital-key-mcp-admin status shows the last result, phygital-key-mcp-admin update updates now, and --no-auto-update turns automatic updates off. How updates are protected: the update channel. Every way to update: updates.
How it is tested
What | Where | How |
End to end, and attacks | This repository | 124 checks with the real release program, a real Chromium browser with a pretend passkey, the official MCP TypeScript SDK and the demo service. They include attacks: changed requests, prompt injection, forged signatures, reused approvals, DNS rebinding, key rollback |
Secure install | This repository | 36 checks on Linux, 32 on macOS. It is installed for real in CI, then attacked as the user. Also checked: the update job (started by systemd and launchd), refusing a badly signed release, a planted |
Container | This repository | Builds the image, starts the gate, checks user |
Generated files | This repository | The files made from the base's templates must match the ones saved here |
Shared test cases | Base | 141 cases that both the Rust and the JavaScript checkers must pass, plus Google's Wycheproof cases, a cross-check with SimpleWebAuthn, property tests, fuzzing and mutation testing |
Security reviews | Base | 9 separate AI security reviews. Every problem found was fixed or written down as a known limit |
CI runs on every change on Linux, macOS and Windows, and again after each daily base update. Job by job: testing. The engine's tests: testing in the base. Found a problem? Report it privately.
Standing approvals
For small actions that repeat, you can say yes once, with limits:
Allow payment to ACME Ltd in EUR, up to 20.00 each, at most 5 times, for 7 days.
The agent asks with request_standing_approval (up to 1000 uses, up to 30 days). For each action it calls use_standing_approval and gets a use token that lasts one minute. It sends that token with the standing approval (the Phygital-Approval and Phygital-Use headers). The service checks both and counts the uses itself. A different payee, a bigger amount or an extra field is refused with NOT_ALLOWED. Standing approvals always need your fingerprint, face or PIN.
You can cancel it on the approval page, or ask the agent to call cancel_approval. New uses stop at once. One more touch signs a cancellation notice that services read from /.well-known/phygital-key-revoked.json. Guides: for people, for services, cancellations.
Receipts
After it acts, a service can send back a signed receipt ("done" or "failed"). The agent passes it on with record_receipt. Your approval page shows it only if it was signed by the key you saved for that service:
phygital-key-mcp trust-service --aud https://deploy.example.com --key <its receipt_public_key>Format and checks: receipts.
All the guides
The shared guides are in the base repository: all the guides (approving, keys, standing approvals, secure install, checking approvals in your service, security). Guides for one product (connecting AI apps, its tools, prompts) are in that product's repository.
This product's guides: Connect your AI app · MCP tools · Agents guide · Things to ask · Prompt snippets · Testing
This repository
Path | What |
The product: its name, program, port and where releases come from, plus the MCP part (address | |
The six MCP tools, and the instructions the server sends to every app | |
124 end-to-end and attack checks: release program, Chromium with a pretend passkey, the official MCP SDK, the demo service | |
The MCP guides: connect your AI, tools, agents, examples, testing | |
Prompt snippets for your agent's instructions | |
The container image | |
The public release keys that every update and installer checks against | |
| Made from the base's templates for this product with |
Everything else comes from the base, Phygital-Key/phygital-key: the approval page, keys, standing approvals, receipts, audit log, secure install, signed updates and the checker. Every day the sync-base workflow moves this product to the base's latest version. It keeps the change only if the tests and the 124 end-to-end checks pass, then saves it and runs the full CI. How the base and the products fit together: architecture.
Changes: CHANGELOG.md. Security problems: SECURITY.md. Contributing: CONTRIBUTING.md.
Privacy
The gate keeps its data in one folder: ~/.phygital-key-mcp, or after the secure install, a folder only the gate's own user can read. In it:
the public keyring (your public keys);
an audit log, chained so changes show (ids, hashes and results, never the request itself);
saved service keys;
signed cancellations;
the access token.
Requests that are still waiting live only in memory. No analytics, no accounts, no tracking. The fonts are built into the program, so the approval page loads nothing from the internet. More: privacy.
Open source and credit
Phygital Key MCP is free and open source under the Apache-2.0 license. You can use it at home or at work, change it, put it in a product and sell that product. You can read every line of code that handles your approvals, and check the release you run against its signed SHA-256 list.
Keep the NOTICE file, with the line "Created by Phygital-Key", in every copy or changed version (Apache-2.0, section 4(d)).
Contributions are welcome under the same license: see CONTRIBUTING.md. Shared behaviour is changed in the base, never here.
No warranty
Provided "as is", without warranty of any kind, as the license says. It hasn't been audited yet: try it with the demo first, read the threat model, and keep a second key added as a backup.
License
Apache-2.0. Created by Phygital-Key.
Available Tools
6 toolscancel_approvalADestructive
Withdraw a request that is still waiting, or revoke an approved standing approval. Cannot be undone. A revoke stops new uses at once and returns sign_url: show it to the person, so services refuse the standing approval too.
| Name | Required | Description | Default |
|---|---|---|---|
| id | Yes | The id from request_approval. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already flag destructiveHint=true and idempotentHint=false, but the description adds substantial context beyond them: the operation is irreversible, a revoke takes effect immediately, and it returns a sign_url that must be shown to the person for downstream services to refuse. That downstream action is exactly the kind of behavioral detail annotations cannot convey.
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; the action and its scope are front-loaded, followed by irreversibility and the return-value consequence in descending order of importance.
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?
With no output schema, the description correctly compensates by disclosing the returned sign_url and what to do with it. Combined with the irreversibility warning, it fully equips an agent to invoke and act on this tool.
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 coverage is 100% and the single 'id' parameter is already documented as coming from request_approval, so the schema does the heavy lifting. The description adds no syntax, format, or sourcing detail for the parameter beyond what is already structured, so the baseline 3 applies.
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?
States precise verbs (withdraw, revoke) and distinguishes the two target resources: a pending request versus an approved standing approval. An agent can tell it apart from get_approval or use_standing_approval without opening any schema.
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?
Gives clear conditions for each mode ('a request that is still waiting' vs 'an approved standing approval'), which is strong context. It does not explicitly name which sibling to use instead when the approval is neither pending nor standing, so it stops short of full alternative routing.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_approvalARead-onlyIdempotent
Wait for the person's decision on a request. Returns status: pending (call again), approved (with the approval string to send with the action), denied, cancelled or expired (then do not act). Changes nothing; safe to call again.
| Name | Required | Description | Default |
|---|---|---|---|
| id | Yes | The id from request_approval. | |
| wait_seconds | No | Seconds to wait for the person to decide (0 to 120, default 60). |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint and idempotentHint, so "changes nothing; safe to call again" largely restates them; but the description adds genuinely new behavior by enumerating the possible status outcomes and explaining that approval yields a string that must accompany the subsequent action. It omits any note on timeout/latency behavior beyond the schema's wait_seconds.
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 compact sentences, front-loaded with the core action, then the return-value semantics, then the safety note. Every clause earns its place with 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?
There is no output schema, so the description must carry return-value meaning, and it does so fully by enumerating every status and the required follow-up action. Combined with 100% schema coverage, an agent has everything needed to call and interpret this tool.
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%: the id is already documented as coming from request_approval and wait_seconds already carries its 0-120/default-60 range, so the description adds no parameter-level detail beyond the schema. Baseline 3 is appropriate.
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 opens with a specific verb and resource ("Wait for the person's decision on a request") and ties the tool to its producer by noting the id comes from request_approval, which cleanly separates it from siblings like request_approval and cancel_approval. An agent can identify the polling role of this tool without inspecting the schema.
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?
It gives an explicit decision tree: on "pending" call again, on "approved" proceed with the returned approval string, and on "denied, cancelled or expired" do not act. That is precise when-to-call-again and when-to-stop guidance, the exact condition set an agent needs for a blocking/polling tool.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
record_receiptA
After the service acted, pass back the signed receipt it returned, so the person sees it was done. Only records it: one per approval, or per use of a standing approval. If no receipt key is pinned for that service, tell the person and don't retry.
| Name | Required | Description | Default |
|---|---|---|---|
| id | Yes | The id from request_approval. | |
| receipt | Yes | The receipt string the service returned (a signed JWT). |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare a non-read-only, non-idempotent, non-destructive, closed-world write. The description adds useful behavior beyond that: it only records, allows one receipt per approval or standing-approval use, and instructs the agent to tell the person and not retry if no receipt key is pinned.
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 short sentences, front-loaded with the trigger condition and action. Each sentence adds operational value: when to call, cardinality, and the no-key edge case.
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 two-parameter write tool with annotations and no output schema, the description covers when to use it and the important failure condition. Return behavior is not described, but 'only records it' gives enough signal for this simple operation.
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 coverage is 100%, and the schema already documents both `id` and `receipt` including that `id` comes from request_approval and `receipt` is a signed JWT. The description reinforces the receipt concept but does not add syntax or format details beyond the schema.
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?
States a specific verb and resource — record the signed receipt — and scopes it to after a service action for an approval or standing-approval use. This clearly distinguishes it from the approval request/get/cancel siblings.
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?
Gives clear usage context: call after the service acted, pass back the returned signed receipt, and do not retry when no receipt key is pinned. It lacks explicit naming of alternative sibling tools, so it falls short of full when/when-not/alternatives guidance.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
request_approvalA
Ask a person to approve one exact action with their passkey, fingerprint or hardware key. Returns an id and approve_url, not the decision: then call get_approval. For many similar actions, use request_standing_approval. Nothing is approved until the person signs.
| Name | Required | Description | Default |
|---|---|---|---|
| aud | Yes | Who will act on the yes, e.g. "https://deploy.example.com". The approval only works there. | |
| level | Yes | How strong the proof must be: "medium" (touch) or "high" (fingerprint, face or PIN). | |
| request | Yes | The exact request: {"type": "<lowercase name>", "params": {...}}. Use strings or integers, never floats. No secrets: the person sees it, and anyone holding the approval can read it. | |
| agent_key | No | Optional: your own P-256 public key (SubjectPublicKeyInfo DER, base64url). The approval is then bound to it: when you act, also send a proof signed with that key (header Phygital-Proof). A copy of the approval is useless to anyone else. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare this is a non-readonly, non-idempotent, non-destructive, closed-world operation. The description adds context annotations cannot convey: that the call returns an id and approve_url rather than the decision, and that no approval exists until the human signs. It stops short of covering auth/permission requirements or retry/expiry behavior, so a 4 rather than 5.
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?
Four short sentences, front-loaded with the core action, then return shape, then sibling routing, then the security invariant. Every sentence carries distinct, non-redundant information with zero filler.
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?
With no output schema, the description correctly supplies the return contract (id and approve_url, not the decision) and the async completion path via get_approval. Combined with 100% schema coverage, an agent has everything needed to call it correctly.
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 all four parameters (request, aud, level, agent_key) are already documented in the schema, including the medium/high level meanings and the no-secrets warning. The description adds no parameter syntax or format detail beyond the schema, so the baseline 3 applies.
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?
States a specific verb and resource with precise scope: "Ask a person to approve one exact action" via passkey/fingerprint/hardware key. It explicitly distinguishes itself from two siblings (get_approval and request_standing_approval), so an agent can route correctly without opening any schema.
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?
Gives explicit follow-up routing ("then call get_approval") and an explicit alternative with its selecting condition ("For many similar actions, use request_standing_approval"). It also states the precondition that nothing is approved until the person signs, leaving nothing to inference.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
request_standing_approvalA
Ask a person once to allow the same kind of action within fixed limits, up to max_uses times, for up to days days. Then call get_approval, and use_standing_approval for each action. For one action, use request_approval. Use sparingly: the person sees every limit.
| Name | Required | Description | Default |
|---|---|---|---|
| aud | Yes | Who will act on each use. | |
| days | Yes | How many days it lasts (1 to 30). | |
| vary | Yes | Params that may change, with limits: integers {"amount_cents": {"min": 1, "max": 2000}} (both required) or strings {"memo": {"one_of": ["coffee", "lunch"]}}. | |
| fixed | Yes | Params that must be exactly these values every time, e.g. {"to": "ACME Ltd", "currency": "EUR"}. | |
| for_type | Yes | The action type each use will have, e.g. "payment". | |
| max_uses | Yes | How many times it may be used (1 to 1000). |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint=false and destructiveHint=false, so the safety profile is covered and the description does not contradict it. It adds behavior beyond the annotations by disclosing the multi-step lifecycle (get_approval then use_standing_approval) and the human-visibility trait ('the person sees every limit'). It stops short of saying what gets returned or what happens on denial.
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 short sentences, each earning its place: what it is, how it sequences with siblings, and when to hold back. The scope constraint (once, fixed limits, max_uses, days) is front-loaded.
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?
There is no output schema, and the description compensates by telling the agent the follow-up call (get_approval) needed to retrieve the result, which is the critical missing piece for a nested-parameter mutation tool. It does not describe the failure/denial path or how the approval is identified, so it is not fully complete.
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 coverage is 100%, so the schema already explains aud, days, max_uses and the fixed/vary objects in detail; that sets the baseline at 3. The prose reinforces max_uses and days and frames the fixed-limits concept, but adds no syntax, format, or constraint detail beyond the schema.
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?
States a specific verb+resource ('Ask a person once to allow the same kind of action') and immediately bounds it by count (max_uses) and duration (days), which is exactly what distinguishes a standing approval from a one-shot one. It also names the sibling that does the other thing ('For one action, use request_approval'), so the agent can separate it from the siblings without opening a schema.
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?
Explicitly routes: use this for repeated actions, request_approval for a single action, and it spells out the downstream workflow ('Then call get_approval, and use_standing_approval for each action'). The 'Use sparingly' caution names a condition (the person sees every limit) that governs when not to use it.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
use_standing_approvalA
Use an approved standing approval for one exact request inside its limits. Counts one use and returns approval and use_token: send both with that request (headers Phygital-Approval and Phygital-Use). The token works once, within 60 seconds.
| Name | Required | Description | Default |
|---|---|---|---|
| id | Yes | The standing approval's id. | |
| request | Yes | The exact request for this use: {"type": ..., "params": {...}}, inside the approved limits. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations only say this is a non-idempotent, non-read-only, non-open-world mutation. The description adds genuinely new behavior: each call counts one use, it returns `approval` and `use_token`, the token works exactly once within 60 seconds, and it must be sent via the Phygital-Approval and Phygital-Use headers. That is exactly the operational detail an agent needs beyond the annotations.
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, front-loaded with the action and scoping, then the return values, then the token/header mechanics. Every sentence carries information the agent needs; there is no filler.
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?
With no output schema, the description correctly explains the return values and how to use them, plus the token's time window. It does not describe failure behavior (e.g., exhausted approval, request outside limits), which for a consumption/mutation tool is a notable remaining gap.
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 coverage is 100% and the description largely restates the constraints already present in the `request` parameter description ('exact request ... inside the approved limits'). It does clarify that `id` identifies the standing approval being consumed, but adds little parameter-level detail beyond the schema, so baseline 3 applies.
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?
States a specific verb+resource: consume a standing approval for one exact request within its limits. This is clearly distinct from siblings like request_standing_approval, cancel_approval, and get_approval, which create, cancel, or read approvals rather than consume one.
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?
It gives clear usage context: the request must be the exact one covered and inside the approved limits, and the returned token is single-use with a 60-second lifetime. It does not explicitly rule out alternatives or point to a sibling when the approval is exhausted or out of limits, so it falls short of a 5.
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.
6 tool updates
v0.6.0- First observed
cancel_approval - First observed
get_approval - First observed
record_receipt - First observed
request_approval - First observed
request_standing_approval - First observed
use_standing_approval
TDQS
Scored across 6 tools
The six tools each target a distinct step of the approval lifecycle, and descriptions explicitly cross-reference when to prefer one over another (e.g. request_approval vs request_standing_approval vs use_standing_approval). The only mild overlap is among the three standing-approval-related tools, but the guidance ('For one action, use request_approval') keeps them separable.
All tools follow a clean verb_noun snake_case pattern: cancel_approval, get_approval, record_receipt, request_approval, request_standing_approval, use_standing_approval. The pattern is fully predictable and readable.
Six tools map neatly onto the request/decision/use/receipt/cancel lifecycle without redundancy. Each tool earns its place in the workflow.
The surface covers the full lifecycle: request (single and standing), poll decision, use a standing approval, record a receipt, and cancel/revoke. There is no listing/introspection tool (e.g. list outstanding approvals), a minor gap agents can work around by tracking ids.
Maintenance
Related MCP Connectors
Human-in-the-loop approval for agent actions, with verifiable action-bound receipts.
Human approval for irreversible AI agent actions, bound to the exact tool call by a passkey tap
Governed AI actions with signed, verifiable receipts: free keyless reads, human-approved writes.
Independent effect verification and signed receipts for consequential AI agent actions.
Related MCP Servers
AlicenseAqualityAmaintenanceThe accountability layer for AI agents — a named human's signed yes before an agent does anything irreversible (payment, record change, deploy), then an offline-verifiable Trust Receipt. Apache-2.0, formally verified.3611Apache 2.0- AlicenseNot gradedqualityDmaintenanceVerifiable action receipts for AI agents — agents sign claims locally, an independent witness countersigns and timestamps, anyone can verify offline.16 npmMIT

infraveil-guardofficial
AlicenseAqualityCmaintenanceA cooperative guardrail for AI agents that blocks destructive shell commands, SQL statements, or cloud operations until a human approves them out-of-band, with a tamper-evident local ledger.4AGPL 3.0- AlicenseNot gradedqualityCmaintenanceA human-in-the-loop governance interlock for AI agents. Agents propose changes, a human countersigns the exact plan, and then it executes stage by stage with precondition checks, verification, and auditing.Apache 2.0