Skip to main content
Pre-release

This release is a pre-release and may not be stable for production use.

kx-auth-cli — the kx auth CLI

The agent-facing auth CLI for the KX MCP composition container. It ships the kx console script and the auth command group: introspect (validate a bearer), login (acquire one via the IdP your MCP server advertises), exchange (swap a token for a backend-scoped one), and assert (inspect or exercise the kdb+ identity-assertion handshake).

It is distributed and run where the agent/client runs, not the server — so it depends only on the lean kx-auth-core (the one implementation of bearer validation + the outbound token-exchange seam, shared with the container) plus httpx, and never on fastmcp. The client-side install stays light.

Built for an agent. Every subcommand supports --json (a structured envelope, never prose) and a stable exit-code contract, so an agent branches on the code, not the text. Human-readable output is the default; --json is the agent path.

Table of contents

Install

Within this workspace, run it straight through uv:

uv run kx auth --help
uv run kx auth introspect --help     # every subcommand has its own --help / -h

Standalone (client-side), install the package and call the kx script directly (pipx / uv tool keep it isolated):

uv tool install kx-auth-cli          # or: pipx install kx-auth-cli
kx auth --help

The exit-code contract

Stable across every subcommand — branch on the code, not the text:

Code Meaning HTTP analogy
0 ok / allowed 200
1 error — malformed input, unreachable endpoint, unexpected failure 400/500
2 usage — bad flags/args, or a missing required input (argparse's own exit)
3 auth-required — no/expired credential; the agent should login 401
4 denied — valid credential, authorization refused 403

--json emits {"status": …, …} on success and {"status": …, "reason": …} on failure.

kx auth introspect

Validate a bearer against the same keys the container enforces (the shared kx-auth-core verifier). Stateless — it never touches the token cache. Token source (first found wins): argument$KX_AUTH_TOKEN → stdin.

# token + key as flags, structured verdict
kx auth introspect "$TOKEN" --public-key-path key.pub \
  --issuer https://idp --audience my-api --json

# config + token entirely from the environment (matches the container)
KX_MCP_AUTH=static KX_MCP_AUTH_PUBLIC_KEY_PATH=key.pub \
  KX_AUTH_TOKEN="$TOKEN" kx auth introspect --json

# validate against a live JWKS endpoint, token piped on stdin
echo "$TOKEN" | kx auth introspect --jwks-uri https://idp/.well-known/jwks.json

Verification config defaults from the KX_MCP_AUTH* environment (the same vars the container reads); the flags (--jwks-uri / --public-key[-path] / --issuer / --audience / --algorithm / --required-scopes) override per call.

kx auth login

Acquire a bearer for an MCP server via the device-code flow, against the authorization server the server advertises — you never hand-configure a realm, client-id, or AS URL.

  • --server is the MCP-server (resource) URL you connect to — not a backend. The container publishes RFC 9728 Protected Resource Metadata naming its authorization server; kx auth discovers the AS and runs the RFC 8628 device-code flow directly against it (the spec forbids the resource server passing the token through). The acquired token is audienced to the MCP-server resource; backend-scoped tokens come from a separate exchange.
  • Client identity is DCR-first: when the AS advertises a registration_endpoint, the client is registered dynamically (RFC 7591). --client-id / $KX_AUTH_CLIENT_ID overrides.
  • The acquired credential is written to the token cache, so exchange can chain off it.
kx auth login --server https://mcp.example --json
kx auth login --server https://mcp.example \
  --client-id my-public-client --scope "kdbx.read offline_access"

The verification prompt (the URL + user code to approve) is printed to stderr, so a --json result on stdout stays clean.

Exit codes: 0 cached · 1 discovery/network/no-device-support/DCR failure · 3 the device code expired or timed out before approval · 4 the user denied the request.

kx auth exchange

Swap a subject token for a backend-scoped credential — a thin wrapper over the shared kx_auth_core.exchange seam (the same outbound code path the container's backends use, so the wire shape and audit chain are one implementation). This is the workload-identity bootstrap target (kubectl create token / gh actions-tokenkx auth exchange → backend-scoped token).

Default strategy is rfc_8693; passthrough and service_account (and custom registered strategies) are selectable with --strategy. Subject source (first found wins): --subject$KX_AUTH_TOKEN → stdin → the login cache for --server (the seamless loginexchange chain). service_account needs no subject.

# RFC 8693 swap (subject as a flag), structured output
kx auth exchange --subject "$TOK" --audience kdbai \
  --token-url https://idp/token --client-id mcp-container --client-secret "$SECRET" --json

# chain off a prior login — subject pulled from the cache for that server
kx auth login --server https://mcp.example --json
kx auth exchange --server https://mcp.example --audience backend-api --token-url https://idp/token --json

# workload-identity bootstrap (token from the platform, on stdin)
kubectl create token my-sa | kx auth exchange --audience kdbai --token-url https://idp/token --json

Exit codes: 0 minted/forwarded · 1 misconfig / unreachable endpoint / no token returned · 2 no subject for a strategy that needs one · 3 the cached login for --server has expired (run kx auth login again) · 4 the strategy refused (e.g. a passthrough audience-guard mismatch).

kx auth assert

Inspect the identity projection that the container would send to plain kdb+, or exercise the full identity-assertion handshake against a live q process. Without --connect, the command projects claims into the shared principal wire format and prints the result; this mode needs neither kdb+ nor PyKX. With --connect HOST:PORT, it logs in over qIPC as the service account, calls .kx.auth.bind, checks .kx.auth.valid[], and optionally runs a q expression supplied with --probe.

Claims source (first found wins): --principal JSON (- for stdin or @file for a file) → --token$KX_AUTH_TOKEN. JWT claims are decoded without verification: assert is a local projection/handshake diagnostic, not a replacement for introspect.

# project-only: inspect exactly what the container would bind
kx auth assert --principal '{"sub":"alice","scope":"kdbx.read","aud":"kx-mcp"}' --json

# bind and verify against a live q process (requires kx-auth-cli[qipc])
kx auth assert --token "$TOK" --connect localhost:5010 \
  --user kxmcp --password "$SVC_PW" --probe "select from trades" --json

The live handshake requires the optional qIPC dependency (pip install 'kx-auth-cli[qipc]'). Prefer $KX_AUTH_KDB_PASSWORD over --password so the service-account password does not land in shell history or process listings; $KX_AUTH_KDB_USER supplies the user when --user is omitted.

Exit codes: 0 projected or bound successfully · 1 invalid claims / connection / module error · 2 missing input or invalid --connect value · 4 the optional probe was denied by q-side policy.

Token cache

login writes a single JSON credential file, read by exchange:

  • Path: ~/.kx/credentials.json, overridable via $KX_AUTH_CACHE. Mode 0600.
  • Keyed by the --server (MCP-server / resource) URL, so multiple deployments coexist.
  • Each entry holds access_token / refresh_token / token_type / scope / expires_at / issued_at / authorization_server.
  • exchange refuses an entry whose expires_at has passed (exit 3 — re-login) rather than sending a dead token to the STS.
  • introspect is stateless and never touches it. OS-keyring storage is a later hardening.

Environment variables

Variable Used by Purpose
KX_AUTH_TOKEN introspect, exchange, assert The bearer / subject token when not passed as an argument. assert decodes its claims without verification.
KX_AUTH_CLIENT_ID login OAuth client id when not registering dynamically (--client-id overrides).
KX_AUTH_CLIENT_SECRET exchange Client secret when not passed as --client-secret — preferred, since a flag lands in shell history and process listings.
KX_AUTH_CACHE login, exchange Override the token-cache path (default ~/.kx/credentials.json).
KX_AUTH_KDB_USER assert --connect Service-account user for the qIPC login when --user is omitted.
KX_AUTH_KDB_PASSWORD assert --connect Service-account password for the qIPC login when --password is omitted.
KX_MCP_AUTH* introspect The verification config the container reads (KX_MCP_AUTH, KX_MCP_AUTH_PUBLIC_KEY[_PATH], KX_MCP_AUTH_JWKS_URI, KX_MCP_AUTH_ISSUER, KX_MCP_AUTH_AUDIENCE, KX_MCP_AUTH_ALGORITHM, KX_MCP_AUTH_REQUIRED_SCOPES).

The fastmcp-free invariant

This CLI runs client-side and must ship light, so it depends on kx-auth-core + httpx only and must never import fastmcp. Two things hold the line:

  • introspect's verifier is the shared kx_auth_core.verify_token, kept in agreement with the container's JWTVerifier by tests/deterministic/unit/test_auth_cli_contract.py.
  • login's discovery / device-code / token-cache machinery is CLI-local, not in kx-auth-core — the container never logs in and the cache is a client-side concern, which keeps the shared core's charter (mechanisms reused by container + bundles + CLI) clean.
  • assert uses the shared, fastmcp-free identity projection. PyKX is imported lazily only for the optional --connect path and is distributed through the qipc extra, not the base CLI.

The user-facing auth reference is the auth guide under docs/ in the kx-mcp-server-container repository. The deeper design material is maintained in the project's internal design docs — the command-surface contract (mediation model, command set, cache, exit codes, workload bootstrap), the benchmark that fed the mediation-model decision, and the outbound token-exchange seam that exchange wraps.

Release files for kx-auth-cli 0.5.0b1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Built distribution (wheel)

Table of built distributions (wheels) for kx-auth-cli 0.5.0b1
File Interpreter ABI Platform
kx_auth_cli-0.5.0b1-py3-none-any.whl Python 3 none any Details

Release files / kx_auth_cli-0.5.0b1-py3-none-any.whl

Download URL kx_auth_cli-0.5.0b1-py3-none-any.whl
Size 24.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
734f4242adbc02c431b9af7c56bfa99a28efa5e2dd1b15c8bdafa2b2bd8ebfba
BLAKE2b-256 checksum
How to use checksums
65423c4a2ee35ed780526859f5534bce24e803ad1b8319e6533b0985cbdb3453
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.9.2

Release history Release notifications | RSS feed

This release

0.5.0b1 This release

1 release file

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page