Digital Bazaar's reference implementation of KYA-OS (Know Your Agent OS) Levels 1 & 2, bound to MCP via MCP-I.
KYA-OS — "Know Your Agent OS", developed under DIF's Trusted AI Agents Working Group — defines how AI agents prove delegated authority from a human without a central authority. This repo implements the L1 (agent identity) and L2 (user-to-agent delegation with per-request edge verification) conformance levels on Digital Bazaar's production W3C VC stack, with every requirement traced to code and a proving test (see Conformance).
AI agents are taking on real work - deploying code, calling APIs, managing files. But most authorization today is session-based, centralized, or just implicit. When something goes wrong, "the AI did it" is not an audit trail.
This project uses W3C Decentralized Identifiers (DIDs) and Verifiable Credentials (VCs) as the authorization layer for agentic systems. A human issues a signed, scoped credential to an agent. Any verifier checks it against the DID document - no central registry, no round-trip to an auth server, no shared secret.
Implemented as an MCP server so any Claude-powered agent can use it today.
OAuth requires a central auth server both parties trust and can reach. That assumption breaks down in multi-agent systems, offline environments, and cross-provider workflows.
| OAuth | DID + VC | |
|---|---|---|
| Central authority required | ✓ | ✗ |
| Works offline / across systems | ✗ | ✓ |
| Credentials travel with the agent | ✗ | ✓ |
| Composable delegation to sub-agents | ✗ | ✓ |
| Cryptographic audit trail | ✗ | ✓ |
See USE_CASES.md for real-world scenarios.
This is a reference implementation: every KYA-OS requirement is traced to the code that enforces it and the test that proves it.
| Level | Scope | Status |
|---|---|---|
| L1 | Agent has a DID and proves control of it. | ✅ implemented |
| L2 | User issues a VC to the agent; per-request verification of proof, issuer, expiry, revocation, and scope. | ✅ implemented |
| L3 | Selective disclosure, credential-to-token bridging, audit trails. | ⏳ deferred (Phase 2) |
docs/conformance/REQUIREMENTS.md— the MUST/SHOULD requirements with stable IDs (R-L1-n,R-L2-n,R-X-n).docs/conformance/CONFORMANCE.md— each requirement mapped to the proving test (22/22 in-scope covered).docs/L1.mdanddocs/L2.md— per-level walkthroughs withfile:linecitations.
Each enforcement point in the source carries a // KYA-OS R-Lx-n comment, so
you can read spec → code → test in either direction.
Human
│ issues a VC 2.0 credential (Data Integrity proof, Ed25519)
▼
Agent (Claude with tool use)
│ calls MCP tools
▼
MCP Server
├── resolve_did → DID Document (Universal Resolver)
├── verify_credential → {valid, issuer, subject, claims}
├── issue_credential → signed VC 2.0 credential object
├── check_delegation → {authorized, reason}
└── verify_delegation_chain → zcap capability-chain verification
Two npm workspace packages:
mcp-server/- MCP server exposing DID/VC toolsdemo-agent/- Claude-powered CLI demo of age-gated access control
Within mcp-server/lib/, pure side-effect-free logic lives in core/
(crypto, vc, documentLoader, zcapChain, claimPredicates,
revocation, challenge, resolver) and IO orchestration lives in tools/
(the MCP tool handlers). Tools compose core functions as a thin imperative
shell.
npm install
npm test # run tests
npm run typecheck # type check
npm run dev --workspace=mcp-server # start MCP server
npm run start --workspace=demo-agent -- valid # valid VC → authorized
npm run start --workspace=demo-agent -- tampered # modified payload → denied
npm run start --workspace=demo-agent -- expired # past TTL → denied| Tool | Input | Output |
|---|---|---|
resolve_did |
did: string |
DID Document JSON |
verify_credential |
credential (VC 2.0 object) |
{valid, issuer, subject, claims, reason?} |
issue_credential |
subjectDid, claims, privateKeyBase64url, expiresInSeconds?, delegatedFrom? |
signed VC 2.0 object (issuer did:key derived from the key) |
check_delegation |
agentDid, requestedAction, credential, requiredClaims?, authProof? |
{authorized, reason} |
create_challenge |
agentDid, ttlSeconds? |
{nonce, issuedAt, expiresAt, agentDid} |
verify_auth |
agentDid, nonce, issuedAt, signatureBase64url, expiresAt? |
{authenticated, reason} |
verify_delegation_chain |
rootCapability, delegatedCapability, agentDid, expectedAction, expectedTarget |
{authorized, reason} |
A human issues a short-lived age-verification credential to an agent. Age is
modeled the way mobile driver's licenses do (ISO/IEC 18013-5): the issuer
precomputes boolean age_over_NN flags from the date of birth, so a verifier
checks age_over_21 without ever seeing the birthdate:
{ "age_over_21": true }- The human's DID signs a VC asserting the agent's age claims
- The agent presents the VC when requesting age-restricted access
- The MCP server verifies the credential and checks the required claims
- Access granted or denied - with a reason, tied to a signed credential
The selective-disclosure demo (sd, sd-unlinkable) takes this further: the
holder reveals only age_over_21 and the verifier never receives the birthdate
or any other claim. See Other models and the
L2 walkthrough.
The demo agent is model-agnostic (built on the Vercel AI SDK) and never
decides access itself: it calls the check_delegation tool and reports that
tool's verdict. Pick a provider with AGENT_PROVIDER or --provider: default
anthropic, ollama (local, no API key), or openai-compatible (DeepSeek,
Kimi, OpenAI, HuggingFace TGI — see Other models).
npm run start --workspace=demo-agent -- valid # valid VC → granted
npm run start --workspace=demo-agent -- tampered # modified VC → denied
npm run start --workspace=demo-agent -- expired # past TTL → denied
npm run start --workspace=demo-agent -- authn # challenge-response auth
npm run start --workspace=demo-agent -- sd # selective disclosure (over 21, no DOB)
npm run start --workspace=demo-agent -- valid --provider=ollamaThe demo loads a .env file from the repo root at startup. Copy the example
and add your key:
cp .env.example .env
# then set ANTHROPIC_API_KEY=... in .envollama needs no key. If a provider that requires a key is selected without
one, the demo fails fast at startup with a clear message (rather than a cryptic
error mid-run).
Provider note. Use
anthropicfor a reliable demo. The localollamadefault (qwen2.5) is a small model whose tool-calling is best-effort — it may mis-call or skipcheck_delegation, so a single run can land on a wrong or NO-DECISION verdict thatanthropicdecides correctly. This is safe by design: the tool is the authority, so a skipped or malformed call denies rather than false-grants — but it makes theollamademo non-deterministic. A largerOLLAMA_MODELimproves reliability.
A built-in openai-compatible provider works with any OpenAI-style endpoint —
DeepSeek, Kimi (Moonshot), OpenAI, or a self-hosted HuggingFace TGI server.
These are popular lower-cost alternatives to the frontier hosted models. Point
it with env vars and select it:
# in .env
OPENAI_COMPATIBLE_BASE_URL=https://api.deepseek.com/v1
OPENAI_COMPATIBLE_MODEL=deepseek-chat
OPENAI_COMPATIBLE_API_KEY=...
npm run start --workspace=demo-agent -- valid --provider=openai-compatibleNot all models are tested. We verify the demo against Anthropic; the
ollamaandopenai-compatiblepaths are wired and unit-tested, but the field of models behind them is large and tool-calling quality varies. If you get a particular model (a DeepSeek/Kimi variant, a HuggingFace model, …) working well — or find one that doesn't — pull requests and notes are welcome. The authorization guarantee holds regardless of the model: a skipped or malformed tool call denies rather than false-grants (R-X-1).
The agent speaks the Vercel AI SDK's unified tool-calling interface, so any AI-SDK provider drops in:
- Install the provider package (e.g. a HuggingFace/community AI-SDK provider).
- In
demo-agent/lib/providers.js, add acasetogetModelreturning{name, model}, and add a row toPROVIDER_KEY_ENVnaming the API-key env var the provider needs (ornullif it needs none — likeollama). The startup preflight then enforces the key automatically. - Select it with
--provider=<name>orAGENT_PROVIDER=<name>, and set any key in.env.
Nothing else changes: the tools, the eval, and the authorization logic are provider-agnostic.
The agent's behaviour is guarded by a deterministic, offline eval (a golden dataset plus tool-deference and leakage canaries) that runs in CI on every push. The eval uses a mock model, so it is unaffected by local-model variance.
- JavaScript (ESM) + JSDoc types / Node - not TypeScript (DB house style)
@modelcontextprotocol/sdk- MCP server@digitalbazaar/vc+@digitalbazaar/data-integrity+eddsa-rdfc-2022- VC 2.0 credentials with Data Integrity proofs@digitalbazaar/ed25519-multikey+@digitalbazaar/did-method-key- keys anddid:keyresolution@digitalbazaar/zcap- authorization-capability delegation chains- Universal Resolver - chain-agnostic DID resolution (fallback)
- Vercel AI SDK (
ai+@ai-sdk/anthropic,ollama-ai-provider-v2) - model-agnostic demo agent