Technical documentation · v2

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.

// 01 · Overview

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.

MCP endpoint · https://mcp.x-ego.com/mcp Paid HTTP · POST https://api.x-ego.com/verify-proof · POST https://api.x-ego.com/verify-action JWKS · https://api.x-ego.com/.well-known/jwks.json Ticket · POST https://api.x-ego.com/ticket · free, and the approval link comes with it
// 02 · Tools & pricing (pilot)

Four tools, one loop.

ToolWhat it doesPrice
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.

// 03 · Paid HTTP endpoints

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.

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

// 04 · Call binding

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.

// 05 · Proof, privacy, receipt

Anonymous by construction.

// 06 · Reaching the human

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.

// 07 · Quick start

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.

← Back to x-ego.com