@agentkeychain/cli
v0.1.4
Published
Command-line tool to credential autonomous agents against AgentKeychain.
Maintainers
Readme
@agentkeychain/cli
Bootstrap autonomous agents (OpenClaw, Claude Code, Devin, custom) against AgentKeychain without copy-pasting tokens.
npm install -g @agentkeychain/cli
akc loginThis opens a browser to AgentKeychain, you click Authorize, a one-time code lands on a short-lived 127.0.0.1 callback the CLI is listening on, and the CLI trades it for an agent refresh token. Credentials are written to ~/.agentkeychain/agents.json (mode 0600), keyed by a local name so you can keep several agents on one machine. Your autonomous agent reads from that file via the @agentkeychain/agent SDK.
The default flow is RFC 8252 + PKCE — the same pattern used by gcloud auth login, gh auth login, and stripe login.
Commands
akc login [--as <name>]— authorize an agent on this machine and store it under<name>(default:default). Loopback flow, falls back to the device flow automatically when loopback binding fails.akc login --device [--as <name>]— force the RFC 8628 device flow (see below)akc launch <name> [--mcp-passthrough <servers>] [-- <claude args…>]— launch Claude Code acting as the stored agent<name>(see Running Claude Code as an agent)akc mcp list | set <servers> [--as <agent>] | clear [--as <agent>]— manage which of Claude Code's own MCP servers a launched session may use (see Lending the session other MCP servers)akc status— list the agents configured on this machineakc logout [<name>]— remove one stored agent (or all of them if<name>is omitted)akc soul pull [--as <name>]/akc soul push [--as <name>] [file]— fetch/upload an agent'ssoul.md
Running Claude Code as an agent
Set up each agent once, then launch Claude Code as whichever one you want:
akc login --device --as marketing # pick the "marketing" agent in the browser
akc login --device --as research # …and the "research" agent
akc launch marketing # Claude Code, acting as marketing
akc launch research -- --model opus # extra args after `--` go to `claude`akc launch <name>:
- mints a fresh access token for
<name>from its stored refresh token, - fetches that agent's
soul.mdand injects it as the system prompt (--append-system-prompt-file), - starts a localhost proxy pinned to
<name>that injects — and auto-refreshes — the agent's AKC bearer on every MCP request, and - execs
claude --strict-mcp-config --mcp-config <generated> …so the AgentKeychain MCP server is the only one wired in by default.
Lending the session other MCP servers
--strict-mcp-config means a launched session inherits none of Claude Code's own MCP config — not user scope in ~/.claude.json, not per-project entries, not .mcp.json. That is deliberate (the agent's reach should be what AgentKeychain granted it, not whatever happens to be configured on the machine), but it needs an escape hatch:
akc launch marketing --mcp-passthrough daydream # one server
akc launch marketing --mcp-passthrough daydream,parcl # several
akc launch marketing --mcp-passthrough all # everything configured hereNamed servers are looked up in Claude Code's own config with its own precedence — local (per-project entries in ~/.claude.json) beats project (.mcp.json) beats user — and copied verbatim into the generated config, so stdio commands, headers and ${VAR} interpolation keep working. A name that matches nothing is a warning at launch, never a silent drop. agentkeychain can't be passed through: the session's identity is not up for grabs.
To make it stick, use akc mcp — no JSON editing required:
akc mcp list # what this machine has, and what each agent gets
akc mcp set all # the machine-wide default
akc mcp set daydream,parcl --as martin # just those two, for one agent
akc mcp set none --as tori # opt one agent out of the default
akc mcp clear --as tori # drop tori's entry, back to the defaultakc mcp list answers the question that otherwise means reading ~/.claude.json yourself:
Configured on this machine:
daydream user http://127.0.0.1:7433/mcp
linear-server local (this project only) https://mcp.linear.app/mcp
Effective passthrough:
defaults all → daydream, linear-server
martin daydream → daydream
perry (inherits) → daydream, linear-server
tori [] → nothingThose commands write ~/.agentkeychain/launch.json, which you can also edit by hand. defaults applies to every agent; an agent's own entry overrides it:
{
"version": 1,
"defaults": { "mcp_passthrough": ["all"] },
"agents": {
"marketing": { "mcp_passthrough": ["daydream"] },
"locked": { "mcp_passthrough": [] }
}
}Here marketing gets exactly daydream, locked gets nothing, and every other agent gets everything configured on the machine. Presence is what decides, not emptiness — "mcp_passthrough": [] is a deliberate opt-out of the default, not a fall-through to it.
Precedence is flag > agent entry > defaults > nothing: a --mcp-passthrough on the command line overrides whatever is stored, and --mcp-passthrough none lends nothing for that launch. Each launch prints which of the three it used.
These preferences are machine-local on purpose: which servers exist, and whether you're willing to lend them to an agent, is a property of this machine rather than of the agent record.
Note that claude.ai connectors (Linear, Gmail, Drive, …) are not file config and can't be passed through this way.
Every launch prints its MCP surface, and the session's system prompt says the same thing — so a launched agent knows what it has, knows that claude mcp list describes a different process, and doesn't send you round the "try restarting" loop for a server that was never going to load.
Why this is the secure shape
- The human picks the agent at launch; Claude can't switch. The identity is fixed before
claudestarts, and the session only ever sees a127.0.0.1URL — there is no in-band way for the model to become a different agent. - One token = one agent. No server-side "act-as": a launch uses exactly that agent's own credentials, workspace binding, and workspace-scoped secrets.
- The AKC token never touches argv or disk. The generated MCP config carries only a random per-launch secret that gates the local proxy; the real access token lives in the proxy's memory and is minted on demand, so long sessions don't hit the ~1h token expiry.
claude -a <name> shim (optional)
If you'd rather type claude -a <name>, drop this in your shell rc:
claude() {
if [ "$1" = "-a" ]; then
local name="$2"; shift 2
akc launch "$name" -- "$@"
else
command claude "$@"
fi
}Then claude -a marketing runs akc launch marketing, and plain claude … is untouched.
Headless environments: akc login --device
On machines without a browser or where loopback ports can't be bound (EC2, Docker containers, CI runners, SSH sessions), the loopback flow doesn't work. Pass --device (or let the CLI auto-fall-back) to use the RFC 8628 device authorization grant instead — the same pattern gh auth login uses on headless machines:
$ akc login --device
To authorize this CLI, visit:
https://agentkeychain.com/device
and enter code: BCDF-GHJK
(or open: https://agentkeychain.com/device?user_code=BCDF-GHJK)
Waiting for approval…You open the URL in any browser (on your laptop, phone, wherever you can sign in), type the 8-letter code, and approve. The CLI polls until the server confirms, then writes tokens as usual. The two machines never talk to each other directly — each one independently talks to AgentKeychain.
Phishing-resistance
The consent page on /device displays the requester IP and a client hint (hostname@platform) for the machine that ran akc login --device. If someone DMs you a user_code and asks you to approve it, those fields will show a machine that isn't yours — cancel in that case. The page includes an explicit warning: "Only approve if YOU started this process."
Env vars
AKC_AUTH_SERVER— auth server URL (where the CLI posts to/v1/device/authorize,/v1/agents/register,/v1/device/token). Default:https://api.agentkeychain.com.AKC_DASHBOARD_URL— dashboard URL (where the loopback flow opens/cli-login). Default:https://app.agentkeychain.com. Only set this if the dashboard is on a non-default origin (e.g. local dev).
Local dev
AKC_AUTH_SERVER=http://localhost:3000 \
AKC_DASHBOARD_URL=https://app.lvh.me:5173 \
node dist/index.js login