Skip to content

Express Connector

@caracalai/mcp-express provides caracalAuth(), an Express RequestHandler that verifies Caracal mandates on every inbound request. It extracts the bearer token from the Authorization header, verifies the ES256 signature and revocation status, and attaches the verified Claims to the request object. Requests that fail verification receive a JSON error response and are not forwarded to downstream handlers.

Terminal window
npm install @caracalai/mcp-express @caracalai/revocation
import { caracalAuth } from '@caracalai/mcp-express';
function caracalAuth(opts: MiddlewareOptions): RequestHandler

Returns an Express middleware function. Attach it globally on the router or per-route.

MiddlewareOptions extends AuthDeps from @caracalai/transport-mcp with two additional fields:

interface MiddlewareOptions extends AuthDeps {
bindContext?: boolean; // Bind verified identity to SDK async context; default: true
ephemeralAgent?: {
coordinator: CoordinatorClient;
applicationId: string;
};
}
// AuthDeps fields (all required unless marked optional):
interface AuthDeps {
issuer: string; // STS base URL; JWKS at {issuer}/.well-known/jwks.json
audience: string; // Must match 'aud' claim
zoneId?: string; // If set, 'zone_id' claim must match
requiredScopes?: string[];
requireAgent?: boolean;
requireDelegation?: boolean;
requireChainContains?: string[];
maxHopCount?: number; // Default: 10
revocations: RevocationStore;
}

bindContext: true (the default) binds the verified identity into the SDK’s AsyncLocalStorage context, making caracal.current() available inside the handler. Set bindContext: false to skip this — useful when you only need req.caracalClaims without SDK context propagation.

ephemeralAgent is optional. When provided, the middleware spawns a short-lived agent session with the Coordinator for the duration of the request, attaching it to both the request object and the SDK context.


The middleware augments the Express Request type. Import the extended type for TypeScript access:

import type { CaracalRequest } from '@caracalai/mcp-express';
app.get('/tool', caracalAuth(opts), (req: CaracalRequest, res) => {
const claims = req.caracalClaims!; // Claims from @caracalai/identity
const ctx = req.caracalContext; // CaracalContext (set when bindContext is true)
});

Attached fields:

interface CaracalRequest extends Request {
caracalClaims?: Claims; // Verified identity claims (always set on success)
caracalContext?: CaracalContext; // SDK context (set when bindContext is true)
}
interface CaracalContext {
subjectToken: string;
zoneId: string;
clientId: string;
agentSessionId?: string;
delegationEdgeId?: string;
parentEdgeId?: string;
sessionId?: string;
traceId?: string;
hop?: number;
}

Claims is the same type exported by @caracalai/identity — see Identity Package.


Failed verification produces a JSON response and halts the middleware chain. The handler is never called.

Error codeHTTP statusCondition
missing_token401No Authorization header or not in Bearer format
invalid_token401Signature invalid, expired, or malformed JWT
invalid_zone401zone_id claim does not match opts.zoneId
session_revoked401Session is in the revocation store
insufficient_scope403Missing a required scope
agent_required403requireAgent is true but agent_session_id absent
delegation_required403requireDelegation is true but delegation_edge_id absent
chain_mismatch403Required application not in delegation chain
hop_count_exceeded403Hop count exceeds maxHopCount

Response body:

{
"error": "insufficient_scope",
"error_description": "Missing required scope: tool:call"
}

Global middleware:

import express from 'express';
import { caracalAuth } from '@caracalai/mcp-express';
import { InMemoryRevocationStore } from '@caracalai/revocation';
const revocations = new InMemoryRevocationStore();
const app = express();
app.use(express.json());
app.use(
caracalAuth({
issuer: process.env.CARACAL_STS_URL!,
audience: 'resource://my-mcp-server',
requiredScopes: ['tool:call'],
revocations,
}),
);
app.post('/tool/search', (req: CaracalRequest, res) => {
const { sub, scope } = req.caracalClaims!;
res.json({ ok: true, subject: sub });
});

Per-route scope enforcement:

app.post(
'/tool/admin',
caracalAuth({
issuer: process.env.CARACAL_STS_URL!,
audience: 'resource://my-mcp-server',
requiredScopes: ['tool:call', 'admin:write'],
revocations,
}),
handler,
);

Production Redis revocation:

import { RedisRevocationStore, RedisRevocationConsumer } from '@caracalai/revocation-redis';
import { createClient } from 'redis';
const redis = createClient({ url: process.env.REDIS_URL });
await redis.connect();
const revocations = new RedisRevocationStore(redis);
const consumer = new RedisRevocationConsumer(redis, revocations, {
consumer: `mcp-server-${process.env.INSTANCE_ID}`,
});
await consumer.ensureGroup();
// Poll in background
setInterval(() => consumer.pollOnce(), 1000);
app.use(caracalAuth({ issuer, audience, revocations }));

See the Redis connector reference for full consumer configuration.