USE-CASES
What the primitives are for. One shipping example, step by step, and six recipes - each with a target, the runnable example in the reference repository that demonstrates it, and the exact file or subpath it comes from - plus the demo server to try a proof against before you build.
An on-chain kill switch for AI agents with wallet access. Agents spend under cryptographically scoped, verifiable delegations, and that spending authority can be revoked on a public chain. A rogue or hijacked agent is stopped before it drains a wallet, with no server anyone has to trust.
Built solo in a weekend at DEF CON 34 (2nd place, Cryptocurrency Village hackathon), now the flagship example of the reference implementation. The agent is Claude Desktop, the same MCP client thousands of people use, plugged into a local kya-wallet gateway that holds the agent's key and signs each call; the LLM never touches key material. In the 3-minute video an agent pays an invoice, gets caught misbehaving, and loses its spending authority on-chain. Its next transaction is refused in about half a second.
The four things the hackathon build had to invent now ship in @kya-os/mcp: CheqdStatusListResolver from @kya-os/mcp/cheqd, prepareCheqdDlrResource for publishing status lists as DID-Linked Resources, verificationMethodJwk inside the verifier, and revocation checked on every call since 1.13.0, which deleted a 60-second blind spot the demo used to work around. Rehearsing it surfaced a real fail-open upstream; fixing it made the demo simpler.
A walkthrough of the README's beats, not a network connection.
The agent spends, safely
bit 0 (active) · ALLOWED · tx hash + signed receipt (detached JWS)
Claude Desktop asks the gateway to pay; the gateway sends wallet_send + VC + holder proof; the server verifies the issuer signature against the on-chain DID document and the status bit. The credential is a W3C Delegation Credential, scope payments.transfer, capped at 10 CHEQ per transfer, and the cap lives in the signed credential, not in app code. The holder proof is signed by the agent's own did:key, and the server runs holderBinding: 'enforce', so the credential is subject-bound, not bearer.
After the kill
FIDO2 touch -> new status-list version (append-only DLR, cheqd testnet) · bit 1 (REVOKED) · DENIED (CREDENTIAL_REVOKED), handler never entered
The operator touches the FIDO2 key: a WebAuthn assertion whose challenge is the SHA-256 of the canonical revocation intent, so it is bound to this exact revocation, not a generic login. The new status-list version publishes as an append-only DID-Linked Resource; the issuer cannot quietly un-revoke, and every verifier reads the same chain. The next call's fresh lookup reads bit 1. The agent can still read the public balance. It cannot move a token.
That refusal is the product.
- The agent spends, safely. It presents its W3C Delegation Credential (scope
payments.transfer, capped at 10 CHEQ per transfer; the cap lives in the signed credential, not in app code) plus a per-request holder proof signed by its own did:key. The server runsholderBinding: 'enforce', so the credential is subject-bound, not bearer. Real CHEQ moves on testnet, and every response carries a detached-JWS receipt. - Attack theater. An over-cap send fails with
SCOPE_CONSTRAINT_VIOLATED, read from the credential. A thief replaying the stolen credential with their own key failsholder_binding_failedbefore the handler is entered. - The kill. A FIDO2 touch authorizes the revocation. The WebAuthn challenge is the SHA-256 of the canonical revocation intent, so the assertion is bound to this exact revocation, not a generic login. A new status-list version publishes as an append-only DID-Linked Resource: the issuer cannot quietly un-revoke, and every verifier reads the same chain.
- After the kill. The agent can still read the public balance. It cannot move a token:
CREDENTIAL_REVOKED, refused before the handler runs. The README's video caption puts the next transaction at about half a second; the bundled verify run below reportselapsedMs: 828.
A genuinely revoked credential is anchored on cheqd testnet right now. Verify it yourself: no keys, no environment variables, no trusting the repo's word for it. The shipped DelegationCredentialVerifier resolves the issuer's DID document from the chain, verifies the status list's Ed25519 signature against it, checks purpose parity, and reads the revocation bit.
cd examples/revokednpm installnpm run verify:once
Expected output, as the README prints it:
{ "verdict": "CREDENTIAL_REVOKED", "checks": { "basicValid": true, "signatureValid": true, "statusValid": false }, "elapsedMs": 828}
The signature is real, the credential is unexpired, and the chain still refuses it. That refusal is the product. Also bundled: samples/delegation-94.json, the actual credential from the DEF CON stage. Its 48-hour validity is long gone, so npm run verify:once -- --index 94 shows expiry beating revocation to the refusal. Fail-closed has layers.
Tiered on purpose. Each tier stands alone; all of it runs from examples/revoked.
One-time setup, then the show: cp .env.example .env.local, then npm run gen:accounts, npm run create:did, npm run publish:statuslist, npm run issue:delegation, npm run serve.
In the console: [1] send, [2] over-cap, [5] theft, [3] revoke, [4] retry. Presenter mode is P, high-contrast is C.
Copy docs/claude_desktop_config.json into your Claude Desktop config and edit the two absolute paths. Claude gets a clean wallet_send / check_balance tool surface; the gateway holds the did:key and signs every call.
No Claude Desktop on hand? npm run agent and the console's simulated-agent buttons drive the exact same path.
Any FIDO2 authenticator works; the DEF CON badge was the stage prop, a YubiKey does the same job. BADGE_SETUP=1 npm run serve, then open /badge-setup.html and register the key; BADGE_WEBAUTHN=1 npm run serve makes revocation require a physical touch.
No valid touch, no revocation: the endpoint two-phases through an intent-bound WebAuthn assertion and refuses everything else.
or copy manually
Show me KYA-OS in action: clone https://github.com/decentralized-identity/kya-os-mcp and run the examples/revoked demo (the on-chain revocation kill switch), then explain how the per-request proof, delegation chain, and status list interact, and scaffold a minimal verifier for my stack using @kya-os/mcp.
Recipes
Patterns the primitives were designed for. Each names a target, the runnable example in the reference repository that demonstrates it, and the file or npm subpath it comes from. No listed builder ships one yet - every recipe is a chance to be the first.
Human consent before the tool ever runs. An agent that calls checkout without a delegation credential gets back a needs_authorization response with a consent URL; the human approves, a scoped credential is issued, and the agent retries, now authorized. Consent is signed, not assumed.
Target A checkout, transfer, or delete tool an agent may only run after a person says yes.
Reference wrapWithDelegation, from @kya-os/mcp · examples/consent-basic · examples/consent-full
const checkout = kyaos.wrapWithDelegation( 'checkout', { scopeId: 'cart:write', consentUrl: 'https://example.com/consent' }, kyaos.wrapWithProof('checkout', async (args) => ({ content: [{ type: 'text', text: `Order placed: ${args.item}` }], })),);
- OAuth / OIDCoauth
- mDLmdl
- Identity verificationidv
- Verifiable credentialcredential
- Consent onlynone
Only oauth ships with a reference adapter (generic OIDC); the other types are protocol vocabulary that downstream adapters implement. The consent page itself supports 8 sign-in modes including OAuth, magic-link, OTP, passkey, and IDV.
A ToolProtection, as consent-persistence binds one:
const protection = { toolName: 'vault.read', requirement: { type: 'oauth' as const, provider: 'generic-oidc', requiredScopes: ['vault:read'] },};
The ceiling lives in the signed credential, not in app code: REVOKED's delegation carries scope payments.transfer capped at 10 CHEQ per transfer, and an over-cap send fails SCOPE_CONSTRAINT_VIOLATED. A chain only narrows (child MaxAmount and ValidUntil at or below the parent, fail-closed), and a server calling downstream forwards the delegation as KYA-OS-* headers with a signed proof JWT, so the next hop verifies the original agent's authority instead of trusting the middle.
Target An agent that pays invoices or buys compute under a ceiling its operator signed, across more than one service.
Reference @kya-os/mcp/delegation · src/card/delegation.ts · examples/revoked
Agent registries that carry proof posture in their listings. The walkthrough builds a card, resolves it, and verifies it, showing that verifyCard recomputes the conformance level from evidence and ignores whatever the card claims about itself.
Target A registry or marketplace that lists agents and wants the row to say what was verified, not what was self-described.
Reference @kya-os/mcp/card · examples/entity-card/walkthrough.ts
Detached proofs establish origin; the audit service composes them with the full authorization lifecycle into an atomically ordered, signed ledger. AuditCheckpointBuilder derives an RFC 9162 tree over entry digests, signs an immutable checkpoint, and produces and verifies inclusion and consistency proofs. Evidence for audit, incident-response, and regulatory-control workflows; it does not itself make an application compliant.
Target An agent whose every tool call must be replayable later, offline, from signed evidence rather than log files.
Reference @kya-os/mcp/audit · AUDITABILITY.md · examples/audit-trail
One canonical Entity Card, projected onto every rail the ecosystem already indexes: toServerCardMeta (MCP server.json _meta), toCatalogEntry (the catalog index row, by-ref), toA2AExtension (an A2A AgentCard extension, required: false), toAgentFacts (NANDA AgentFacts JSON-LD). Pure, deterministic projections, each pointing back at the same card.json on the did:web document.
Target An agent that has to show up in MCP server.json, an A2A AgentCard, and NANDA AgentFacts from one source of truth.
Reference @kya-os/mcp/card · src/card/emit.ts · examples/entity-card/server.ts
From the revocation seam itself: “FAIL-CLOSED is the invariant: an unreachable status list, a malformed credential, a mismatched statusPurpose, or an out-of-range index all resolve to { revoked: true }. The absence of proof-of-liveness is treated as revoked, never as ‘probably fine’.” On-chain, CheqdStatusListResolver pins the issuer, verifies the list's own signature against the on-chain DID document, and throws on anything unprovable.
Target Any credential an operator must be able to withdraw: a delegation, a card attestation, an agent's spending authority.
Reference src/card/revocation.ts · @kya-os/mcp/cheqd · examples/statuslist