Composable middleware to make your web app AI-agent-friendly. One line of code.
Most websites weren't built for AI agents. They return 200 OK for pages that don't exist, serve 500K tokens of JavaScript for a simple page, and have no machine-readable way to discover what they do. We scored 20 popular sites — the average agent-readiness score was 38%.
agent-layer fixes this with composable middleware for Express, Koa, Hono, and Fastify:
import express from "express";
import { agentLayer } from "@agent-layer/express";
const app = express();
app.use(agentLayer({
rateLimit: { max: 100, windowMs: 60_000 },
llmsTxt: { title: "My API", description: "REST API for widgets" },
discovery: { manifest: { name: "My API", description: "REST API for widgets" } },
}));That one call adds structured errors, rate limit headers, /llms.txt, agent discovery, and more.
| Package | Description | Install |
|---|---|---|
@agent-layer/core |
Framework-agnostic core logic — errors, rate limits, llms.txt, discovery, MCP, A2A, x402, analytics | npm i @agent-layer/core |
@agent-layer/express |
Express middleware (v4 & v5) | npm i @agent-layer/core @agent-layer/express |
@agent-layer/koa |
Koa middleware | npm i @agent-layer/core @agent-layer/koa |
@agent-layer/hono |
Hono middleware (v4+) | npm i @agent-layer/core @agent-layer/hono |
@agent-layer/fastify |
Fastify plugin (v4+) | npm i @agent-layer/core @agent-layer/fastify |
@agent-layer/strapi |
Strapi 4 plugin — auto-generate agent endpoints from content types | npm i @agent-layer/core @agent-layer/strapi |
@agent-layer/firestore |
Firestore adapter — schema declaration, query translation, Express/Koa routes | npm i @agent-layer/core @agent-layer/firestore |
Python version: agent-layer-python — FastAPI, Flask, and Django support.
All four framework adapters support the full feature set. Strapi and Firestore are specialized packages.
| Feature | Core | Express | Koa | Hono | Fastify | Strapi | Firestore |
|---|---|---|---|---|---|---|---|
| Structured errors | ✓ | ✓ | ✓ | ✓ | ✓ | ||
| Rate limiting | ✓ | ✓ | ✓ | ✓ | ✓ | ||
| llms.txt | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | |
| Discovery (AI manifest) | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | |
| Agent meta (HTML) | ✓ | ✓ | ✓ | ✓ | |||
| OAuth discovery | ✓ | ✓ | ✓ | ✓ | ✓ | ||
| Analytics | ✓ | ✓ | ✓ | ✓ | ✓ | ||
| API key auth | ✓ | ✓ | ✓ | ✓ | ✓ | ||
| x402 payments | ✓ | ✓ | ✓ | ✓ | ✓ | ||
| A2A agent card | ✓ | ✓ | ✓ | ✓ | ✓ | ||
| Agent identity (JWT/SPIFFE) | ✓ | ✓ | ✓ | ✓ | ✓ | ||
| agents.txt | ✓ | ✓ | ✓ | ✓ | ✓ | ||
| Unified discovery | ✓ | ✓ | ✓ | ✓ | ✓ | ||
| MCP server | ✓ | ✓ | ✓ | ✓ | ✓ | ||
| Content-type introspection | ✓ | ||||||
| Schema / query translation | ✓ |
import express from "express";
import { agentLayer } from "@agent-layer/express";
const app = express();
app.use(agentLayer({
errors: true,
rateLimit: { max: 100, windowMs: 60_000 },
llmsTxt: { title: "My API", description: "REST API for widgets" },
discovery: { manifest: { name: "My API", description: "REST API for widgets" } },
}));
app.listen(3000);import Koa from "koa";
import { agentLayer } from "@agent-layer/koa";
const app = new Koa();
const router = agentLayer({
rateLimit: { max: 100, windowMs: 60_000 },
llmsTxt: { title: "My API", description: "REST API for widgets" },
discovery: { manifest: { name: "My API", description: "REST API for widgets" } },
});
app.use(router.routes()).use(router.allowedMethods());
app.listen(3000);import { Hono } from "hono";
import { agentLayer } from "@agent-layer/hono";
const app = new Hono();
app.route("/", agentLayer({
rateLimit: { max: 100, windowMs: 60_000 },
llmsTxt: { title: "My API", description: "REST API for widgets" },
discovery: { manifest: { name: "My API", description: "REST API for widgets" } },
}));
export default app;import Fastify from "fastify";
import { agentLayer } from "@agent-layer/fastify";
const fastify = Fastify();
await fastify.register(agentLayer({
rateLimit: { max: 100, windowMs: 60_000 },
llmsTxt: { title: "My API", description: "REST API for widgets" },
discovery: { manifest: { name: "My API", description: "REST API for widgets" } },
}));
await fastify.listen({ port: 3000 });Each module works standalone — use what you need. The examples below use Express; all other adapters export the same functions.
Catches all errors and returns structured JSON that agents can parse. No more 200 OK for 404s.
import { agentErrors, notFoundHandler } from "@agent-layer/express";
app.use(notFoundHandler());
app.use(agentErrors());{
"error": {
"type": "not_found_error",
"code": "ROUTE_NOT_FOUND",
"message": "No route matches GET /api/nonexistent",
"status": 404,
"is_retriable": false
}
}Adds X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset to every response. Returns 429 with Retry-After when limits are hit.
import { rateLimits } from "@agent-layer/express";
app.use(rateLimits({ max: 100, windowMs: 60_000 }));Serves /llms.txt — a plain-text description of your site in ~500 tokens instead of 500,000.
import { llmsTxtRoutes } from "@agent-layer/express";
const llms = llmsTxtRoutes({ title: "My API", description: "REST API" });
app.get("/llms.txt", llms.llmsTxt);
app.get("/llms-full.txt", llms.llmsFullTxt);Serves /.well-known/ai with a JSON manifest pointing to your OpenAPI spec, llms.txt, auth endpoints, and docs.
import { discoveryRoutes } from "@agent-layer/express";
const discovery = discoveryRoutes({
manifest: { name: "My API", description: "REST API" },
});
app.get("/.well-known/ai", discovery.wellKnownAi);
app.get("/openapi.json", discovery.openApiJson);Serve /.well-known/agent.json per Google's A2A protocol:
import { a2aRoutes } from "@agent-layer/express";
const a2a = a2aRoutes({
card: {
name: "My Service",
description: "What my service does",
skills: [{ id: "search", name: "Search", description: "Search things" }],
},
});
app.get("/.well-known/agent.json", a2a.agentCard);Accept payments from AI agents via the x402 protocol. Agents hit a protected endpoint, get 402 Payment Required, pay with USDC stablecoin, and retry:
import { x402Payment } from "@agent-layer/express";
app.use(x402Payment({
facilitatorUrl: "https://x402.org/facilitator",
payeeAddress: "0xYourWallet",
network: "base-sepolia",
}));Verify agent credentials per the IETF draft-klrc-aiagent-auth:
import { agentIdentity } from "@agent-layer/express";
app.use(agentIdentity({
issuer: "https://my-app.example.com",
audience: "https://my-app.example.com",
jwksUri: "https://my-app.example.com/.well-known/jwks.json",
}));Detect AI agent traffic, collect telemetry, and batch flush to your analytics backend:
import { agentAnalytics } from "@agent-layer/express";
app.use(agentAnalytics({
flushIntervalMs: 10_000,
onFlush: (events) => console.log(`${events.length} agent events`),
}));Issue and validate scoped API keys for agent access:
import { apiKeyAuth, requireScope } from "@agent-layer/express";
app.use(apiKeyAuth({ store: myKeyStore }));
app.get("/api/admin", requireScope("admin"), adminHandler);Serve /agents.txt to declare which AI agents can access your site and how:
import { agentsTxtRoutes } from "@agent-layer/express";
const agentsTxt = agentsTxtRoutes({
rules: [
{ agent: "*", allow: ["/api/*"], disallow: ["/admin/*"] },
],
enforce: true,
});
app.get("/agents.txt", agentsTxt.agentsTxt);
app.use(agentsTxt.enforce());Generate a Model Context Protocol server from your existing API routes. AI agents discover and call your endpoints via standard MCP JSON-RPC:
import { mcpServer } from "@agent-layer/express";
const mcp = mcpServer({
name: "my-api",
version: "1.0.0",
instructions: "Use these tools to manage users",
routes: [
{ method: "GET", path: "/api/users", summary: "List users",
parameters: [{ name: "limit", in: "query", description: "Max results" }] },
{ method: "POST", path: "/api/users", summary: "Create user",
parameters: [{ name: "name", in: "body", required: true }] },
],
});
app.use("/mcp", mcp.router());Supports Streamable HTTP transport (POST for JSON-RPC, GET for SSE, DELETE for session end).
Generate all discovery formats from one config — /.well-known/ai, /.well-known/agent.json, /llms.txt, /llms-full.txt, /agents.txt, and /openapi.json:
import { unifiedDiscovery } from "@agent-layer/express";
const handlers = unifiedDiscovery({
name: "My API",
description: "REST API for widgets",
baseUrl: "https://api.example.com",
skills: [{ id: "search", name: "Search", description: "Full-text search" }],
formats: { aiManifest: true, a2a: true, llmsTxt: true, agentsTxt: true },
});
app.get("/.well-known/ai", handlers.wellKnownAi);
app.get("/.well-known/agent.json", handlers.agentCard);
app.get("/llms.txt", handlers.llmsTxt);
app.get("/llms-full.txt", handlers.llmsFullTxt);
app.get("/agents.txt", handlers.agentsTxt);Serves /.well-known/oauth-authorization-server so agents know how to authenticate programmatically:
import { agentAuth } from "@agent-layer/express";
const auth = agentAuth({
issuer: "https://my-app.example.com",
authorizationEndpoint: "https://my-app.example.com/oauth/authorize",
tokenEndpoint: "https://my-app.example.com/oauth/token",
});
app.get("/.well-known/oauth-authorization-server", auth.oauthDiscovery);Injects data-agent-id attributes on interactive elements and ARIA landmarks so browser-based agents can find buttons and forms:
import { agentMeta } from "@agent-layer/express";
app.use(agentMeta({ prefix: "app" }));The @agent-layer/score package is a Lighthouse-style CLI that scores your API's agent-readiness across 13 checks: structured errors, discovery endpoints, llms.txt, rate limit headers, OpenAPI, CORS, security headers, agents.txt, and more.
npx @agent-layer/score https://myapp.comOur automated tests spin up real HTTP servers and verify the score difference — a bare server scores 16/100 while the same server with agentLayer() middleware scores 100/100. See packages/score/src/scanner-e2e.test.ts for the full test.
Add a badge to your README to show your API's agent-readiness score:
[](https://github.com/lightlayer-dev/agent-layer-ts "Scored by @agent-layer/score")The score CLI outputs the badge markdown automatically:
npx @agent-layer/score https://myapp.com --badgeScored by @agent-layer/score from LightLayer.
pnpm install
pnpm build
pnpm test # 954 tests across 118 test files- LightLayer Gateway — A standalone reverse proxy that makes any API agent-ready with zero code changes. Same features as agent-layer, but deployed as infrastructure instead of middleware.
- agent-layer-python — Python equivalent for FastAPI, Flask, and Django.
MIT — LightLayer