Skip to content
pringlizedPublic

About

aINZ (Agent Identity, AuthN & AuthZ) — a small Rust security tool for MCP servers that gives each AI agent a secure pass, controls exactly which MCP tools it's allowed to call, and checks that pass on every call, so an agent can only ever do what you've approved. Runs entirely on your own machine.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

22 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

aINZ

Agent Identity, AutheNtication & AuthoriZation — a small, self-contained agent-security stack for MCP, written from scratch in Rust.

status: prototype rev1 language: Rust license: Apache 2.0

aINZ credentials an agent, decides what it may do, and enforces that decision at the one place that can actually see a tool call — the MCP boundary. It borrows ideas from Ory's Hydra, Keto and Oathkeeper (opaque tokens resolved by central introspection, relationship-based permissions, and enforcement at the boundary) and implements a deliberately minimal subset — roughly a thousand lines of Rust — tuned for one job: securing an agent's access to an MCP server. It is not a clone or a repackaging of those services. Everything runs in-process on a single offline machine: no auth server, no network hops between trusted components, nothing that phones home.

It answers three questions, each owned by exactly one piece:

Question Owner Idea borrowed from
Who is this agent? — issue + resolve a keycard mint + verify::introspect Hydra
May it do this? — per-tool decision over a rule graph verify::check Keto (Zanzibar)
Enforced where? — block the call before the tool runs the MCP server itself — the enforcement point (PEP) Oathkeeper

The shape in one paragraph

When you stand up an agent, you first decide the exact set of MCP tools it is allowed to call — its approved surface. You hand that list to identity.register(), which in one atomic step issues the agent a keycard (a random, meaningless token — think of a hotel key card) and records its rules (which tools it may invoke). The keycard has no power of its own; it is just a lookup handle, and all the real authority lives in aINZ's registry — so revoking an agent is a single DELETE. From then on, every time the agent tries to call a tool, the MCP server checks the keycard: is it still valid (verify::introspect), and does a rule allow this agent to call this tool (verify::check)? It then allows the call or denies it.

What's original here

  • Genuine per-tool authorization middleware — every real tool call is a live authz decision, not a static capability list (verified absent from the official Rust MCP SDK and from common reference servers). Discovery is fully decoupled from it: tools/list shows every tool's name + one-line summary up front and describe_tools returns full schemas on demand, but invoking anything still runs the live check.
  • Service-scoped grants — a keycard's rules are qualified by service_id, so the same bare tool name (e.g. secret_read) resolves independently across two genuinely separate MCP deployments (mcp-one / mcp-two) with no collisions.

Quickstart

Requirements: Rust (cargo), the sqlite3 CLI, and — for the console — Node/npm. jq is optional (nicer demo output).

# 1. build the decision engine + the MCP server
cargo build --manifest-path libs/Cargo.toml --features dev    # ainz lib + ainz-identity CLI
cargo build --manifest-path mcp-one/Cargo.toml # the MCP server

# 2. create the local store (idempotent — safe to re-run)
libs/scripts/init-db.sh                         # → libs/ainz.db

# 3. see it work
./libs/scripts/identity-demo.sh                 # issuance: one atomic register
./mcp-one/scripts/mcp-demo.sh                   # tool discovery + describe_tools over stdio
./mcp-one/scripts/authz-demo.sh                 # grant & deny, proven end-to-end

# 4. the console (optional, interactive)
cd frontend && npm install && npm run dev       # → http://localhost:5190

authz-demo.sh is the one to watch: it registers a docs-only agent, then drives the real server with that keycard — doc_read → ALLOW, deploy_run → DENY, bogus token → DENY — the same tool and server, decided entirely by verify::introspect + verify::check:

$ ./mcp-one/scripts/authz-demo.sh

▶ register 'sess-authz-…' with DOCS-ONLY perms (no deploy tools):
  token: mlk_ba430b8e4904…  (granted: doc_* only)

── valid keycard (docs-only) ─────────────────────────────────
doc_read   (granted)  → {"isError":false,"text":{"authorized_agent":"sess-authz-…","tool":"doc_read","ok":true}}
deploy_run (ungranted)→ {"isError":true, "text":"authorization denied: sess-authz-… may not invoke deploy_run"}
audit:
{"tool":"doc_read",  "agent":"sess-authz-…","decision":"allow","reason":"granted"}
{"tool":"deploy_run","agent":null,          "decision":"deny", "reason":"sess-authz-… may not invoke deploy_run"}

