X-EGO Human Proof
An MCP server that lets an AI agent prove a real human is present — and that the human approved one specific action. Passkey-backed, EdDSA-signed, single-use. No accounts, no personal data, no cross-service tracking.
What it is.
X-EGO exposes human verification as tools for AI agents over the
Model Context Protocol (MCP). The human side is a
Planetary ID — an identity bound to a person by their passkey
(fingerprint or Face ID, WebAuthn). They approve on x-ego.com/verify;
biometrics never leave their device.
What it proves: a person holding a registered passkey approved this — now, for your service specifically, and where you asked for it, for exactly the action you named — or, with call binding (section 04), for exactly the call you are about to make.
What it doesn’t prove: global uniqueness. Sybil resistance is cost-bounded: one identity, one payment.
Four tools, one loop.
| Tool | What it does | Price |
|---|---|---|
| xego_request_proof_url | Returns the link you send the human to, carrying the ticket that authorizes the proof. Verifies nothing itself — it only prepares the request. Optionally binds one named action, or the exact call, to the proof. | Free |
| xego_verify_proof | Verifies the proof the human brought back — signature, expiry, audience — and returns an anonymous pairwise ID. | $0.05 |
| xego_verify_action | Verifies the proof and that the human approved exactly the action you
name — or, with expected_call, the exact call itself. Returns a
receipt link. For transfers, deletions, confirmations — anything an agent
should not decide alone. |
$0.15 |
| xego_check_pairwise_seen_before | Has this pairwise ID been seen for your service before? Blocks one human acting as many users. | $0.01 |
Prices are pilot pricing and may change as the pilot progresses — the live price is always the one carried in the x402 challenge itself. Full argument schemas, return values and error codes ship with the tools themselves — your client reads them from the server, so they are always current. This page does not repeat them.
A proof is issued only against a ticket — so do not build the link
yourself. Writing your own /verify?audience=… URL no longer
works: the human taps their passkey and the answer is ticket_required, with
nothing to show for it. The ticket costs nothing — it is a lock, not
a toll, and putting a finger on a sensor stays free. Ask for one with
xego_request_proof_url, or, without an MCP client, with
POST https://api.x-ego.com/ticket and
Authorization: Bearer pk-…; you get back the ticket and a ready-made
approval link. It fits exactly one action, for one service, for fifteen minutes, and
only the human who owns the key can use it. Without a personal key no ticket can be
issued — the key arrives with the Planetary ID purchase.
How paid calls work. Settlement runs over the x402 protocol on Base mainnet USDC — no account, no wallet signup. One payment authorizes exactly one execution.
When you are charged differs by surface — read this before you budget.
On the MCP tools, payment settles before the tool runs: invalid input is
rejected for free, but once the check runs you pay for it — a verdict of
valid:false there is an answer you paid for. The paid HTTP
endpoints (section 03) work the other way round: settlement happens only after a
positive verdict, so a negative verdict costs you nothing. Malformed input (400),
bad_signature, expired, wrong_audience,
action_mismatch and planetary_id_required are all free there.
The one negative you do pay for is token_replayed — that check had
already been done once.
Free lanes. Anonymous callers get 30 requests per minute per IP. Pilot operator keys call the paid tools free of charge during the pilot — issued manually, get in touch.
Personal keys & credit. Buying a Planetary ID (€3, one-time)
issues a personal key with $3.00 of verification credit — enough for
3 action verifications, no crypto wallet anywhere. Send it as
Authorization: Bearer pk-…; calls debit the credit at list prices. Top up
by card at x-ego.com/topup ($5 / $20 / $50). Your balance is on the MCP host,
behind the same key: GET https://mcp.x-ego.com/stats with that
Authorization header — without it the call returns
401 unauthorized.
For humans. A Planetary ID costs €3 once and includes unlimited self-verification, forever.
Same core, no MCP client.
If you are not speaking MCP, two POST endpoints on
api.x-ego.com reach the same verification core. JSON body, JSON verdict,
paid per call with x402. Send the request unpaid first to receive the machine-readable
challenge in the PAYMENT-REQUIRED response header, then retry the
same body with a PAYMENT-SIGNATURE header.
| Endpoint | Body | Price |
|---|---|---|
| POST /verify-proof | token, expected_audience, optional
mark_as_seen. Returns valid, pairwise_id,
audience, expires_at, rarity,
receipt_url. |
$0.05 |
| POST /verify-action | The same three fields plus expected_action — the action string
byte-for-byte as the human saw it (surrounding whitespace ignored). A different
string is rejected with action_mismatch. Returns everything above plus
act, the hash of the approved action. |
$0.15 |
A verdict of valid:false carries a code — planetary_id_required
means the human never bought an ID, so no proof was ever issued to them. Full schemas:
api.x-ego.com/openapi.json.
These endpoints do not do call binding. They take
expected_action, a string, and nothing else. expected_call
— section 04 — exists only on the MCP surface. If you need the human to
approve the actual call rather than a sentence describing it, use the MCP tool; there is
no HTTP equivalent yet.
Approving a sentence is not approving the call.
expected_action binds the proof to the text the human read.
That is a real guarantee, and for a human-to-human claim it is often enough. But the text
is not the call that then executes. Whoever wrote the sentence chose what to leave out of
it, and a summary leaves things out by construction — “tidy up a config
file” and a write to ~/.ssh/authorized_keys can be the same sentence.
The gap between the sentence and the call is where the attack lives.
Call binding closes it. Instead of a sentence you pass the call itself as
expected_call, and the approval screen renders its fields — all of them
— from that object. The human approves a structure, not a description of one. The
proof then carries cal, a hash over the canonical form of that exact object,
and a mismatch anywhere in it fails verification.
expected_call: {
v: 1, // binding version, must be 1
tool: "wire_transfer", // non-empty string
target: "bank.example.com", // string, may be ""
args: { amount_cents: 50000, // JSON object; integers only,
to: "123456789/0100" }, // no decimals — use minor units
policy: "eu-payments/v2" // optional; a short REFERENCE to
} // rules, max 64 chars, not a sentence
The canonicalization is a subset of RFC 8785: keys sorted, no whitespace, minimal escaping,
no Unicode normalization, no lowercasing, decimals refused. It is published at
x-ego.com/call-binding.js so anyone can recompute cal from the
receipt without trusting us — which is the only reason it counts as proof. The same
algorithm runs in three places (browser, signer, verifier) and the three are held byte-identical
by shared test vectors.
MCP only. expected_call is accepted by the
xego_verify_action tool. The paid HTTP route in section 03 does not implement
it and will ignore it — there, the string is all you get.
The displayed text is never sent alongside the call. It is derived from the signed object. Letting the caller supply both would reopen the same gap one floor up: the human would read a label that does not match what is being signed.
Anonymous by construction.
- Proofs are JWTs signed with EdDSA (Ed25519). They live 180 seconds and are single-use — verifying the same one twice fails. Public keys are at the JWKS endpoint.
- Each service sees only a pairwise identifier, different for every service. Two services can never link their users to each other, and neither learns a name, an ID number, or any personal data — X-EGO stores none.
- Biometrics are checked on-device only (WebAuthn). Nothing biometric is ever transmitted.
- An approved action normally gets a public receipt at
x-ego.com/receipt?r=…— a page anyone can open, with no tools and no account, to check that a human approved that exact content. Paste the link anywhere the claim needs to hold up. - A missing receipt is not a forgery. The archive write is deliberately
fail-open: if archiving fails, the verification still succeeds rather than punishing a
human who did approve. So
/proof/<jti>can answerfound:falsewithnote:"not_confirmed_by_archive"for a perfectly legitimate approval. Read that as the archive cannot confirm this, never as this is fake. The signed token is the proof; the receipt is a convenience for humans, not the source of truth. If a claim has to hold, verify the token — do not treat a present receipt as necessary or a missing one as disqualifying.
When nobody is watching the chat.
An agent working while its owner sleeps will reach steps it must not take alone. Printing
a link into a transcript nobody is reading is not a channel — it routes the request
through the very thing the human is supposed to be supervising. A human who turns on
notifications at x-ego.com/notify gets the request on their own device
instead, without giving X-EGO an email address or a phone number.
- A request expires in 15 minutes. Silence is a refusal, not a pending state. If nobody approves, no proof exists and a correct executor stops.
- Only an agent holding that human’s own personal key
(
pk-…) may interrupt them, at most 20 times a day per device. This is not a channel you can reach strangers on, by design. - The notification carries no content to the push service — it is told only that something is waiting. The request itself is fetched from X-EGO by the device.
- There is deliberately no “approve” button in the notification. Only “No”, because refusing needs no signature. Approving happens in one place only: with the passkey, on x-ego.com. That is the whole defence against a forged notification — the worst a convincing fake achieves is that nothing happens.
Connect an agent.
Add the endpoint as a connector in any MCP-capable client — in Claude: Settings → Connectors → Add custom connector:
https://mcp.x-ego.com/mcp?key=pk-…
Put your personal key in that URL. Without it the tools still answer, but
xego_request_proof_url comes back with ticketed: false and the link
it returns cannot produce a proof — your human taps their passkey for nothing. The key
arrives with the Planetary ID purchase; it also pays for the calls out of its credit, so no
wallet is involved. If your client can set headers, send
Authorization: Bearer pk-… instead — a header does not end up in logs
the way a URL does. Either way the key must be there when the connection is opened, not added
to a later call: the connection remembers who it belongs to.
The tools appear automatically. A minimal integration is three calls:
1. xego_request_proof_url({ audience: "your-domain.com" })
2. — the human approves at the returned URL —
3. xego_verify_proof({ token, expected_audience: "your-domain.com" })
To bind the approval to a specific action, pass that action to
xego_request_proof_url and verify with xego_verify_action.
For anything an agent should not decide alone, pass expected_call instead of a
sentence — section 04 — so the human approves the call itself.
To skip the copy & paste entirely, ask for the token to be delivered.
Token delivery. By default the human ends up holding a token and has to
hand it back to your agent. Pass redirect_uri to
xego_request_proof_url and the approval page delivers it to you instead. The
target must be https on your own domain, or an http loopback
address for a local agent — nothing else is accepted, because a token delivered to
the wrong place is a token handed to someone else. Send state and you get the
same value back, so you can tell which request the token answers.
The token arrives in the URL fragment, never the query string: a fragment is not
sent to the server and does not appear in access logs or in a Referer header.
It is valid for 180 seconds and can be verified once.