Identity control plane for AI agents & MCP servers

The authority you can't delegate and still be you.

As AI agents start acting on your behalf — calling tools, reaching data, spending budgets — the real question isn't can this agent prove who it is? It's can I still decide, right now, what it's allowed to do? AIAgentPrincipal keeps a human in control of an agent's authority, checked on every request and revocable the instant you change your mind — including from the AI itself.

// the whole product in one line: revocation that beats a still-valid token.

live decision · checked per request
# the agent calls a tool it's scoped for
enforce  tool:call:search     200 granted

# you change your mind — kill that one token
revoke   ait_9f3e…            revoked ✓

# the SAME call, seconds later
enforce  tool:call:search     403 denied  ← before its TTL
the token is still cryptographically valid.
the answer is still no.
The one idea

A token is a claim, not a key to the kingdom.

Every other system treats a bearer token as the authority: a valid signature plus an unexpired clock means you're in, and nobody can take it back until it dies on its own. For a person clicking a button, fine. For an agent that fans out thousands of times a minute, a token you can't recall is the wrong model.

Here the signed token — an AIT, an Agent Identity Token — is only an assertion of identity. Authority is decided on every request by a policy enforcement point that checks the live principal.

Disable an agent, or revoke one token, and every outstanding call dies on the next request — not at the next TTL.

That gap — between "the token is still cryptographically valid" and "the call is refused anyway" — is the entire point. You never stop being the one in control of you.

Agent holds an AIT request PEP verify sig check LIVE principal every call live principal state allow granted deny revoked / disabled revoke(token) ──▶ the SAME call now denied before its TTL · agent can stay live
allowed now recalled decided per request
Why this exists

It came out of building FaradayMail. The identity layer wanted out.

AIAgentPrincipal came out of building FaradayMail — a private communications product I'm developing across a couple of nodes, not yet in production. Building it, I kept answering the same question: who is this, and what are they allowed to do right now? The identity was internal, because the product needed it.

But the more I built, the clearer it got: that layer didn't want to stay welded inside one app. It wanted to be external — metaversable. Something any system could reach for if it needed to. So alongside the mail product, I split it out and built this: an IdP for AI agents and the objects that act for us.

There was a second realization, less comfortable. It was so easy to reach for the commercial clouds to build exactly what I wanted — even when I had local inference models that could do the job. That convenience is a gravity well.

By design, that pull drags smaller companies into consumption they don't control: someone else's identity, someone else's authority, someone else's bill — and someone else's kill switch.

So I made the identity layer its own thing, and I made it assert control. As AI objects start acting for us, the authority to say what they may do — and to take it back — should belong to you, runnable on infrastructure you own, not rented from the thing you're trying to stay independent of.

— the reason this is a separate, self-hostable control plane, and not a feature buried in someone's product.

What it does

The parts a security engineer attacks first — built, and built to be read.

Model- and host-agnostic. Works whether the agent is Claude, another hosted model, or something you run on-prem; whether your directory is Active Directory, LDAP, or Entra; whether your store is SQLite or Postgres.

Live authority, not bearer

A per-request PEP checks the live principal, so agent- and token-level revocation take effect on the next call — not at TTL.

Signed, short-lived tokens

EdDSA (ed25519) AITs verified with verify_strict. Signing keys rotate live; retired keys keep verifying old tokens.

Attestation, verified

Workload-identity JWTs (SPIFFE / K8s / cloud) are verified at registration — not just captured — and can fail closed.

Directory federation

An agent's owner resolves against the directory you already run — Active Directory, any LDAP, or Entra.

Key encrypted at rest

The signing seed is sealed (XChaCha20-Poly1305) under a KEK that can come from a cloud KMS by envelope encryption. The DB holds only ciphertext.

Tamper-evident audit

Every decision is an append-only, hash-chained JSONL record. Edit, delete, or reorder a line and the chain breaks — provably, offline.

mTLS + bearer

In-process TLS 1.3, and mutual TLS: callers prove a client certificate chaining to a pinned CA on top of the bearer key.

MCP enforcing gateway

A reverse proxy in front of an MCP server maps each JSON-RPC call to a scope and calls enforce before forwarding — fail-closed.

