Skip to main content
Glama
Phygital-Key

phygital-key-mcp

Official

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 REQUEST_MISMATCH.

Can't be reused

Each approval names one service (aud), lasts 60 seconds by default and works once. A second use gives REPLAYED.

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 (agent_key). Then only that app can use the approval: a copy taken from a log or a chat is refused.

Out of the AI's reach

With the secure install, the gate runs as its own locked-down user on your computer (_phygitalkeymcp on macOS, phygitalkey-mcp on Linux). The AI, even acting as you, can't change its program, read its data or add keys. Only your administrator password can.

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 (phygital-key-mcp)

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)

curl -fsSL https://github.com/Phygital-Key/phygital-key-mcp/releases/latest/download/install.sh | sh

macOS, Linux (Homebrew)

brew install phygital-key/tap/phygital-key-mcp

Windows (Scoop)

scoop bucket add phygital-key-mcp https://github.com/Phygital-Key/phygital-key-mcp then scoop install phygital-key-mcp

Anywhere with Rust

cargo install --locked --git https://github.com/Phygital-Key/phygital-key-mcp phygital-key-mcp

Server (Docker)

docker run -d --name phygital-key-mcp -p 127.0.0.1:7468:7468 -v phygital-key-mcp:/data ghcr.io/phygital-key/phygital-key-mcp:latest

Secure install (macOS, Linux)

See out of the AI's reach

By hand

Download from Releases. Each file has a .sha256 next to it, and the signed lists release.txt and release2.txt give every file's SHA-256

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 demo

The 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 phygital-key-mcp stdio. No token needed: only the app that started the gate can talk to it.

Connects to a web address

Run phygital-key-mcp serve (or use the secure install, or the container), then use http://localhost:7468/mcp with the header that phygital-key-mcp token prints.

The first way (stdio), in Claude Code:

claude mcp add phygital-key-mcp -- phygital-key-mcp stdio

Claude 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

request_approval

Asks you to approve one exact request for one service, at level medium (a touch on your key) or high (fingerprint, face or PIN). It can also take the app's own key (agent_key), so only that app can use the approval.

get_approval

Waits for your answer and gets the signed approval

cancel_approval

Withdraws a request that is still waiting, or cancels a standing approval

request_standing_approval

Asks for a limited, repeated permission (up to 1000 uses, up to 30 days)

use_standing_approval

Gets a one-minute use token for one action inside those limits

record_receipt

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 high

Exit 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

APPROVAL_REQUIRED, MALFORMED

2

It was approved for this service (aud)

WRONG_AUDIENCE

3

It is in time: not before iat (30 seconds of clock difference allowed), not after exp, and it lives at most 300 seconds

EXPIRED

4

It is the exact request, byte for byte, in the fixed form

REQUEST_MISMATCH

5

It is a strong enough yes for this action (medium is a touch, high is fingerprint, face or PIN)

ASSURANCE_TOO_LOW

6

It was signed by a key in your keyring (your list of trusted keys), for the right site

UNTRUSTED_KEY

7

The passkey's signed answer (WebAuthn assertion): type, challenge, origin, site hash and ES256 signature

BAD_SIGNATURE

8

The "person was there" and "person was verified" flags match the level

ASSURANCE_TOO_LOW

9

It is the first use (used approvals are remembered)

REPLAYED

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.

NOTE

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 bash

It 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 answering

macOS

Linux

Gate user

_phygitalkeymcp

phygitalkey-mcp

Program

/Library/PhygitalKey/mcp/bin/phygital-key-mcp

/usr/lib/phygital-key-mcp/bin/phygital-key-mcp

Data

/Library/Application Support/PhygitalKey/mcp

/var/lib/phygital-key-mcp

Background services

launchd io.phygitalkey.mcp.gate, io.phygitalkey.mcp.update

systemd phygital-key-mcp.service, phygital-key-mcp-update.timer

On Linux, systemd holds the gate's port while the gate restarts for an update, so no other program can grab it in between.

IMPORTANT

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 ~/.curlrc and PATH, switching back after a failed update, and the sandbox score

Container

This repository

Builds the image, starts the gate, checks user 65532 and adding a key

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

src/main.rs

The product: its name, program, port and where releases come from, plus the MCP part (address /mcp, stdio mode)

src/mcp.rs

The six MCP tools, and the instructions the server sends to every app

tests/e2e/

