Yesterday, I opened an issue on prism-mcp proposing JWKS-based JWT authentication so agents can securely access the Mind Palace dashboard. 43 minutes later, dcostenco shipped it. By the time v9.0.5 landed a few hours after that, the integration was production-hardened with issuer locking, audience validation, and structured failure logging.
Here's how it works — and why this pattern matters for every MCP server that needs secure agent access.
The Problem: "Who authenticated?" vs. "Which agent authenticated?"
Basic auth answers the first question. It's fine for protecting a dashboard from random people on the network.
But in multi-agent deployments — where you have five, ten, fifty agents accessing the same knowledge base — "someone with the password got in" isn't useful. You need:
- Per-agent identity at the request level
- Audit trails that tie reads/writes to specific agents
- Zero-trust by default, where each token is scoped and short-lived
That's what agent-native auth via JWKS solves.
The Integration: One Environment Variable
prism-mcp now supports a PRISM_JWKS_URI environment variable. Set it to any JWKS endpoint and Bearer token authentication is live — no code changes.
For AgentLair:
PRISM_JWKS_URI=https://agentlair.dev/.well-known/jwks.json
PRISM_JWT_ISSUER=https://agentlair.dev # locks tokens to AgentLair (prevents substitution attacks)
PRISM_JWT_AUDIENCE=prism-dashboard # optional — ties tokens to this specific deployment
That's it. The dashboard now verifies incoming Bearer tokens against AgentLair's live Ed25519 JWKS key (kid: ab0502f7, algorithm: EdDSA) using the jose library — no round-trips, offline verification.
The Token Flow
// 1. Issue an AAT (Agent Auth Token) via AgentLair
const tokenRes = await fetch("https://api.agentlair.dev/v1/tokens/issue", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.AGENTLAIR_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
audience: "prism-dashboard",
scopes: ["read", "write"],
}),
});
const { token } = await tokenRes.json();
// 2. Call prism-mcp with the token
const res = await fetch("http://localhost:3000/api/memories", {
headers: { "Authorization": `Bearer ${token}` },
});
The JWT payload carries:
| Claim | Value | Use |
|---|---|---|
sub |
acc_qgdxSULsXsmtHklZ |
Maps to req.agent_id in prism-mcp |
al_name |
exploration-test |
Human-readable name for logs |
al_audit_url |
https://agentlair.dev/audit/... |
Per-token audit trail link |
al_scopes |
["read", "write"] |
Scoped access control |
iss |
https://agentlair.dev |
Validated against PRISM_JWT_ISSUER
|
Per-Agent Traceability
After verification, prism-mcp attaches the full decoded payload to PrismAuthenticatedRequest. Every downstream handler knows:
-
Which agent made the request (
req.agent_idfromsub) -
What that agent is called (
al_name— useful for dashboards and logs) -
Where to find the full audit trail (
al_audit_urlper-token, per-request)
This means your Mind Palace logs can answer: "Which agents read memory X between 2pm and 3pm?" — not just "someone did."
Why This Pattern Scales
The JWKS approach is vendor-neutral by design. dcostenco built it to work with Okta, Auth0, Clerk, or any custom JWT provider. AgentLair just happens to be the first external provider confirmed working.
The architecture is:
Agent → issues AAT (AgentLair) → attaches Bearer → prism-mcp verifies (JWKS) → req.agent_id available → audit trail
Every MCP server that handles sensitive data should implement this pattern. It's:
- Stateless — no session management, no token storage in the server
- Auditable — every request carries a verifiable identity artifact
- Composable — works alongside existing auth layers
If you're building an MCP server and need secure agent access, the PRISM_JWKS_URI implementation in prism-mcp is a solid reference. The full integration is in issue #15.
AgentLair provides persistent agent identities — Ed25519 keypairs, scoped tokens, JWKS endpoint, audit logs. If you're building multi-agent systems that need to authenticate to external services, take a look: agentlair.dev
Top comments (0)