Durable & HA

SQLite or Postgres behind one Store trait; Postgres coordinates multiple instances with advisory locks.

Spend

Authority is a cost. So the control plane meters it.

Every decision is counted per agent, and spend accrues whether or not you cap it — so the numbers are there to bill against or chargeback from day one. Give an agent a budget and it's enforced on the same hot path as revocation: the call that would go over is refused on the next request, not at the token's TTL.

live decision · metered per request
# this agent has a 3-credit budget. token TTL: 3600s.
enforce  tool:call:search     200 granted   used 1/3
enforce  tool:call:search     200 granted   used 2/3
enforce  tool:call:search     200 granted   used 3/3
enforce  tool:call:search     403 quota_exhausted  ← budget spent
the token is valid for another 59 minutes.
the next call is still refused.

Meter every call

Calls, allowed, denied, last-seen and spend tracked per agent — GET /v1/usage. Put a price on a credit and it reads as a dollar figure.

Budgets, enforced live

A per-agent credit cap denied at the boundary — quota_exhausted on the next request, before the TTL. Top it up with one call.

Reclaim the idle

Last-seen per agent surfaces the ones gone quiet — the authority, and spend, a terminations pass recovers. Revoke in one call.

Where it fits

You're not the first to see this. The question is who gets to run one.

The agent-identity control plane is where the whole field is heading — Microsoft, Okta, the PAM giants and the standards bodies are all converging on it. AIAgentPrincipal is built to sit on that consensus, not around it — and to hand it to the people the enterprise platforms price out.

Built on the open standards

  • Per-request enforcement — the PEP/PDP model the MCP authorization spec points to: decide at the moment the tool is called, and never pass the token through.
  • Live revocation — the same goal as CAEP's Session-Revoked signal, checked synchronously on every request instead of pushed and hoped for.
  • Workload attestation — SPIFFE-style JWT-SVID verified at registration: the authorization layer SPIFFE deliberately leaves out.

Owned by you, not rented

  • One self-hostable binary — not a licensed tier of a platform you already rent (Entra Agent ID, Okta for AI Agents), and not a full IAM suite you need a team to run. Stand it up in an afternoon.
  • Model-, host-, directory-agnostic — any model, cloud or on-prem, AD / LDAP / Entra, SQLite or Postgres.
  • Authority stays on infrastructure you own. The gap in this market was never awareness — it's access. That's the part this closes.
Trust, honestly

Confidence without inflation.

The security-critical properties are enforced in code and checked automatically — all of them enabled in one process, driven end to end over mutual TLS, on every commit, against both backends.

Authority is live, not bearerallow → revoke that token → the same call is denied, before TTLverified
Token integrityed25519, verify_strict — rejects malleable / small-order signaturesverified
Attestation gateno evidence → 403; signed workload JWT → verified, SPIFFE identity recordedverified
Key encrypted at restDB row is AIPk ciphertext, never the raw seed — SQLite & Postgresverified
Mutual TLSvalid client cert → allowed; no cert / rogue cert → refused at handshakeverified
Audit integrityedits and deletions both caught by the hash chainverified
KEK from cloud KMSenvelope logic tested vs a fake KMS; real AWS path compiles, needs a live CMKpartial
⚠
Pre-1.0, single-author, not yet independently audited. The directory, KMS, and Postgres paths are verified against test instances, not a production deployment. This is a working, inspectable foundation you can read end to end — not a turnkey, certified product. The full threat model (assets, trust boundaries, adversaries mapped to code, and known gaps) ships in the repo.
Get started

See the thesis fire in thirty seconds.

Bring up the whole stack — control plane, enforcing gateway, a mock MCP server — and watch a token get used, revoked, and refused mid-flight while the agent itself stays live.

demo.sh
# whole stack in one command
docker compose up --build

# issue a token, use it, revoke it, use it again
POST /v1/agents/:id/token        → { token, jti }
POST /v1/gateway/enforce          → 200 granted
POST /v1/tokens/revoke            → { revoked: true }
POST /v1/gateway/enforce          → 403 revoked_or_disabled  # same call, before TTL