Skip to main content

Demo B — LLM agent with a protected MCP tool

A real LLM travel agent enrolls with HelixID, receives a scoped credential, and calls a protected MCP booking tool. The booking runs only after @helixid/mcp verifies the agent's presentation against the live HelixID API.

This demo also covers revocation and agent-to-agent delegation.

Step 1 — Get an LLM API key

The concierge uses a real LLM to decide when to call the booking tool. Obtain a key from Anthropic, OpenAI, or Azure OpenAI:

Step 2 — Get the demo

git clone https://github.com/helixid/helixid.git
cd helixid
cd examples/e2e-travel-concierge
cp .env.example .env

Edit .env and add your provider and API key:

LLM_PROVIDER=anthropic # anthropic (default) | openai | azure
LLM_API_KEY=your-provider-key

Step 3 — Run it

docker compose up --build

This starts the real issuer API with SQLite and local did:key identities, HelixID Console, a protected MCP server, the LLM agent, and the web chat. A one-shot setup service enrolls one agent, issues its credential, saves its encrypted wallet to the shared volume, and exits.

Which database is the source of truth

The Console/HelixID SQLite database holds real agent trust state: enrollment, issued credentials, scopes, revocation, status lists, and audit events. The Travel Concierge app's persona list is only local demo state used to show selectable agents in the chat UI — it is not the Console database and does not replace HelixID's records.

URLWhat
http://localhost:8090Travel Concierge chat
http://localhost:8080HelixID Console — sign in admin / admin, then open Audit

Step 4 — The four guided use cases

The web chat has four tabs, each exercising a different trust decision against the same MCP server and the same search_flights / book_flight tools:

Use casePersona / credential stateWhat to tryExpected result
1 — Full-access agentConcierge has read:catalog + write:ordersBook a flightBooking succeeds
2 — Read-only agentRuntime-onboarded agent has only read:catalogSearch, then bookSearch succeeds; booking is refused for missing write:orders
3 — Revoked credentialConcierge's issued VC is revoked through the live APIRetry bookingVP is rejected because the status-list bit is revoked
4 — Delegated agentResearch starts with no tool scopes, then receives a Planner-signed child VC with read:catalogSearch, then bookDelegated search succeeds; booking is refused because the child VC lacks write:orders

Use case 1 — Full-access agent

With Concierge Agent selected, type or click a suggestion:

Book flight BA249 for Ada Lovelace

The LLM calls book_flight, the agent signs a fresh VP locally, and the MCP server verifies the credential and its write:orders scope before creating the booking. Refresh Console → Audit to see the enrollment, credential issuance, and successful VP_VERIFIED event.

Use case 2 — Read-only agent

Generate an onboard token in Console, then click Onboard new agent in the Travel Concierge chat and paste the token. The agent service consumes the token, creates a local encrypted wallet, adds only local persona metadata to the manifest, and the new agent appears in the persona selector — no restart.

Select it and search (succeeds — it has read:catalog), then try to book (refused — it lacks write:orders).

Use case 3 — Revoked credential

Select Concierge Agent, open the tab, and click Revoke selected agent. The agent service loads the selected persona's wallet server-side, reads the credential id, and calls POST /v1/vcs/:vcId/revoke with the demo admin key. The browser never sees the wallet, VC, VP, private key, or admin key.

Retry the same booking: the wallet still signs a valid VP, but HelixID rejects it because the live status list now marks the credential revoked. This is the point of status-list revocation — nothing was rotated, and the agent's other credentials are untouched.

Reset with docker compose down -v to issue a fresh Concierge credential.

Use case 4 — Delegated agent

Create the demo Planner Agent (read:catalog + write:orders, delegation depth 1) and Research Agent (read:catalog), then delegate only read:catalog from Planner to Research.

Research can search through the delegated child credential, but booking is refused because that delegated credential lacks write:orders — a child can never exceed its parent, and here it was deliberately given less. See Delegation.

What is enforced where

This path is enforced by the SDK and MCP verifier. The shipped API does not yet expose API-side delegation issuance, nor Console audit for local child-chain verification.

Confirming the tool is actually protected

Call the MCP tool with no presentation at all:

curl -s http://localhost:7100/mcp \
-H 'content-type: application/json' \
-H 'accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/call",
"params":{"name":"book_flight","arguments":{"flightId":"BA249","passengerName":"Mallory"}}}'

The protected tool refuses the booking because HelixID did not receive a valid presentation.

Reset

docker compose down -v

Source: examples/e2e-travel-concierge.