@lucid-agents/express
Express adapter for a completed Lucid HTTP runtime.
The Express adapter translates Node requests and streams to standard Web Request and Response objects, then binds the canonical route plan owned by @lucid-agents/http. It does not own a second entrypoint registry, manifest builder, task runtime, or paywall.
Installation
bun add @lucid-agents/core @lucid-agents/http @lucid-agents/express express
bun add -D @types/expressAdd @lucid-agents/payments when the runtime receives x402 payments. No Express-specific payment package is required.
Basic usage
import { z } from 'zod';
import { createAgent } from '@lucid-agents/core';
import { http } from '@lucid-agents/http';
import { createAgentApp } from '@lucid-agents/express';
const runtime = await createAgent({
name: 'my-agent',
version: '1.0.0',
})
.use(http())
.addEntrypoint({
key: 'greet',
input: z.object({ name: z.string() }),
async handler({ input }) {
return { output: { message: `Hello, ${input.name}!` } };
},
})
.build();
const { app } = await createAgentApp(runtime);
const server = app.listen(3000);Always install http() before creating the app. During shutdown, close the Node server and call await runtime.close().
API reference
createAgentApp(runtime, options?)
Creates an Express app and mounts every route in runtime.http.routes.
const { app, runtime, agent, addEntrypoint } =
await createAgentApp(runtime, options);| Property | Description |
|---|---|
app | Configured Express application. |
runtime | The same completed runtime passed to the adapter. |
agent | The runtime's protocol-agnostic agent core. |
addEntrypoint | Typed delegate to runtime.entrypoints.add(). |
Adding an entrypoint through either API updates the canonical registry and invalidates the generated Agent Card.
CreateAgentAppOptions
type CreateAgentAppOptions = {
beforeMount?: (app: Express) => void;
afterMount?: (app: Express) => void;
};Use beforeMount for middleware that must wrap agent routes. Use afterMount for additional routes or error handlers.
import cors from 'cors';
import helmet from 'helmet';
const { app } = await createAgentApp(runtime, {
beforeMount(app) {
app.set('trust proxy', true);
app.use(helmet());
app.use(cors());
},
afterMount(app) {
app.get('/custom', (_request, response) => {
response.json({ custom: true });
});
},
});The adapter forwards unhandled route errors to Express next(error), so an error handler registered in afterMount can handle them.
Canonical routes
Paths below are relative to http({ basePath }). The default base path is empty.
| Method | Route | Description |
|---|---|---|
GET | / | Landing page when enabled. |
GET | /health | Health status. |
GET | /entrypoints | Discoverable entrypoints. |
POST | /entrypoints/:key/invoke | Invoke an entrypoint. |
POST | /entrypoints/:key/stream | Stream SSE envelopes. |
GET | /.well-known/agent.json | Legacy Agent Card path. |
GET | /.well-known/agent-card.json | Agent Card. |
GET | /.well-known/oasf-record.json | OASF record, or 404 when identity is not enabled. |
GET | /favicon.svg | Agent favicon. |
When the runtime includes a2a(), the same plan also mounts:
| Method | Route | Description |
|---|---|---|
POST | /tasks | Create and start a task. |
GET | /tasks | List tasks owned by the access token. |
GET | /tasks/:taskId | Read an owned task. |
POST | /tasks/:taskId/cancel | Cancel an owned running task. |
GET | /tasks/:taskId/subscribe | Subscribe to owned task updates over SSE. |
Task creation accepts a 20–256 character Task-Access-Token header or generates a token and returns it with the task. All later task operations require that token. Task routes are omitted when a2a() is not installed.
Payments and authentication
Payments are installed on the runtime, before the adapter is created:
import { createAgent } from '@lucid-agents/core';
import { http } from '@lucid-agents/http';
import { payments, paymentsFromEnv } from '@lucid-agents/payments';
import { createAgentApp } from '@lucid-agents/express';
const runtime = await createAgent({
name: 'paid-agent',
version: '1.0.0',
})
.use(payments({ config: paymentsFromEnv() }))
.use(http())
.addEntrypoint({
key: 'premium',
price: '0.01',
async handler() {
return { output: { result: 'premium content' } };
},
})
.build();
const { app } = await createAgentApp(runtime);
app.listen(3000);The HTTP runtime uses one authorization path for invoke, stream, and task creation. x402, MPP, SIWX entitlements, payment-policy admission, settlement, and idempotency therefore behave the same in Express, Hono, and TanStack. If both x402 and MPP are installed, each priced entrypoint must set paymentProtocol: 'x402' | 'mpp'.
Streaming and request bodies
The adapter passes request bodies through as Web streams and pipes response bodies back to Node without buffering. Do not add a JSON parser solely for Lucid routes; the canonical handlers parse their own request bodies. Application-specific middleware can still be registered in beforeMount.
Exports
export { createAgentApp } from '@lucid-agents/express';
export type { CreateAgentAppOptions } from '@lucid-agents/express';