124 end-to-end and attack checks: release program, Chromium with a pretend passkey, the official MCP SDK, the demo service

docs/

The MCP guides: connect your AI, tools, agents, examples, testing

examples/

Prompt snippets for your agent's instructions

packaging/container/

The container image

release-keys.pem

The public release keys that every update and installer checks against

install.sh, install-service.sh, tests/secure-install.sh, scripts/

Made from the base's templates for this product with phygital-key-mcp product-files. Never edited by hand: CI fails if they don't match. See AGENTS.md

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 tools
cancel_approvalA
Destructive

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.

ParametersJSON Schema
NameRequiredDescriptionDefault
idYesThe id from request_approval.

TDQS

A4.5/5.0
Behavior5/5

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.

Conciseness5/5

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.

Completeness5/5

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.

Parameters3/5

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.

Purpose5/5

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.

Usage Guidelines4/5

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_approvalA
Read-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.

ParametersJSON Schema
NameRequiredDescriptionDefault
idYesThe id from request_approval.
wait_secondsNoSeconds to wait for the person to decide (0 to 120, default 60).

TDQS

A4.5/5.0
Behavior4/5

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.

Conciseness5/5

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.

Completeness5/5

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.

Parameters3/5

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.

Purpose5/5

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.

Usage Guidelines5/5

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.

ParametersJSON Schema
NameRequiredDescriptionDefault
idYesThe id from request_approval.
receiptYesThe receipt string the service returned (a signed JWT).

TDQS

A4.2/5.0
Behavior4/5

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.

Conciseness5/5

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.

Completeness4/5

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.

Parameters3/5

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.

Purpose5/5

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.

Usage Guidelines4/5

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.

ParametersJSON Schema
NameRequiredDescriptionDefault
audYesWho will act on the yes, e.g. "https://deploy.example.com". The approval only works there.
levelYesHow strong the proof must be: "medium" (touch) or "high" (fingerprint, face or PIN).
requestYesThe 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_keyNoOptional: 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

A4.5/5.0
Behavior4/5

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.

Conciseness5/5

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.

Completeness5/5

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.

Parameters3/5

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.

Purpose5/5

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.

Usage Guidelines5/5

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.

ParametersJSON Schema
NameRequiredDescriptionDefault
audYesWho will act on each use.
daysYesHow many days it lasts (1 to 30).
varyYesParams that may change, with limits: integers {"amount_cents": {"min": 1, "max": 2000}} (both required) or strings {"memo": {"one_of": ["coffee", "lunch"]}}.
fixedYesParams that must be exactly these values every time, e.g. {"to": "ACME Ltd", "currency": "EUR"}.
for_typeYesThe action type each use will have, e.g. "payment".
max_usesYesHow many times it may be used (1 to 1000).

TDQS

A4.4/5.0
Behavior4/5

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.

Conciseness5/5

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.

Completeness4/5

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.

Parameters3/5

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.

Purpose5/5

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.

Usage Guidelines5/5

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.

ParametersJSON Schema
NameRequiredDescriptionDefault
idYesThe standing approval's id.
requestYesThe exact request for this use: {"type": ..., "params": {...}}, inside the approved limits.

TDQS

A4.4/5.0
Behavior5/5

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.

Conciseness5/5

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.

Completeness4/5

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.

Parameters3/5

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.

Purpose5/5

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.

Usage Guidelines4/5

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.

  1. 6 tool updatesv0.6.0
    • First observedcancel_approval
    • First observedget_approval
    • First observedrecord_receipt
    • First observedrequest_approval
    • First observedrequest_standing_approval
    • First observeduse_standing_approval

TDQS

A4.4/5.0

Scored across 6 tools

Disambiguation4/5

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.

Naming Consistency5/5

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.

Tool Count5/5

Six tools map neatly onto the request/decision/use/receipt/cancel lifecycle without redundancy. Each tool earns its place in the workflow.

Completeness4/5

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

ActivityMaintained
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers

  • A
    license
    A
    quality
    A
    maintenance
    The 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.
    3
    611
    Apache 2.0
  • A
    license
    Not graded
    quality
    D
    maintenance
    Verifiable action receipts for AI agents — agents sign claims locally, an independent witness countersigns and timestamps, anyone can verify offline.
    16 npm
    MIT
  • A
    license
    A
    quality
    C
    maintenance
    A 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.
    4
    AGPL 3.0
  • A
    license
    Not graded
    quality
    C
    maintenance
    A 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