KYA-OS MCP protocol reference implementation — delegation, proof generation, session lifecycle, and cryptographic identity for the Model Context Protocol
TypeScript
29
394 commits
updated Oct 1, 2026
KYA-OS: agent identity, delegation, and proof. This repo is the MCP binding.
KYA-OS (Know Your Agent Operating System) is an identity, authority, and accountability layer that other agent-facing protocols adopt, so that any time an agent acts you can verify who called (agent identity), under what authority (delegation chain rooted at a Responsible Party, plus consent where required), and what they did (signed proofs composing into audit trails).
The shape of the contribution is roughly analogous to TLS. TLS is not a transport, it is a security layer that transports adopt. KYA-OS is not a transport or a runtime, it is an identity and accountability layer that host protocols embed.
Three jobs, six primitives:
did:key, did:web): a stable, cryptographically-controlled identifier that the agent can prove it owns, and that credentials can be issued against. Without this, there is nothing to bind authority to or hold accountable.Note on the name. This protocol was previously known as MCP-Identity / MCP-I. The rename to KYA-OS reflects the protocol's binding-agnostic scope. See the
[Unreleased]entry inCHANGELOG.mdfor the full rationale.
@kya-os/mcp is the MCP binding of KYA-OS, the reference implementation for Model Context Protocol servers, and the first binding to ship.
KYA-OS primitives are intended to embed in three kinds of host surface:
The MCP binding ships first because MCP is the most concentrated agent-to-tool RPC surface today. Additional bindings will be specified in the working group as they reach consensus.
The KYA-OS protocol itself is defined in SPEC.md. Binding-specific behavior is called out so future bindings can diverge cleanly where they need to.
npm install @kya-os/mcp
Before, a standard MCP server with no identity or proofs:
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
const server = new McpServer({ name: 'my-server', version: '1.0.0' });
server.registerTool('greet', { description: 'Say hello' }, async (args) => ({
content: [{ type: 'text', text: `Hello, ${args.name}!` }],
}));
After, every tool response now carries a signed cryptographic proof:
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { withKyaOs, NodeCryptoProvider } from '@kya-os/mcp'; // +1 line
const server = new McpServer({ name: 'my-server', version: '1.0.0' });
await withKyaOs(server, { crypto: new NodeCryptoProvider() }); // +1 line
server.registerTool('greet', { description: 'Say hello' }, async (args) => ({
content: [{ type: 'text', text: `Hello, ${args.name}!` }],
}));
That's it. withKyaOs auto-generates an Ed25519 identity, registers the _kyaos protocol tool, and wraps the transport so every tool response includes a detached JWS proof in _meta. Invisible to the LLM, verifiable by anyone.
See the full working example: examples/context7-with-kya-os, a real MCP server (Context7) migrated with exactly 2 lines of code.
Some tools shouldn't run without a human saying "yes." KYA-OS adds per-tool authorization using W3C Verifiable Credentials:
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}` }],
})),
);
When an agent calls checkout without a delegation credential, it gets back a needs_authorization response with a consent URL. The human approves, a scoped credential is issued, and the agent retries, now authorized.
Try it yourself: examples/consent-basic walks through the full consent flow end-to-end.
Detached proofs establish origin and bind request/response content. The
@kya-os/mcp/audit service composes those proofs and the full authorization
lifecycle into an atomically ordered, signed ledger with RFC 9162 checkpoints,
independent observations, privacy-separated evidence, and offline replay
bundles.
import { withKyaOs, NodeCryptoProvider } from '@kya-os/mcp';
import { createAuditTrail } from '@kya-os/mcp/audit';
const audit = createAuditTrail({
recorder: checkpointRecorderClient, // or createLocalAuditRecorder(...)
delivery: 'required',
hasher,
ledgerId: 'kya:tenant-opaque:prod:primary',
expectedLedgerEpochId: 'epoch-2026-07',
tenantRef,
producer: pairwiseProducerRef,
sourceId: 'mcp-server-1',
binding: 'urn:kya-os:audit-binding:mcp:2025-11-25',
privacy: { classification: 'internal', retentionClass: 'audit-365d' },
clock: Date,
});
await withKyaOs(server, { crypto: new NodeCryptoProvider(), audit });
The MCP adapter records intent, terminal success/failure, denial/challenge, proof, delegation, authorization, and replay-rejection paths without copying raw tool arguments or response bodies. Delivery and assurance claims are explicit; unsafe high-assurance combinations fail at startup.
See AUDITABILITY.md for the trust model, provider contracts,
assurance profiles, Checkpoint integration, replay CLI, and production checklist.
Run the local walkthrough with npm run example:audit-trail.
Proofs answer what an agent did. The Entity Card answers who is calling — a typed, DID-anchored identity (agent, mcp, client, verifier, human) an entity publishes once and every discovery rail can index. It is claim-minimal: it asserts only identity, type, declared capabilities, and accountability locators. The trust level (L1/L2/L3) is never self-claimed — a verifier RECOMPUTES it from evidence. Three ergonomic calls, imported from the published @kya-os/mcp/card subpath:
import { card, withKyaOsCard, requireProof, InMemoryNonceCache } from '@kya-os/mcp/card';
// 1. BUILD — describe the agent, fluently. No conformanceLevel: a verifier derives it.
const myCard = card({ did: 'did:web:acme.example:agents:pay', entityType: 'agent', name: 'Acme Pay' })
.capability('search') // L1: bare-string, self-declared
.attestedCapability('payments.transfer', capabilityVc) // L2: VC-backed
.accountableTo('did:web:acme.example:org', { via: 'vc_root>del_123' })
.usesProof()
.build();
// 2. EMIT — mount the three discovery artifacts (card.json, DID service entry, server.json _meta).
const mount = withKyaOsCard(myCard);
const serverJson = mount.mountServerJson({ name: 'acme-mcp', version: '1.0.0' });
// 3. GUARD — verify a per-request holder-of-key proof, fail-closed.
const nonces = new InMemoryNonceCache(); // ATOMIC replay defense — never hand-roll this seam
const guard = requireProof({
resolveKey, // resolve the signing key from its kid (DID document)
expectedAudience: 'did:web:acme.example:mcp:server',
consumeNonceIfFresh: nonces.consume, // test-AND-set; a replayed nonce is rejected
});
// Pass the EXACT body the client signed (without _meta) plus the _meta that carried the proof.
const { _meta, ...signedBody } = incomingRequest;
const verdict = await guard(signedBody, _meta); // { ok: true, did, level } or a 401-shaped reject
Miss the proof, replay a nonce, or tamper the body and requireProof fails closed. To go the other direction — DISCOVER and verify another entity's card — use resolveCard + verifyCard (the verifier recomputes the conformance floor rather than trusting the card).
Run the full 10-minute path end-to-end: examples/entity-card —
npm run example:entity-card:server(build → emit → guard, with a valid proof accepted and a replay + tamper rejected) andnpm run example:entity-card(the discover → resolve → verify walkthrough). See SPEC-ENTITY-CARD.md for normative detail.
A live agent (Claude Desktop) pays invoices from a testnet wallet under a signed, scoped, revocable credential. When it goes rogue, a FIDO2 hardware touch revokes that credential on a public chain: the StatusList2021 bit flips in a cheqd DID-Linked Resource, and the agent's next transaction is refused in about half a second. Funds never move.
Built in a weekend on this package (2nd place, DEF CON 34 Cryptocurrency Village), and everything the demo had to invent now ships here: the on-chain resolver, the always-fresh revocation checks, the DLR artifact type (#165 through #169).
Start with the 60-second path: verify a genuinely revoked credential against the live testnet, zero configuration. examples/revoked
git clone https://github.com/decentralized-identity/kya-os-mcp.git
cd kya-os-mcp && npm install
bash scripts/demo.sh
This starts all example servers and opens MCP Inspector. Connect to any server, call a tool, and inspect the proof in _meta:
| Port | Example | What it demonstrates |
|---|---|---|
| 3001 | node-server | Proofs + restricted tools (low-level API) |
| 3002 | consent-basic | Human consent flow with built-in UI |
| 3003 | consent-full | Production consent UI (@kya-os/consent) |
| 3004 | context7-with-kya-os | 2-line migration of a real MCP server |
Also available: outbound-delegation (gateway pattern), verify-proof (standalone verification), statuslist (revocation lifecycle), cheqd-dlr (operator DID linkage + DLR publishing).
A public reference deployment runs the latest published release, with the identity did:web:demo-mcp.kya-os.ai.
Every surface is a plain HTTPS fetch, so no privileged access is needed to check any claim it makes.
| Surface | URL |
|---|---|
| MCP endpoint (streamable-http) | https://demo-mcp.kya-os.ai/mcp |
| DID document | /.well-known/did.json |
| Entity Card | /card.json |
| Revocation status list | /status-list |
| Exactly what is running | /provenance |
Connect MCP Inspector to https://demo-mcp.kya-os.ai/mcp, call vault_read, and inspect the proof in _meta.
Then verify that proof yourself with examples/verify-proof, which resolves the server's did:web over the public internet and checks the signature.
A guided browser walkthrough of the same server (valid proof, tamper, replay, stolen key, live revocation, cross-language re-verification) runs at poc.kya-os.ai.
The daily probe in CI performs those same read-only checks: it fetches the discovery surfaces, round-trips a real tool call over MCP, and verifies the returned proof against the publicly resolved DID. The server is operated by a maintainer on pinned releases; this repo does not deploy it, it independently verifies it.
| Capability | How it works |
|---|---|
| Cryptographic identity | Ed25519 (EdDSA) and P-256 (ES256, FIPS-eligible) key pairs, did:key / did:web resolution, optional did:cheqd resolver support |
| Entity Card | Typed, DID-anchored identity: fluent card() builder, requireProof per-request holder-of-key guard, CIMD OAuth on-ramp (client_id ⇄ did:web, MCP's default client auth), and withKyaOsCard projections that embed into MCP server.json / Server Cards (draft SEP-2127), A2A AgentCards, and NANDA AgentFacts |
| Signed proofs | Detached JWS over JCS-canonicalized request/response hashes |
| Delegation credentials | W3C Verifiable Credentials with scope constraints, rooted at a Responsible Party |
| Revocation | StatusList2021 bitstring with cascading revocation |
| Replay prevention | Nonce-based handshake with timestamp skew validation |
| Verifiable auditability | Typed producer events, authoritative atomic recorder, signed chain receipts, RFC 9162 checkpoints, independent observation, encrypted evidence references, replay bundles, and offline CLI |
| Extensible | Bring your own KMS, HSM, atomic nonce provider, or DID method |
The in-memory defaults are single-process only. For a load-balanced /
multi-instance deployment, inject a durable Redis / Durable Object / DB-backed
implementation for every runtime-state seam — the nonce cache
(NonceCacheProvider) together with the consent stores (GrantStore,
PendingFlowStore, SessionStore) — so replay protection, grants, pending OAuth
flows, and sessions are shared across instances and survive restarts.
Delegation grants must retain their original signed evidence: credentialJwt for VC-JWTs, or delegationCredential for object VCs.
wrapWithDelegation revalidates that evidence with the same verifier as a directly presented credential on every reuse, including chain resolution, audience, scope, expiry, and configured status/revocation checks.
Store providers must preserve these fields and return only active grants; cache availability alone does not authorize a request.
Grant expiry is bounded by the earliest credential or constraint expiry in the verified chain.
Upgrade compatibility: legacy grants without signed evidence intentionally return needs_authorization; re-present a valid delegation or complete the normal consent flow to replace them.
Do not reconstruct unsigned credentials from cached metadata.
Under holderBinding: 'enforce', did:key session retries also require a fresh holder proof.
off and warn retain their documented session behavior, and non-did:key binding remains deferred; neither provides an enforced proof-of-possession guarantee for those requests.
Status and chain providers now receive reads on grant reuse as they do on direct calls, and an unavailable required provider prevents reuse.
This does not strengthen a provider's own status-cache freshness policy.
VC-JWT envelope exp and nbf are checked on every verification, including signature-cache hits and grant reuse.
Omitted claims remain supported.
A present claim must be a finite NumericDate.
An expired envelope denies authorization even when the embedded VC is otherwise current, and verified envelope expiry can shorten the cached grant's lifetime.
nbf allows JWT_NBF_LEEWAY_SECONDS (30 seconds) of clock skew; exp allows none.
Delegation issuers: each re-delegation must be signed by its parent's subject.
The issuerDid inside a credential is only a claim, so the chain walk also checks who signed it.
Set delegation.trustedRootIssuers to the DIDs allowed to issue root delegations, the Responsible Party for the whole chain.
With it set, a chain whose root is signed by any other DID is rejected, including when a stored grant is reused.
Without it, any issuer is accepted, including an agent signing a delegation for itself, and the middleware logs a warning at startup.
A root whose claimed issuerDid differs from its signer is still accepted, and logged.
Nonce providers: give your NonceCacheProvider an atomic consume(nonce, ttlSeconds, agentDid?).
It must record an unseen (agentDid, nonce), return literal true only to the call that recorded it, and reject on storage failure.
Implement the claim in the shared backend with a conditional insert and expiry, not with a local mutex around remote reads and writes.
Eventually consistent KV alone cannot provide it.
The bundled memory provider implements consume, but it protects only one cache instance and loses its state on restart.
consume is optional: a provider without it keeps working by falling back to has then add, which cannot stop concurrent duplicates of one signed request, and a warning is logged once.
Set requireAtomicNonce: true on the middleware, ProofVerifier, SessionManager or consumeFromNonceCacheProvider to refuse that fallback; each then throws at construction for a provider without consume.
Any result other than literal true, or a storage failure, denies admission.
Providers must honor the requested retention duration.
The verifiers retain nonces through the final accepted second, including future timestamps and clock skew, while preserving a longer configured TTL.
The detached verifier also covers later increases up to its supported setTimestampSkew maximum.
Custom card consumeNonceIfFresh callbacks must honor the new third minTtlSec argument; the bundled card cache and provider adapter do so.
Instances sharing a replay namespace must agree on the longest proof-acceptance policy and retain state accordingly.
Retire old non-atomic writers before claiming atomic protection.
Preserve or extend existing records through the full remaining acceptance window; merely keeping their old, shorter TTLs is insufficient.
If the backend cannot extend them safely, pause affected admission and wait out that window before cutover.
Do not clear records while their proofs may still be accepted.
KYA-OS integrates through typed seams — the adapter pattern is the whole
model. Every external dependency is a port with an in-memory or reference
default; bind your own backend, or drop in a shipped adapter. Adding one is
"implement this interface" (or, for a self-contained system, "drop a folder
under src/integrations/") — contributions welcome.
| Seam (port) | Reference / default | Bring your own |
|---|---|---|
DID resolution — DIDResolver | did:key, did:web | did:cheqd, custom methods |
Revocation — StatusListResolver | not configured | cheqd StatusList2021 |
Authorization — AuthorizationServerAdapter | GenericOidcAdapter (OIDC + PKCE) | Auth0, Okta, your IdP (example) |
State — GrantStore, SessionStore, NonceCacheProvider, PendingFlowStore | in-memory | Redis, DynamoDB, Durable Objects, DB; atomic consumption for replay and pending state (multi-instance) |
Audit delivery — AuditRecorder, AuditAnchorProvider | local recorder | Checkpoint, on-chain anchoring (AUDITABILITY.md) |
Crypto — CryptoProvider | Node, WebCrypto | your KMS / HSM |
Policy — PolicyEngine | built-in default | custom engine |
cheqd is the reference for what filling the seams looks like end to end — one
package binds three at once: DID resolution (did:cheqd), revocation (on-chain
StatusList2021), and audit anchoring (DID-Linked Resources). Enabling the
resolver is a small config change:
import { cheqdResolver } from '@kya-os/mcp/cheqd';
await withKyaOs(server, {
crypto,
delegation: {
didResolvers: { cheqd: cheqdResolver({ resolverUrl: 'https://resolver.cheqd.net' }) },
},
});
Its full reference (registrar writes, did:web <-> did:cheqd linkage,
DID-Linked Resource helpers, live testnet E2E) lives in
src/integrations/cheqd/README.md; see
examples/cheqd-dlr for a complete operator flow.
MIT
354 followers · starred Aug 2026
TypeScript
98.9%
KYA-OS MCP protocol reference implementation — delegation, proof generation, session lifecycle, and cryptographic identity for the Model Context Protocol
TypeScript
29
394 commits
updated Oct 1, 2026
KYA-OS: agent identity, delegation, and proof. This repo is the MCP binding.
KYA-OS (Know Your Agent Operating System) is an identity, authority, and accountability layer that other agent-facing protocols adopt, so that any time an agent acts you can verify who called (agent identity), under what authority (delegation chain rooted at a Responsible Party, plus consent where required), and what they did (signed proofs composing into audit trails).
The shape of the contribution is roughly analogous to TLS. TLS is not a transport, it is a security layer that transports adopt. KYA-OS is not a transport or a runtime, it is an identity and accountability layer that host protocols embed.
Three jobs, six primitives:
did:key, did:web): a stable, cryptographically-controlled identifier that the agent can prove it owns, and that credentials can be issued against. Without this, there is nothing to bind authority to or hold accountable.Note on the name. This protocol was previously known as MCP-Identity / MCP-I. The rename to KYA-OS reflects the protocol's binding-agnostic scope. See the
[Unreleased]entry inCHANGELOG.mdfor the full rationale.
@kya-os/mcp is the MCP binding of KYA-OS, the reference implementation for Model Context Protocol servers, and the first binding to ship.
KYA-OS primitives are intended to embed in three kinds of host surface:
The MCP binding ships first because MCP is the most concentrated agent-to-tool RPC surface today. Additional bindings will be specified in the working group as they reach consensus.
The KYA-OS protocol itself is defined in SPEC.md. Binding-specific behavior is called out so future bindings can diverge cleanly where they need to.
npm install @kya-os/mcp
Before, a standard MCP server with no identity or proofs:
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
const server = new McpServer({ name: 'my-server', version: '1.0.0' });
server.registerTool('greet', { description: 'Say hello' }, async (args) => ({
content: [{ type: 'text', text: `Hello, ${args.name}!` }],
}));
After, every tool response now carries a signed cryptographic proof:
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { withKyaOs, NodeCryptoProvider } from '@kya-os/mcp'; // +1 line
const server = new McpServer({ name: 'my-server', version: '1.0.0' });
await withKyaOs(server, { crypto: new NodeCryptoProvider() }); // +1 line
server.registerTool('greet', { description: 'Say hello' }, async (args) => ({
content: [{ type: 'text', text: `Hello, ${args.name}!` }],
}));
That's it. withKyaOs auto-generates an Ed25519 identity, registers the _kyaos protocol tool, and wraps the transport so every tool response includes a detached JWS proof in _meta. Invisible to the LLM, verifiable by anyone.
See the full working example: examples/context7-with-kya-os, a real MCP server (Context7) migrated with exactly 2 lines of code.
Some tools shouldn't run without a human saying "yes." KYA-OS adds per-tool authorization using W3C Verifiable Credentials:
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}` }],
})),
);
When an agent calls checkout without a delegation credential, it gets back a needs_authorization response with a consent URL. The human approves, a scoped credential is issued, and the agent retries, now authorized.
Try it yourself: examples/consent-basic walks through the full consent flow end-to-end.
Detached proofs establish origin and bind request/response content. The
@kya-os/mcp/audit service composes those proofs and the full authorization
lifecycle into an atomically ordered, signed ledger with RFC 9162 checkpoints,
independent observations, privacy-separated evidence, and offline replay
bundles.
import { withKyaOs, NodeCryptoProvider } from '@kya-os/mcp';
import { createAuditTrail } from '@kya-os/mcp/audit';
const audit = createAuditTrail({
recorder: checkpointRecorderClient, // or createLocalAuditRecorder(...)
delivery: 'required',
hasher,
ledgerId: 'kya:tenant-opaque:prod:primary',
expectedLedgerEpochId: 'epoch-2026-07',
tenantRef,
producer: pairwiseProducerRef,
sourceId: 'mcp-server-1',
binding: 'urn:kya-os:audit-binding:mcp:2025-11-25',
privacy: { classification: 'internal', retentionClass: 'audit-365d' },
clock: Date,
});
await withKyaOs(server, { crypto: new NodeCryptoProvider(), audit });
The MCP adapter records intent, terminal success/failure, denial/challenge, proof, delegation, authorization, and replay-rejection paths without copying raw tool arguments or response bodies. Delivery and assurance claims are explicit; unsafe high-assurance combinations fail at startup.
See AUDITABILITY.md for the trust model, provider contracts,
assurance profiles, Checkpoint integration, replay CLI, and production checklist.
Run the local walkthrough with npm run example:audit-trail.
Proofs answer what an agent did. The Entity Card answers who is calling — a typed, DID-anchored identity (agent, mcp, client, verifier, human) an entity publishes once and every discovery rail can index. It is claim-minimal: it asserts only identity, type, declared capabilities, and accountability locators. The trust level (L1/L2/L3) is never self-claimed — a verifier RECOMPUTES it from evidence. Three ergonomic calls, imported from the published @kya-os/mcp/card subpath:
import { card, withKyaOsCard, requireProof, InMemoryNonceCache } from '@kya-os/mcp/card';
// 1. BUILD — describe the agent, fluently. No conformanceLevel: a verifier derives it.
const myCard = card({ did: 'did:web:acme.example:agents:pay', entityType: 'agent', name: 'Acme Pay' })
.capability('search') // L1: bare-string, self-declared
.attestedCapability('payments.transfer', capabilityVc) // L2: VC-backed
.accountableTo('did:web:acme.example:org', { via: 'vc_root>del_123' })
.usesProof()
.build();
// 2. EMIT — mount the three discovery artifacts (card.json, DID service entry, server.json _meta).
const mount = withKyaOsCard(myCard);
const serverJson = mount.mountServerJson({ name: 'acme-mcp', version: '1.0.0' });
// 3. GUARD — verify a per-request holder-of-key proof, fail-closed.
const nonces = new InMemoryNonceCache(); // ATOMIC replay defense — never hand-roll this seam
const guard = requireProof({
resolveKey, // resolve the signing key from its kid (DID document)
expectedAudience: 'did:web:acme.example:mcp:server',
consumeNonceIfFresh: nonces.consume, // test-AND-set; a replayed nonce is rejected
});
// Pass the EXACT body the client signed (without _meta) plus the _meta that carried the proof.
const { _meta, ...signedBody } = incomingRequest;
const verdict = await guard(signedBody, _meta); // { ok: true, did, level } or a 401-shaped reject
Miss the proof, replay a nonce, or tamper the body and requireProof fails closed. To go the other direction — DISCOVER and verify another entity's card — use resolveCard + verifyCard (the verifier recomputes the conformance floor rather than trusting the card).
Run the full 10-minute path end-to-end: examples/entity-card —
npm run example:entity-card:server(build → emit → guard, with a valid proof accepted and a replay + tamper rejected) andnpm run example:entity-card(the discover → resolve → verify walkthrough). See SPEC-ENTITY-CARD.md for normative detail.
A live agent (Claude Desktop) pays invoices from a testnet wallet under a signed, scoped, revocable credential. When it goes rogue, a FIDO2 hardware touch revokes that credential on a public chain: the StatusList2021 bit flips in a cheqd DID-Linked Resource, and the agent's next transaction is refused in about half a second. Funds never move.
Built in a weekend on this package (2nd place, DEF CON 34 Cryptocurrency Village), and everything the demo had to invent now ships here: the on-chain resolver, the always-fresh revocation checks, the DLR artifact type (#165 through #169).
Start with the 60-second path: verify a genuinely revoked credential against the live testnet, zero configuration. examples/revoked
git clone https://github.com/decentralized-identity/kya-os-mcp.git
cd kya-os-mcp && npm install
bash scripts/demo.sh
This starts all example servers and opens MCP Inspector. Connect to any server, call a tool, and inspect the proof in _meta:
| Port | Example | What it demonstrates |
|---|---|---|
| 3001 | node-server | Proofs + restricted tools (low-level API) |
| 3002 | consent-basic | Human consent flow with built-in UI |
| 3003 | consent-full | Production consent UI (@kya-os/consent) |
| 3004 | context7-with-kya-os | 2-line migration of a real MCP server |
Also available: outbound-delegation (gateway pattern), verify-proof (standalone verification), statuslist (revocation lifecycle), cheqd-dlr (operator DID linkage + DLR publishing).
A public reference deployment runs the latest published release, with the identity did:web:demo-mcp.kya-os.ai.
Every surface is a plain HTTPS fetch, so no privileged access is needed to check any claim it makes.
| Surface | URL |
|---|---|
| MCP endpoint (streamable-http) | https://demo-mcp.kya-os.ai/mcp |
| DID document | /.well-known/did.json |
| Entity Card | /card.json |
| Revocation status list | /status-list |
| Exactly what is running | /provenance |
Connect MCP Inspector to https://demo-mcp.kya-os.ai/mcp, call vault_read, and inspect the proof in _meta.
Then verify that proof yourself with examples/verify-proof, which resolves the server's did:web over the public internet and checks the signature.
A guided browser walkthrough of the same server (valid proof, tamper, replay, stolen key, live revocation, cross-language re-verification) runs at poc.kya-os.ai.
The daily probe in CI performs those same read-only checks: it fetches the discovery surfaces, round-trips a real tool call over MCP, and verifies the returned proof against the publicly resolved DID. The server is operated by a maintainer on pinned releases; this repo does not deploy it, it independently verifies it.
| Capability | How it works |
|---|---|
| Cryptographic identity | Ed25519 (EdDSA) and P-256 (ES256, FIPS-eligible) key pairs, did:key / did:web resolution, optional did:cheqd resolver support |
| Entity Card | Typed, DID-anchored identity: fluent card() builder, requireProof per-request holder-of-key guard, CIMD OAuth on-ramp (client_id ⇄ did:web, MCP's default client auth), and withKyaOsCard projections that embed into MCP server.json / Server Cards (draft SEP-2127), A2A AgentCards, and NANDA AgentFacts |
| Signed proofs | Detached JWS over JCS-canonicalized request/response hashes |
| Delegation credentials | W3C Verifiable Credentials with scope constraints, rooted at a Responsible Party |
| Revocation | StatusList2021 bitstring with cascading revocation |
| Replay prevention | Nonce-based handshake with timestamp skew validation |
| Verifiable auditability | Typed producer events, authoritative atomic recorder, signed chain receipts, RFC 9162 checkpoints, independent observation, encrypted evidence references, replay bundles, and offline CLI |
| Extensible | Bring your own KMS, HSM, atomic nonce provider, or DID method |
The in-memory defaults are single-process only. For a load-balanced /
multi-instance deployment, inject a durable Redis / Durable Object / DB-backed
implementation for every runtime-state seam — the nonce cache
(NonceCacheProvider) together with the consent stores (GrantStore,
PendingFlowStore, SessionStore) — so replay protection, grants, pending OAuth
flows, and sessions are shared across instances and survive restarts.
Delegation grants must retain their original signed evidence: credentialJwt for VC-JWTs, or delegationCredential for object VCs.
wrapWithDelegation revalidates that evidence with the same verifier as a directly presented credential on every reuse, including chain resolution, audience, scope, expiry, and configured status/revocation checks.
Store providers must preserve these fields and return only active grants; cache availability alone does not authorize a request.
Grant expiry is bounded by the earliest credential or constraint expiry in the verified chain.
Upgrade compatibility: legacy grants without signed evidence intentionally return needs_authorization; re-present a valid delegation or complete the normal consent flow to replace them.
Do not reconstruct unsigned credentials from cached metadata.
Under holderBinding: 'enforce', did:key session retries also require a fresh holder proof.
off and warn retain their documented session behavior, and non-did:key binding remains deferred; neither provides an enforced proof-of-possession guarantee for those requests.
Status and chain providers now receive reads on grant reuse as they do on direct calls, and an unavailable required provider prevents reuse.
This does not strengthen a provider's own status-cache freshness policy.
VC-JWT envelope exp and nbf are checked on every verification, including signature-cache hits and grant reuse.
Omitted claims remain supported.
A present claim must be a finite NumericDate.
An expired envelope denies authorization even when the embedded VC is otherwise current, and verified envelope expiry can shorten the cached grant's lifetime.
nbf allows JWT_NBF_LEEWAY_SECONDS (30 seconds) of clock skew; exp allows none.
Delegation issuers: each re-delegation must be signed by its parent's subject.
The issuerDid inside a credential is only a claim, so the chain walk also checks who signed it.
Set delegation.trustedRootIssuers to the DIDs allowed to issue root delegations, the Responsible Party for the whole chain.
With it set, a chain whose root is signed by any other DID is rejected, including when a stored grant is reused.
Without it, any issuer is accepted, including an agent signing a delegation for itself, and the middleware logs a warning at startup.
A root whose claimed issuerDid differs from its signer is still accepted, and logged.
Nonce providers: give your NonceCacheProvider an atomic consume(nonce, ttlSeconds, agentDid?).
It must record an unseen (agentDid, nonce), return literal true only to the call that recorded it, and reject on storage failure.
Implement the claim in the shared backend with a conditional insert and expiry, not with a local mutex around remote reads and writes.
Eventually consistent KV alone cannot provide it.
The bundled memory provider implements consume, but it protects only one cache instance and loses its state on restart.
consume is optional: a provider without it keeps working by falling back to has then add, which cannot stop concurrent duplicates of one signed request, and a warning is logged once.
Set requireAtomicNonce: true on the middleware, ProofVerifier, SessionManager or consumeFromNonceCacheProvider to refuse that fallback; each then throws at construction for a provider without consume.
Any result other than literal true, or a storage failure, denies admission.
Providers must honor the requested retention duration.
The verifiers retain nonces through the final accepted second, including future timestamps and clock skew, while preserving a longer configured TTL.
The detached verifier also covers later increases up to its supported setTimestampSkew maximum.
Custom card consumeNonceIfFresh callbacks must honor the new third minTtlSec argument; the bundled card cache and provider adapter do so.
Instances sharing a replay namespace must agree on the longest proof-acceptance policy and retain state accordingly.
Retire old non-atomic writers before claiming atomic protection.
Preserve or extend existing records through the full remaining acceptance window; merely keeping their old, shorter TTLs is insufficient.
If the backend cannot extend them safely, pause affected admission and wait out that window before cutover.
Do not clear records while their proofs may still be accepted.
KYA-OS integrates through typed seams — the adapter pattern is the whole
model. Every external dependency is a port with an in-memory or reference
default; bind your own backend, or drop in a shipped adapter. Adding one is
"implement this interface" (or, for a self-contained system, "drop a folder
under src/integrations/") — contributions welcome.
| Seam (port) | Reference / default | Bring your own |
|---|---|---|
DID resolution — DIDResolver | did:key, did:web | did:cheqd, custom methods |
Revocation — StatusListResolver | not configured | cheqd StatusList2021 |
Authorization — AuthorizationServerAdapter | GenericOidcAdapter (OIDC + PKCE) | Auth0, Okta, your IdP (example) |
State — GrantStore, SessionStore, NonceCacheProvider, PendingFlowStore | in-memory | Redis, DynamoDB, Durable Objects, DB; atomic consumption for replay and pending state (multi-instance) |
Audit delivery — AuditRecorder, AuditAnchorProvider | local recorder | Checkpoint, on-chain anchoring (AUDITABILITY.md) |
Crypto — CryptoProvider | Node, WebCrypto | your KMS / HSM |
Policy — PolicyEngine | built-in default | custom engine |
cheqd is the reference for what filling the seams looks like end to end — one
package binds three at once: DID resolution (did:cheqd), revocation (on-chain
StatusList2021), and audit anchoring (DID-Linked Resources). Enabling the
resolver is a small config change:
import { cheqdResolver } from '@kya-os/mcp/cheqd';
await withKyaOs(server, {
crypto,
delegation: {
didResolvers: { cheqd: cheqdResolver({ resolverUrl: 'https://resolver.cheqd.net' }) },
},
});
Its full reference (registrar writes, did:web <-> did:cheqd linkage,
DID-Linked Resource helpers, live testnet E2E) lives in
src/integrations/cheqd/README.md; see
examples/cheqd-dlr for a complete operator flow.
MIT
354 followers · starred Aug 2026
TypeScript
98.9%