Install the server alongside it's depednencies:
uv pip install -e .Run the server with:
run-credential-checking-serverOR, alternatively:
uv run -m src.credential_checking_serverThen use a TMCP-enabled client to connect to it using the server's DID (see the demo folder in the TMCP repository for some TMCP-enabled clients).
For fast-agent, use:
uv run fast-agent go --url <server-did-here>This section provides a complete guide to testing the credential checking server demo.
- Python 3.11+
uvpackage manager- fast-agent client
# Terminal 1: Start server
$ uv run -m src.credential_checking_server
Server DID: did:webvh:Qmb15Uwt...
# Terminal 2: Generate credentials,
# keep note of the generated file `test_presentation.txt`
$ uv run examples/generate_test_credential.py \
<SERVER DID HERE> \
"did:webvh:QmYYY:issuer.example.com" \
"did:webvh:QmZZZ:holder.example.com"
# Terminal 1: Restart server (Ctrl+C then restart)
$ uv run -m src.credential_checking_server
# Terminal 2: Connect with fast-agent
$ uv run fast-agent go --url "did:webvh:Qmb15Uwt..."
# In fast-agent:
# First: Run the `list_required_credential` tool
> list_required_credential
# Second: Note the `nonce` you get in the output,
# Put the `nonce` in the string in `test_presentation.txt` and send it to the TMCP client.
> submit_credential format="sd-jwt" presentation="eyJ..." nonce="<nonce from step 1>"
# Expected: {"status": "success", ...}Note
The nonce generated by example script in test_presentation.txt needs to be replaced by the nonce generated by the list_required_credentials tool. This is a workaround and does not reflect how nonce binding should be implemented according to the spec.
Start the server and note the server DID from the output:
uv run -m src.credential_checking_serverTip
Copy the Server DID - you'll need it in the next step.
Run the example script to generate "demo credentials" with the server DID:
uv run examples/generate_test_credential.py \
"<SERVER_DID HERE>" \
"did:webvh:QmYYY:issuer.example.com" \
"did:webvh:QmZZZ:holder.example.com"Arguments:
| Argument | Description | Example |
|---|---|---|
server_did |
The verifier's DID (from Step 1) | did:webvh:Qmb15Uwt... |
issuer_did |
The credential issuer's DID | did:webvh:QmYYY:issuer.example.com |
holder_did |
The credential holder's DID | did:webvh:QmZZZ:holder.example.com |
Generated Files:
| File | Description |
|---|---|
issuer_public_key.json |
Issuer's public key for verification |
holder_key.json |
Holder's private key for signing |
test_presentation.txt |
Ready-to-use submission command |
The server needs to reload the new issuer_public_key.json:
# Stop the previous server (Ctrl+C)
# Then restart:
uv run -m src.credential_checking_serverIn a new terminal, connect to the server:
uv run fast-agent go --url <server-did-here>Get the server's DID for credential binding.
Request:
get_server_didResponse Schema:
{
"server_did": "did:webvh:Qmb...",
"usage": "Use this DID as the 'aud' claim when generating credentials"
}Get the credential requirements for accessing this service.
Request:
list_required_credentialsResponse Schema:
{
"requirements": [
{
"format": "sd-jwt",
"required_claims": [
{
"claim": "given_name",
"purpose": "To verify your identity"
},
{
"claim": "family_name",
"purpose": "To verify your identity"
}
],
"trusted_issuers": [
{
"did": "did:webvh:QmYYY:issuer.example.com",
"name": "Trusted Issuer",
"description": "Registered credential issuer"
}
],
"credential_types": ["IdentityCredential"]
}
],
"presentation_definition": {
"nonce": "abc123xyz...",
"expires_at": "2025-12-16T12:00:00Z",
"max_age": 300
},
"verifier": {
"did": "did:webvh:Qmb15Uwt...",
"name": "TMCP Credential Checking Server",
"purpose": "Verify identity for service access"
}
}Submit a credential presentation for verification.
Request:
Copy the command from test_presentation.txt:
submit_credential format="sd-jwt" presentation="<JWT>~<Disclosure1>~<Disclosure2>~<KB-JWT>" nonce="test_nonce_XXXXXXXXXX"Success Response Schema:
{
"status": "success",
"message": "Credential verified successfully",
"verification_result": {
"verified_at": "2025-12-16T11:55:05Z",
"holder": {
"did": "did:webvh:QmZZZ:holder.example.com",
"verified": true
},
"issuer": {
"did": "did:webvh:QmYYY:issuer.example.com",
"name": "Trusted Issuer",
"verified": true,
"trusted": true
},
"credential": {
"issued_at": "2025-12-16T10:00:00Z",
"expires_at": "2025-12-16T14:00:00Z",
"type": "IdentityCredential"
},
"disclosed_claims": {
"given_name": "John",
"family_name": "Doe"
}
}
}Failure Response Schema:
{
"status": "failure",
"error": {
"code": "INVALID_ISSUER",
"message": "Credential issuer is not in the trusted registry",
"details": {
"issuer_did": "did:webvh:unknown:issuer",
"reason": "Issuer not found in trusted registry"
}
},
"verification_result": {
"verified_at": "2025-12-16T11:55:05Z",
"holder": {
"verified": false
},
"issuer": {
"verified": false,
"trusted": false
}
}
}| Error Code | Cause | Resolution |
|---|---|---|
INVALID_ISSUER |
Issuer DID not in trusted registry | Use a trusted issuer or add to registry |
INVALID_SIGNATURE |
Cryptographic verification failed | Regenerate credential with correct keys |
EXPIRED_CREDENTIAL |
Credential past expiration | Issue a new credential |
INVALID_HOLDER |
Holder DID mismatch | Ensure sub matches session client DID |
INVALID_AUDIENCE |
aud claim doesn't match server DID |
Use correct server DID in presentation |
INVALID_NONCE |
Nonce mismatch or expired | Use the nonce from list_required_credentials |
MISSING_CLAIMS |
Required claims not disclosed | Disclose all required claims |
INVALID_DISCLOSURE |
Disclosure hash doesn't match | Regenerate presentation |
# Terminal 1: Start server
$ uv run -m src.credential_checking_server
Server DID: did:webvh:Qmb15Uwt...
# Terminal 2: Generate credentials,
# keep note of the generated file `test_presentation.txt`
$ uv run examples/generate_test_credential.py \
"did:webvh:Qmb15Uwt..." \
"did:webvh:QmYYY:issuer.example.com" \
"did:webvh:QmZZZ:holder.example.com"
# Terminal 1: Restart server (Ctrl+C then restart)
$ uv run -m src.credential_checking_server
# Terminal 2: Connect with fast-agent
$ uv run fast-agent go --url "did:webvh:Qmb15Uwt..."
# In fast-agent:
# First: Run the `list_required_credential` tool
> list_required_credential
# Second: Note the `nonce` you get in the output,
# Put the `nonce` in the string in `test_presentation.txt` and send it to the TMCP client.
> submit_credential format="sd-jwt" presentation="eyJ..." nonce="<nonce from step 1>"
# Expected: {"status": "success", ...}Per the Specification: The server should resolve issuer public keys from their DIDs:
Credential.iss = "did:webvh:issuer.example.com"
│
▼
Server resolves DID → DID Document → Public Key
│
▼
Server verifies signature
Current Implementation (Temporary Workaround):
Since DID resolution for did:webvh is not yet implemented, the server uses a cached key file:
issuer_public_key.json → Server loads key → Verifies signature
Files involved:
issuer_public_key.json- Temporary cache of issuer's public key- This file will be removed once DID resolution is implemented
TODO:
- Implement
did:webvhDID resolution - Remove
issuer_public_key.jsonworkaround - Update trusted issuer registry to only store DIDs