Phase 6: dedicated auth worker — RFC 7591 DCR + RFC 8414 metadata + JWKS rotation, all via iii primitives
Tracks an additive follow-up to the v0.4.0 overhaul (#45). Targets v0.5.0.
Why
Phase 1 of #45 delegates exposure to iii-worker-manager RBAC via auth_function_id. That's the hook. It is currently empty — every deploy must write its own auth function from scratch.
The MCP + A2A specs both lean on the same OAuth 2.1 primitives:
- RFC 7591 — Dynamic Client Registration. Clients onboard at runtime via
POST /register. Without it, every Claude Desktop / Cursor / IDE that hits a federated MCP needs an admin to register manually at each server. Doesn't scale past ~5 servers. Anthropic's MCP marketplace flow assumes DCR.
- RFC 8414 — Authorization Server Metadata.
/.well-known/oauth-authorization-server. Tells clients which scopes / grant types / PKCE methods the IdP supports. We already cover the resource-server side (RFC 9728); 8414 is the auth-server side. Both required for full client discovery.
- JWKS rotation, token introspection, IdP capability matrix (Okta / Auth0 / Entra / Google / Keycloak / Ping / ForgeRock). Builders pick IdP first, write code second. Without this content next to the worker, they wire Entra-backed MCP, hit the 7591 wall in prod, rip out + redo.
Current state: each MCP/A2A deploy reimplements this stack, usually as FastAPI middleware.
Iron rule: everything through iii primitives. No FastAPI middleware bolted on the side. Auth becomes a worker the same way every other capability is a worker.
Surface
New crate iii-hq/workers/auth/. Sibling shape to introspect/ and llm-router/ (~1500-2000 LOC).
Functions registered:
| Function ID |
Trigger |
Purpose |
RFC |
auth::validate |
(called via iii.trigger by iii-worker-manager) |
Token introspection — returns AuthResult { allowed_functions, forbidden_functions, context } |
— |
auth::server_metadata |
HTTP GET /.well-known/oauth-authorization-server |
Authorization server discovery doc |
8414 |
auth::resource_metadata |
HTTP GET /.well-known/oauth-protected-resource |
Resource server discovery doc |
9728 |
auth::register |
HTTP POST /register |
Dynamic Client Registration |
7591 |
auth::jwks |
HTTP GET /.well-known/jwks.json |
Public key set |
— |
auth::jwks_rotate |
cron (e.g. daily) |
Generate new keypair, retire old, update state::set |
— |
auth::token |
HTTP POST /token |
Token issuance + refresh + introspection |
6749 / 7662 |
State scopes:
auth:clients — registered clients, indexed by client_id. Survives worker restart.
auth:jwks — current + previous keypairs (rotation overlap window).
auth:tokens — refresh tokens + revocation list.
CLI flags:
--engine-url <URL>
--issuer <URL> # public base URL advertised in metadata
--idp-mode <KIND> # local | bridge:keycloak | bridge:okta | bridge:auth0 | bridge:entra
# local issues + verifies own JWTs; bridge: federates to external IdP
--rotation-cron <EXPR> # default "0 3 * * *" (daily 3am UTC)
--debug
Wiring into mcp + a2a
No code changes in the protocol workers. The deploy authors config.yaml:
workers:
- name: iii-worker-manager
config:
rbac:
auth_function_id: auth::validate
expose_functions:
- metadata:
public: true
- name: iii-auth
config:
issuer: https://api.acme.dev
idp-mode: bridge:keycloak
- name: iii-mcp # served as POST /mcp
- name: iii-a2a # served as GET /.well-known/agent-card.json + POST /a2a
Now every tools/call (MCP) and message/send (A2A) flows through auth::validate. JWKS rotation is automatic. DCR endpoint is live. Auth-server metadata is discoverable. None of this lives in the mcp or a2a crate.
IdP capability matrix
Document in auth/README.md next to the worker. Initial roster (from Posta's research):
| IdP |
RFC 7591 DCR |
RFC 8414 metadata |
PKCE |
Notes |
| Keycloak |
yes |
yes |
required |
reference IdP for bridge:keycloak; full demo in README |
| Okta |
yes |
yes |
required |
bridge config straightforward |
| Auth0 |
yes |
yes |
required |
bridge config straightforward |
| Entra ID |
no |
yes |
required |
DCR not supported — clients must be pre-registered manually |
| Google |
no |
yes |
required |
DCR not supported |
| Ping |
yes |
yes |
required |
bridge config |
| ForgeRock |
yes |
yes |
required |
bridge config |
Builders read this BEFORE picking. Saves the rip-and-redo cycle.
Differentiation vs incumbents
Posta ships FastAPI middleware. iii ships a worker that any protocol bridge consumes. mcp/a2a stay narrow protocol transports — auth doesn't pollute their crates. JWKS rotation is a cron trigger, not a thread you have to remember to start. DCR is a function any worker can override (e.g. inject domain validation) by registering auth::register with higher priority. That last move is impossible in middleware-shaped frameworks.
Out of scope
- mTLS / client cert flows.
- SAML / WS-Federation.
- Per-tenant key isolation (single-tenant for v0.5.0; multi-tenant tracked separately).
- Token issuance for non-OAuth flows.
Sequencing
Lands as v0.5.0 after #45 ships v0.4.0. No dependency on Phase 2c (Streamable HTTP SSE) — auth works against the existing Authorization header pathway today.
Phase 6: dedicated
authworker — RFC 7591 DCR + RFC 8414 metadata + JWKS rotation, all via iii primitivesTracks an additive follow-up to the v0.4.0 overhaul (#45). Targets v0.5.0.
Why
Phase 1 of #45 delegates exposure to
iii-worker-managerRBAC viaauth_function_id. That's the hook. It is currently empty — every deploy must write its own auth function from scratch.The MCP + A2A specs both lean on the same OAuth 2.1 primitives:
POST /register. Without it, every Claude Desktop / Cursor / IDE that hits a federated MCP needs an admin to register manually at each server. Doesn't scale past ~5 servers. Anthropic's MCP marketplace flow assumes DCR./.well-known/oauth-authorization-server. Tells clients which scopes / grant types / PKCE methods the IdP supports. We already cover the resource-server side (RFC 9728); 8414 is the auth-server side. Both required for full client discovery.Current state: each MCP/A2A deploy reimplements this stack, usually as FastAPI middleware.
Iron rule: everything through iii primitives. No FastAPI middleware bolted on the side. Auth becomes a worker the same way every other capability is a worker.
Surface
New crate
iii-hq/workers/auth/. Sibling shape tointrospect/andllm-router/(~1500-2000 LOC).Functions registered:
auth::validateiii.triggerbyiii-worker-manager)AuthResult { allowed_functions, forbidden_functions, context }auth::server_metadataGET /.well-known/oauth-authorization-serverauth::resource_metadataGET /.well-known/oauth-protected-resourceauth::registerPOST /registerauth::jwksGET /.well-known/jwks.jsonauth::jwks_rotatestate::setauth::tokenPOST /tokenState scopes:
auth:clients— registered clients, indexed byclient_id. Survives worker restart.auth:jwks— current + previous keypairs (rotation overlap window).auth:tokens— refresh tokens + revocation list.CLI flags:
Wiring into mcp + a2a
No code changes in the protocol workers. The deploy authors
config.yaml:Now every
tools/call(MCP) andmessage/send(A2A) flows throughauth::validate. JWKS rotation is automatic. DCR endpoint is live. Auth-server metadata is discoverable. None of this lives in the mcp or a2a crate.IdP capability matrix
Document in
auth/README.mdnext to the worker. Initial roster (from Posta's research):bridge:keycloak; full demo in READMEBuilders read this BEFORE picking. Saves the rip-and-redo cycle.
Differentiation vs incumbents
Posta ships FastAPI middleware. iii ships a worker that any protocol bridge consumes. mcp/a2a stay narrow protocol transports — auth doesn't pollute their crates. JWKS rotation is a cron trigger, not a thread you have to remember to start. DCR is a function any worker can override (e.g. inject domain validation) by registering
auth::registerwith higher priority. That last move is impossible in middleware-shaped frameworks.Out of scope
Sequencing
Lands as v0.5.0 after #45 ships v0.4.0. No dependency on Phase 2c (Streamable HTTP SSE) — auth works against the existing
Authorizationheader pathway today.