Skip to content

Tutorial: quickstart

Goal

At the end you will store a secret, issue a wrapped token, and watch an agent list the credential and call an API with it, without the value ever coming back.

Every command and output below comes from a real run, recorded in demo/tutorials/quickstart/expected/ and checked by run.sh --check.

Run it

bash demo/tutorials/quickstart/run.sh all
bash demo/tutorials/quickstart/run.sh --check

Script: demo/tutorials/quickstart/run.sh.

1. The human stores a secret and a policy

Outside the chat, as the vault administrator. API_URL is stored too: any value can be a reference, not only passwords.

# The human stores a secret and a read policy (outside the chat)

$ bao kv put -mount=secret my-project/dev API_TOKEN=tok-fake-not-a-real-secret API_URL=http://127.0.0.1:18080/health
======= Secret Path =======
secret/data/my-project/dev

======= Metadata =======
Key                Value
---                -----
created_time       <time>
custom_metadata    <nil>
deletion_time      n/a
destroyed          false
version            1
exit_code=0

$ bao policy write my-project-read deploy/policy-project-read.hcl
Success! Uploaded policy: my-project-read
exit_code=0

2. The agent sees the tools, the session and the names

Each khcall is one MCP session, and each session uses a fresh wrapped token. keyholder_status reports the TTL and policies but never the token; keyholder_list returns names only.

# The agent sees the tools, the session and the reference names

$ BAO_WRAPPING_TOKEN=<wrapped token> khcall -tools
keyholder_list
keyholder_run
keyholder_status
exit_code=0

$ BAO_WRAPPING_TOKEN=<wrapped token> khcall keyholder_status keyholder_list
### keyholder_status
{
  "addr": "http://127.0.0.1:18200",
  "initialized": true,
  "kv_mount": "secret",
  "scope": "my-project",
  "sealed": false,
  "server_version": "2.7.1",
  "token_expire_time": "<time>",
  "token_policies": [
    "default",
    "my-project-read"
  ],
  "token_renewable": true,
  "token_ttl_seconds": <ttl>
}
### keyholder_list
{
  "entries": [
    {
      "ref": "vault://my-project/dev/API_TOKEN",
      "version": 1
    },
    {
      "ref": "vault://my-project/dev/API_URL",
      "version": 1
    }
  ],
  "note": "Use {{vault://path/KEY}} in the args, env or files of keyholder_run.",
  "scope": "my-project"
}
exit_code=0

3. The agent calls an API with the token

The test API answers with the header it received. The agent reads [hidden:...]; the last line confirms, by hash, that the API received the real stored value. The audit line on stderr names the command and the references, never the value.

# The agent calls an API with the token; the value is masked

$ BAO_WRAPPING_TOKEN=<wrapped token> khcall keyholder_run '{"command":"curl","args":["-sS","-H","Authorization: Bearer {{vault://my-project/dev/API_TOKEN}}","{{vault://my-project/dev/API_URL}}"]}'
{
  "exit_code": 0,
  "refs_used": [
    "vault://my-project/dev/API_TOKEN",
    "vault://my-project/dev/API_URL"
  ],
  "stderr": "",
  "stdout": "api says: hello, caller with header Authorization: Bearer [hidden:vault://my-project/dev/API_TOKEN]\n"
}
--- stderr
<timestamp> audit cmd="curl" refs=[vault://my-project/dev/API_TOKEN vault://my-project/dev/API_URL] exit=0 dur=<duration> status=ok
exit_code=0

Did the API receive the stored token? yes