── bogus keycard ─────────────────────────────────────────────
doc_read (bad token)  → {"isError":true,"text":"authorization denied: inactive or unknown keycard"}

✔ same tool, same server — allow vs deny decided by verify::introspect + verify::check.

The Console

An interactive web UI (SvelteKit, port 5190) for testing MCP authorization. Think MCP Inspector — except the sidebar mints a real aINZ keycard, and every panel exercises the actual binaries over stdio. No mocks, no fixtures.

The aINZ Console — MCP Tools: the meta discovery surface and a live, authorized tool call

The sidebar — mint an identity. Set the human principal (sub) and a TTL, tick the tool surface (everything except secret_read, a permanently-denied tool, is checked by default), and hit + New Agent Identity. That shells out to ainz-identity register --json and hands back a real keycard — the token is shown once. Revoke tears the agent down (card and rules) atomically.

Four tabs, each a different vantage point on that live agent:

Tab Shows Backed by
Request live preview of the exact registration request as you edit the fields GET /api/identity
Identity the minted keycard — token, agent id, issued / expires POST /api/identity → ainz-identity register
Permissions the agent's relation_tuples (the doors it holds) next to the known tools it has no rule for sqlite3 read of the verify graph
MCP Tools what the agent sees on connect — the meta discovery tools (list_tools / describe_tools / ping / whoami) — plus a live tools/call query ainz-mcp over stdio

Tool query. Fire a real tools/call at ainz-mcp using the live keycard — name a tool, add JSON args, and optionally a claimed service_id. Allow/deny reflects that agent's actual grants (verify::introspect + verify::check). With no identity minted, the call runs in dev mode (unauthenticated, everything allowed).

Layout

Five top-level directories:

ainz/
├── libs/        the ainz Rust crate — the decision engine
│   ├── src/         model · token · storage (core) · mint · verify · identity
│   ├── migrations/  the SQLite schema (agent_registry, relation_tuples)
│   └── scripts/     init-db.sh, identity-demo.sh
├── mcp-one/     an MCP server (rmcp) — the PEP; tool discovery + authz middleware
│   └── scripts/     mcp-demo.sh (discovery), authz-demo.sh (grant & deny)
├── mcp-two/     a second, independent MCP server — proves service-scoped authz
├── frontend/    the Console (SvelteKit) — drives the real binaries, no mocks
└── docs/        full design docs (mkdocs-material) + mkdocs.yml at the root

The libs/ crate is a single package today (ainz) exposing six modules across a strict dependency DAG — core (model · token · storage) → verify / mint → identity. Each owns exactly one duty; identity is a thin transaction boundary that composes the other two. When those modules earn their own crates, libs/ becomes a Cargo workspace.

Status

Built & tested

  • identity / mint / verify — issuance, the rule graph, and the authorization decision (read + write)
  • verify::check (Zanzibar DFS with inheritance) + verify::introspect (keycard → identity)
  • The MCP server: tool discovery (list_tools / describe_tools) and the enforcement middleware (grants & denies)
  • The Console driving all of it against the real binaries
  • 19 lib unit tests + six demo scripts

Chosen but not yet wired

  • Encrypted SQLite (SQLCipher) — the prototype uses plain SQLite today
  • Bearer identity over stdio rides an env var (AINZ_TOKEN) — a prototype simplification

Deferred until a use case demands it

  • gate — a standalone body-inspecting proxy PEP. Only needed for an MCP server you don't control (can't inject middleware there); until then the server enforces itself.
  • Macaroon-based delegation — for a credential that must cross a trust boundary you don't control and be verified with no live authority present. Not the per-agent flow.

Scope

This is the decision layer — identity, authn, authz — and it stops at the MCP boundary. What process presents the token, and whether it could reach the MCP by some other route, is out of scope (that's a sandboxing concern). aINZ decides and enforces at the point a tool call is actually visible; it doesn't claim to be the only door to the data.

Docs

Full design writeup — architecture, the tuple model, per-component deep dives, a from-scratch glossary (macaroon, HMAC, ReBAC, Zanzibar, …), and the design decisions behind every trade-off — lives in docs/:

mkdocs serve        # → http://localhost:8899   (requires mkdocs-material)

Prototype — rev1. Licensed under Apache 2.0.

About

aINZ (Agent Identity, AuthN & AuthZ) — a small Rust security tool for MCP servers that gives each AI agent a secure pass, controls exactly which MCP tools it's allowed to call, and checks that pass on every call, so an agent can only ever do what you've approved. Runs entirely on your own machine.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages