Skip to content

digitalbazaar/agent-credential-server

Repository files navigation

agent-credential-server

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.


Why DIDs + VCs instead of OAuth?

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.


Conformance

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)

Each enforcement point in the source carries a // KYA-OS R-Lx-n comment, so you can read spec → code → test in either direction.


Architecture

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 tools
  • demo-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.


Quick Start

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

MCP Tools

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}

Demo Scenario

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 }
  1. The human's DID signs a VC asserting the agent's age claims
  2. The agent presents the VC when requesting age-restricted access
  3. The MCP server verifies the credential and checks the required claims
  4. 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=ollama

Configuration

The 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 .env

ollama 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 anthropic for a reliable demo. The local ollama default (qwen2.5) is a small model whose tool-calling is best-effort — it may mis-call or skip check_delegation, so a single run can land on a wrong or NO-DECISION verdict that anthropic decides 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 the ollama demo non-deterministic. A larger OLLAMA_MODEL improves reliability.

Other models (OpenAI-compatible)

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-compatible

Not all models are tested. We verify the demo against Anthropic; the ollama and openai-compatible paths 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).

Adding a new named provider

The agent speaks the Vercel AI SDK's unified tool-calling interface, so any AI-SDK provider drops in:

  1. Install the provider package (e.g. a HuggingFace/community AI-SDK provider).
  2. In demo-agent/lib/providers.js, add a case to getModel returning {name, model}, and add a row to PROVIDER_KEY_ENV naming the API-key env var the provider needs (or null if it needs none — like ollama). The startup preflight then enforces the key automatically.
  3. Select it with --provider=<name> or AGENT_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.

Stack

  • 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 and did:key resolution
  • @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

References

About

Chain-agnostic MCP server + demo agent for AI agent authorization via W3C Verifiable Credentials and DIDs.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors