Skip to main content

Server Adapters API Reference

Complete reference for @juspay/neurolink/server -- the HTTP server layer for NeuroLink.

import {
createServer,
createAllRoutes,
HonoServerAdapter,
// ... ~120 exports
} from "@juspay/neurolink/server";

Table of Contents​


Factory and Base Class​

Convenience function that creates a server adapter from a NeuroLink instance.

function createServer(
neurolink: NeuroLink,
options?: {
framework?: ServerFramework; // default: "hono"
config?: ServerAdapterConfig;
},
): Promise<BaseServerAdapter>;

ServerAdapterFactory​

Static factory class for creating adapters. Supports dynamic imports so unused frameworks are never bundled.

MethodSignatureDescription
create(options: ServerAdapterFactoryOptions) => Promise<BaseServerAdapter>Create adapter by framework name
createHono(neurolink, config?) => Promise<BaseServerAdapter>Shortcut for Hono
createExpress(neurolink, config?) => Promise<BaseServerAdapter>Shortcut for Express
createFastify(neurolink, config?) => Promise<BaseServerAdapter>Shortcut for Fastify
createKoa(neurolink, config?) => Promise<BaseServerAdapter>Shortcut for Koa
registerAdapter(framework, adapterClass) => voidRegister a custom adapter class
isSupported(framework: string) => booleanCheck if a framework is supported
getSupportedFrameworks() => Array<{framework, status, description}>List all supported frameworks
getRecommendedFramework() => ServerFrameworkReturns "hono"

BaseServerAdapter​

Abstract base class that all framework adapters extend. Extends EventEmitter.

MethodSignatureDescription
initialize() => Promise<void>Initialize routes, middleware, framework
start() => Promise<void>Start listening (abstract)
stop() => Promise<void>Stop server with graceful shutdown (abstract)
registerRoute(route: RouteDefinition) => voidRegister a single route
registerRouteGroup(group: RouteGroup) => voidRegister a route group with prefix
registerMiddleware(middleware: MiddlewareDefinition) => voidRegister middleware
getStatus() => ServerStatusGet running status, uptime, route count
listRoutes() => RouteDefinition[]List all registered routes
getConfig() => RequiredServerAdapterConfigGet resolved configuration
getLifecycleState() => ServerLifecycleStateGet current lifecycle state
getActiveConnectionCount() => numberNumber of active connections
getFrameworkInstance() => unknownGet underlying framework instance (abstract)

ServerAdapterConfig​

All fields are optional; defaults are applied by the base class.

FieldTypeDefaultDescription
portnumber3000Server port
hoststring"0.0.0.0"Server host
basePathstring"/api"Base path for all routes
corsCORSConfigenabled, origins ["*"]CORS settings
rateLimitRateLimitConfigenabled, 100 req/15 minRate limiting
bodyParserBodyParserConfigenabled, 10 MB limitBody parsing
loggingLoggingConfigenabled, level "info"Request logging
timeoutnumber30000Request timeout (ms)
enableMetricsbooleantrueExpose /api/metrics
enableSwaggerbooleanfalseEnable OpenAPI docs
disableBuiltInHealthbooleanfalseSkip built-in /health, /ready
redactionRedactionConfigdisabledStream redaction settings
shutdownShutdownConfig30s shutdown, 15s drainGraceful shutdown behavior
carryMiddlewareStatebooleanfalseCarry middleware state (opt-in)

ShutdownConfig​

FieldTypeDefaultDescription
gracefulShutdownTimeoutMsnumber30000Max time for entire shutdown
drainTimeoutMsnumber15000Max time to drain connections
forceClosebooleantrueForce-close after timeout

Server Lifecycle States​

"uninitialized" | "initializing" | "initialized" | "starting" | "running" | "draining" | "stopping" | "stopped" | "error"

Events (ServerAdapterEvents)​

EventPayload
initialized{ config, routeCount, middlewareCount }
started{ port, host, timestamp }
stopped{ uptime, timestamp }
request{ requestId, method, path, timestamp }
response{ requestId, statusCode, duration, timestamp }
error{ requestId?, error, timestamp }

Framework Adapters​

All adapters extend BaseServerAdapter and share the same public API. They differ in which underlying HTTP framework they wrap.

ClassFrameworkMulti-runtimeNotes
HonoServerAdapterHonoNode.js, Bun, Deno, EdgeRecommended. Auto-detects runtime.
ExpressServerAdapterExpressNode.jsDynamic-imports express, cors, express-rate-limit
FastifyServerAdapterFastifyNode.jsDynamic-imports fastify
KoaServerAdapterKoaNode.jsDynamic-imports koa, @koa/router, @koa/cors
import { HonoServerAdapter } from "@juspay/neurolink/server";

const adapter = new HonoServerAdapter(neurolink, { port: 8080 });
await adapter.initialize();
await adapter.start();

Middleware​

All middleware factory functions return MiddlewareDefinition objects. Register them with adapter.registerMiddleware(mw).

type MiddlewareDefinition = {
name: string;
order?: number; // informational: middleware runs in registration order
handler: MiddlewareHandler;
paths?: string[]; // apply to these paths (default: all)
excludePaths?: string[]; // skip these paths
};

type MiddlewareHandler = (
ctx: ServerContext,
next: () => Promise<unknown>,
) => Promise<unknown>;

Authentication Middleware​

createAuthMiddleware(config)​

General-purpose authentication middleware supporting bearer, API key, basic, and custom strategies.

function createAuthMiddleware(config: AuthConfig): MiddlewareDefinition;

type AuthConfig = {
type: "bearer" | "api-key" | "basic" | "custom";
validate: (token: string, ctx: ServerContext) => Promise<AuthResult | null>;
headerName?: string; // default varies by type
skipPaths?: string[];
errorMessage?: string; // default: "Authentication required"
extractToken?: (ctx: ServerContext) => string | null; // for "custom" type
skipDevPlayground?: boolean; // default: true (honours the dev playground header only when NODE_ENV is "development")
};

type AuthResult = {
id: string;
email?: string;
roles?: string[];
/**
* true when this identity stands for every holder of one credential (a
* shared API key) rather than one person. Carried to `ctx.user.shared`; the
* agent routes do not warn about a body `userId` for a shared identity. See
* the security guide.
*/
shared?: boolean;
metadata?: Record<string, unknown>;
};

createBearerAuthMiddleware(validate, options?)​

Simplified bearer token authentication.

function createBearerAuthMiddleware(
validate: TokenValidator,
options?: BearerAuthOptions,
): MiddlewareDefinition;

type TokenValidator = (
token: string,
) => Promise<AuthenticatedUser | null> | AuthenticatedUser | null;

type BearerAuthOptions = {
required?: boolean; // default: true
headerName?: string; // default: "authorization"
skipPaths?: string[];
};

createApiKeyAuthMiddleware(store, options?)​

API key authentication using an ApiKeyStore.

function createApiKeyAuthMiddleware(
store: ApiKeyStore,
options?: ApiKeyAuthOptions,
): MiddlewareDefinition;

type ApiKeyAuthOptions = {
headerName?: string; // default: "x-api-key"
skipPaths?: string[];
};

ApiKeyStore​

In-memory API key store.

MethodSignatureDescription
addKey(apiKey: string, user: AuthenticatedUser) => voidRegister a key
validate(apiKey: string) => AuthenticatedUser | nullValidate a key
removeKey(apiKey: string) => booleanRemove a key
clear() => voidRemove all keys
sizenumber (getter)Number of stored keys

createRoleMiddleware(config) / createRoleAuthMiddleware(requiredRoles, options?)​

Role-based access control. Place after authentication middleware.

function createRoleMiddleware(config: {
requiredRoles: string[];
requireAll?: boolean; // default: false (any role matches)
errorMessage?: string;
}): MiddlewareDefinition;

// Simplified version
function createRoleAuthMiddleware(
requiredRoles: string[],
options?: { requireAll?: boolean },
): MiddlewareDefinition;

createPermissionAuthMiddleware(requiredPermissions, options?)​

Permission-based access control.

function createPermissionAuthMiddleware(
requiredPermissions: string[],
options?: { requireAll?: boolean },
): MiddlewareDefinition;

Rate Limiting Middleware​

createRateLimitMiddleware(config)​

Fixed-window rate limiter with configurable store.

function createRateLimitMiddleware(
config: RateLimitMiddlewareConfig,
): MiddlewareDefinition;

type RateLimitMiddlewareConfig = {
maxRequests: number;
windowMs: number;
message?: string;
skipPaths?: string[];
keyGenerator?: (ctx: ServerContext) => string; // default: IP address
onRateLimitExceeded?: (ctx: ServerContext, retryAfter: number) => unknown;
store?: RateLimitStore; // default: InMemoryRateLimitStore
};

Sets response headers: X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, and Retry-After on 429.

createSlidingWindowRateLimitMiddleware(config)​

Sliding-window variant for smoother rate limiting.

function createSlidingWindowRateLimitMiddleware(
config: RateLimitMiddlewareConfig & { subWindows?: number },
): MiddlewareDefinition;

createFixedWindowRateLimitMiddleware(config, store?)​

Fixed-window rate limiter with store as a separate parameter.

function createFixedWindowRateLimitMiddleware(
config: FixedWindowRateLimitConfig,
store?: RateLimitStore,
): MiddlewareDefinition;

InMemoryRateLimitStore​

Default in-memory rate limit store implementing RateLimitStore.

type RateLimitStore = {
get(key: string): Promise<RateLimitEntry | undefined>;
set(key: string, entry: RateLimitEntry): Promise<void>;
increment(key: string, windowMs: number): Promise<RateLimitEntry>;
reset(key: string): Promise<void>;
};

Also exported as MemoryRateLimitStore (alias).


Validation Middleware​

createRequestValidationMiddleware(config)​

Schema-based request validation for body, query, params, and headers.

function createRequestValidationMiddleware(
config: ValidationConfig,
): MiddlewareDefinition;

type ValidationConfig = {
bodySchema?: ValidationSchema;
querySchema?: ValidationSchema;
paramsSchema?: ValidationSchema;
headersSchema?: ValidationSchema;
customValidator?: (ctx: ServerContext) => Promise<void>;
skipPaths?: string[];
errorFormatter?: (errors: ValidationError[]) => unknown;
};

type ValidationSchema = {
required?: string[];
properties?: Record<string, PropertySchema>;
additionalProperties?: boolean;
};

type PropertySchema = {
type: "string" | "number" | "boolean" | "object" | "array";
minimum?: number;
maximum?: number;
minLength?: number;
maxLength?: number;
minItems?: number;
maxItems?: number;
pattern?: string;
enum?: unknown[];
default?: unknown;
validate?: (value: unknown) => boolean | string;
};

Also exported as createValidationMiddleware (alias).

createBodyValidationMiddleware(schema) / createQueryValidationMiddleware(schema)​

Convenience wrappers for body-only or query-only validation.

createFieldValidator(fieldName, rules)​

Returns a (value: unknown) => void function that throws ValidationError on failure.

CommonSchemas​

Pre-built ValidationSchema objects: uuid, email, pagination, sorting, idParam, dateRange, search.

ValidationError (middleware)​

Re-exported from errors.ts. Contains an errors array of { field, message, value? }.


Caching Middleware​

createCacheMiddleware(config)​

Response caching with LRU eviction and per-path TTL support.

function createCacheMiddleware(config: CacheConfig): MiddlewareDefinition;

type CacheConfig = {
ttlMs: number;
maxSize?: number; // default: 1000
keyGenerator?: (ctx: ServerContext) => string;
methods?: string[]; // default: ["GET"]
paths?: string[];
excludePaths?: string[];
store?: CacheStore; // default: InMemoryCacheStore
includeQuery?: boolean; // default: true
ttlByPath?: Record<string, number>;
};

Sets response headers: X-Cache (HIT / MISS), X-Cache-Age, Cache-Control.

createCacheInvalidator(store)​

Returns { invalidate(pattern), clear() } for programmatic cache invalidation.

InMemoryCacheStore​

LRU cache store implementing CacheStore.

type CacheStore = {
get(key: string): Promise<CacheEntry | undefined>;
set(key: string, entry: CacheEntry): Promise<void>;
delete(key: string): Promise<void>;
clear(): Promise<void>;
};

type CacheEntry = {
data: unknown;
createdAt: number;
ttlMs: number;
headers?: Record<string, string>;
};

LRUCache<K, V>​

Generic synchronous LRU cache. Methods: get, set, has, delete, clear, size.

ResponseCacheStore<T>​

Synchronous response cache with TTL. Methods: get, set, has, invalidate, invalidateByPattern, clear, size.


Common Middleware​

FactoryOrderDescription
createTimingMiddleware()0Adds X-Response-Time and Server-Timing headers
createRequestIdMiddleware(options?)0Ensures every request has an X-Request-ID header
createErrorHandlingMiddleware(options?)1Catches errors and formats consistent error responses
createSecurityHeadersMiddleware(options?)2Adds X-Frame-Options, X-Content-Type-Options, HSTS, CSP, etc.
createLoggingMiddleware(options?)3Logs request/response information; skips health endpoints by default
createCompressionMiddleware(options?)5Signals compression preference to adapters

createRequestIdMiddleware options​

{
headerName?: string; // default: "x-request-id"
prefix?: string; // default: "req"
generator?: () => string;
}

createErrorHandlingMiddleware options​

{
includeStack?: boolean; // default: false
onError?: (error: Error, ctx: ServerContext) => unknown;
logErrors?: boolean; // default: true
}

createSecurityHeadersMiddleware options​

{
contentSecurityPolicy?: string;
frameOptions?: "DENY" | "SAMEORIGIN" | false; // default: "DENY"
contentTypeOptions?: "nosniff" | false; // default: "nosniff"
hstsMaxAge?: number | false; // default: 31536000
referrerPolicy?: string | false; // default: "strict-origin-when-cross-origin"
customHeaders?: Record<string, string>;
}

createLoggingMiddleware options​

{
logBody?: boolean; // default: false
logResponse?: boolean; // default: false
logger?: { info, error };
skipPaths?: string[]; // default: ["/health", "/ready", "/metrics"]
}

createCompressionMiddleware options​

{
threshold?: number; // default: 1024 bytes
contentTypes?: string[]; // default: text/*, application/json, etc.
}

Abort Signal Middleware​

createAbortSignalMiddleware(options?)​

Attaches an AbortController to ctx.abortSignal and ctx.abortController for handling client disconnections and request timeouts.

function createAbortSignalMiddleware(
options?: AbortSignalMiddlewareOptions,
): MiddlewareDefinition;

type AbortSignalMiddlewareOptions = {
onAbort?: (ctx: ServerContext) => void;
timeout?: number; // request timeout in ms
};

createExpressAbortMiddleware(options?)​

Express-specific middleware that sets res.locals.abortSignal and res.locals.abortController.

function createExpressAbortMiddleware(
options?: AbortSignalMiddlewareOptions,
): (req, res, next) => void;

Deprecation Middleware​

createDeprecationMiddleware(config)​

Adds RFC 8594 deprecation headers (Deprecation, Sunset, Link, X-Deprecation-Notice) to responses for routes marked as deprecated.

function createDeprecationMiddleware(
config: DeprecationConfig,
): MiddlewareDefinition;

type DeprecationConfig = {
routes: RouteDefinition[];
noticeHeader?: string; // default: "X-Deprecation-Notice"
includeLink?: boolean; // default: true
};

Stream Redaction​

Redaction is disabled by default (opt-in security feature).

redactStreamChunk(chunk, config?)​

Redact sensitive fields from a DataStreamEvent chunk. Returns the chunk unchanged when config.enabled is falsy.

function redactStreamChunk(
chunk: DataStreamEvent,
config?: RedactionConfig,
): DataStreamEvent;

createStreamRedactor(config?)​

Returns a reusable transform function <T>(chunk: T) => T. No-op when redaction is disabled.

function createStreamRedactor(config?: RedactionConfig): <T>(chunk: T) => T;

RedactionConfig​

type RedactionConfig = {
enabled?: boolean; // default: false
additionalFields?: string[];
preserveFields?: string[];
redactToolArgs?: boolean; // default: true (when enabled)
redactToolResults?: boolean; // default: true (when enabled)
placeholder?: string; // default: "[REDACTED]"
};

Default redacted fields: request, args, result, apiKey, token, authorization, credentials, password, secret.


MCP Body Attachment Middleware​

createMCPBodyAttachmentMiddleware()​

Bridges Fastify's body parsing with MCP SDK expectations by attaching request.body to request.raw.body.

function createMCPBodyAttachmentMiddleware(): MiddlewareDefinition;

fastifyMCPBodyHook(request)​

Lower-level Fastify preHandler hook for the same purpose.

function fastifyMCPBodyHook(request: {
raw: { body?: unknown };
body?: unknown;
}): Promise<void>;

Route Groups​

Route group factories return RouteGroup objects. Register them with adapter.registerRouteGroup(group).

createAllRoutes(basePath?, options?)​

Creates all standard route groups in one call.

function createAllRoutes(
basePath?: string, // default: "/api"
options?: CreateRoutesOptions,
): RouteGroup[];

type CreateRoutesOptions = {
enableSwagger?: boolean;
getRoutes?: () => RouteDefinition[];
/** Enables both the Claude and OpenAI-compatible proxy surfaces. */
proxy?: boolean;
/** Legacy per-format flags, still honoured. */
claudeProxy?: boolean;
openaiProxy?: boolean;
/** Attachment handling on /agent/execute and /agent/stream (see below). */
agent?: {
allowedInputRoots?: string[];
maxInputBytes?: number;
maxInputAttachments?: number;
maxInputTotalBytes?: number;
};
/**
* Set to true when the auth middleware identifies individual people: the
* agent routes then take userId and the session only from the authenticated
* user and ignore the request body's, on every adapter and for every request
* (an unauthenticated request gets neither), and the memory routes scope every
* session to that user. A server with per-person authentication must set this:
* at the default (false) the agent routes take userId and sessionId from the
* request body, so any authenticated caller can name another person, and the
* memory routes ignore who is calling. The agent routes have one exception,
* Hono, where a request whose client sent its own X-Request-ID header gets the
* authenticated user over the body, as before, and so does a context the
* built-in adapters did not build (your own adapter, a handler called
* directly). When the server carries middleware state, the routes log one
* warning (per server) the first time a request served from the body names a
* different userId than its ctx.user; with carryMiddlewareState off there is no
* ctx.user to compare and no warning. Leave it false for no auth or one shared
* API key, and mark the shared identity with `shared: true` so that warning stays quiet
* (`neurolink serve` does). Any truthy value turns it on. This is the one
* switch for `registerAllRoutes` / `createAllRoutes`; the `agent` and `memory`
* options do not take it. `registerAllRoutes` also turns on the server's
* `carryMiddlewareState`, which the routes need to see the user on every
* request; a caller that registers the groups itself must set that option on
* the server, or a perUserAuth request has a user only on Hono with the
* client's own X-Request-ID and every other request runs as anonymous. See
* the security guide.
*/
perUserAuth?: boolean;
/**
* Admin roles for the memory routes; only consulted with perUserAuth. Whether
* the routes are scoped is decided by the top-level perUserAuth, not here. See
* "Memory routes and per-user authentication" below.
*/
memory?: { adminRoles?: string[] };
};

registerAllRoutes(adapter, basePath?, options?)​

Registers all route groups with an adapter. If the adapter has listRoutes(), auto-binds it for OpenAPI spec generation.

function registerAllRoutes(
adapter: { registerRouteGroup; listRoutes? },
basePath?: string,
options?: CreateRoutesOptions,
): void;

Individual Route Factories​

FactoryPrefixEndpoints
createAgentRoutes(basePath?, options?)/agentPOST /agent/execute -- Execute agent
POST /agent/stream -- Stream agent response (SSE)
GET /agent/providers -- List available providers
POST /agent/embed -- Generate single embedding
POST /agent/embed-many -- Generate batch embeddings
createToolRoutes(basePath?)/toolsGET /tools -- List all tools
GET /tools/search -- Search tools by query
GET /tools/:name -- Get tool details
POST /tools/:name/execute -- Execute tool
POST /tools/execute -- Execute tool (body-based)
createMCPRoutes(basePath?)/mcpGET /mcp/servers -- List MCP servers
GET /mcp/servers/:name -- Get server status
GET /mcp/servers/:name/tools -- List server tools
POST /mcp/servers/:name/tools/:toolName/execute -- Execute server tool
createMemoryRoutes(basePath?, options?)/memoryGET /memory/sessions -- List sessions
GET /memory/sessions/:id -- Get session details
GET /memory/sessions/:id/messages -- Get session messages
DELETE /memory/sessions/:sessionId -- Delete (clear) a session
DELETE /memory/sessions -- Clear all sessions
GET /memory/stats -- Memory statistics
GET /memory/health -- Memory health check
createHealthRoutes(basePath?)/healthGET /health -- Basic health check
GET /health/live -- Liveness probe
GET /health/ready -- Readiness probe
GET /health/detailed -- Detailed health with service status
createOpenApiRoutes(basePath?, getRoutes?)/docsGET /docs/openapi.json -- OpenAPI spec (JSON)
GET /docs/openapi.yaml -- OpenAPI spec (YAML)

All route factories accept a basePath parameter (default: "/api").

Attachments on /agent/execute and /agent/stream​

input.images and input.files are strings that name content. The SDK resolves them itself, which is right for a program naming its own files and wrong for a network server, so the agent routes check each one before the SDK sees it. An https:// URL or an allowed local path is read by the route and handed to the SDK as bytes; a data: URI already carries its content and passes through unchanged; anything else is refused:

EntryResult
data: URIPassed through unchanged. Its length counts against the size limits below, and over a limit it is 413.
https:// URLDownloaded by the server. A redirect is refused and the size is capped. With no proxy configured the host is resolved, every address is checked against blocked ranges (loopback, private, link-local, cloud metadata) and the connection is pinned to the checked addresses. With HTTPS_PROXY or ALL_PROXY set, the proxy resolves the host: only the URL is checked (IP literals, single-label and internal host names), and what a public-looking name resolves to is the proxy's egress policy. A refused URL is 400.
http:// URLRefused with 400. Use https.
Local pathRead only from a directory in allowedInputRoots, after symlinks are resolved, through a single no-follow file handle. 403 for a path outside every root (the same answer whether or not the file exists, so the route is not an oracle for which files exist) or a file the server cannot read, 404 for a missing file inside a root, 400 for something that is not a regular file, 413 for a file over a size limit.
Any other scheme (file: ...)Refused with 400.

Local paths are off by default: a server does not read its own disk on a caller's say-so unless the operator names where.

A request is bounded as a whole as well as per attachment. input.images and input.files are counted together, and share one byte budget:

OptionDefaultMeaning
allowedInputRootsnoneDirectories local paths may be read from. Every local path is refused without one.
maxInputBytes50 MiBLargest single attachment. If maxInputTotalBytes is set lower, the smaller of the two is the cap.
maxInputAttachments20Most attachments in one request. A longer list is 413 before anything is read or downloaded.
maxInputTotalBytes100 MiBSum of all attachments in one request. A data: URI is charged its string length. Over the budget is 413, and a file or download that would overflow it stops mid-transfer.

A data: URI that is over a limit, and a request with too many attachments, are refused before any other attachment is read. The remaining attachments are read one at a time, each allowed only what the ones before it left of the shared budget. An https:// download that does not fit is refused with 400 like any other URL the server cannot fetch.

registerAllRoutes(adapter, "/api", {
agent: {
allowedInputRoots: ["/srv/uploads"], // default: none, every local path is refused
maxInputBytes: 20 * 1024 * 1024, // per attachment, default 50 MiB
maxInputAttachments: 10, // per request, images and files together, default 20
maxInputTotalBytes: 40 * 1024 * 1024, // per request, one budget, default 100 MiB
},
});

/agent/stream reports a refusal as an error frame in its event stream, as it does for any other error. Calling neurolink.generate() directly is unchanged.

neurolink serve and neurolink server register the routes with these defaults and expose no setting for them, so a CLI-run server refuses every local path and uses the limits above. To change them, build the server with createServer and registerAllRoutes as shown.

Memory routes and per-user authentication​

The memory routes ignore who is calling unless the server opts in with perUserAuth (any truthy value). The opt-in is the same switch the agent routes use (see the security guide); it is off by default because one shared API key (such as neurolink serve with NEUROLINK_SERVER_API_KEY) sets the same ctx.user for every caller and names no person to scope by. A server with per-person authentication must set it: without it the routes do not read ctx.user, so any authenticated caller can name another person with ?userId= on Redis, read any in-memory session by id, and clear every session.

Default (perUserAuth unset): no authentication, or one shared API key. The routes do not read ctx.user. ?userId= chooses the user on Redis (default "default"), the in-memory manager keys sessions by id alone, and GET /memory/stats and DELETE /memory/sessions are open to every caller that passes your authentication, exactly as before scoping existed. A session's owner is not checked.

perUserAuth: true: one credential per person.

RouteAuthenticated userAdmin role (adminRoles, default ["admin"])
Any memory route except GET /memory/health401 (AUTH_REQUIRED) when the request has no user, or the user's id is emptySame
GET /memory/sessions, GET /memory/sessions/:id, .../:id/messagesTheir own sessions. ?userId= may repeat their own id; naming anyone else is 403. Another user's session, named without ?userId=, is 404May name another user with ?userId=
DELETE /memory/sessions/:sessionIdTheir own sessions only: 403 when ?userId= names someone else, 404 for another user's session named without itIn-memory: any session. Redis: the session of the user named with ?userId=; without it the admin's own, so another user's is 404
GET /memory/stats, DELETE /memory/sessions403Allowed

Two malformed inputs are handled in scoped mode only (without perUserAuth they are passed on as they always were). A ?userId= that is not a single string, which Express, Fastify and Koa produce for a repeated ?userId=a&userId=b (and an application that configures a bracket-parsing query parser, for ?userId[]=x), is refused with 400 (VALIDATION_ERROR) on the routes that take ?userId=, for every caller, admins included, rather than read as "no user named", which would answer about a different user than the one asked for. A user whose roles is not an array (for example roles: "admin" from a faulty authentication middleware) has no roles, so it is an ordinary user rather than a 500.

The 401 matters on servers where authentication is optional or absent: without it an anonymous request would carry no identity to scope by and be treated as trusted. GET /memory/health stays open for probes.

registerAllRoutes with perUserAuth also turns on carryMiddlewareState for the server, which is how ctx.user reaches these handlers (see Carrying middleware state). A caller that registers the memory routes itself with createMemoryRoutes("/api", { perUserAuth: true }) must create the server with carryMiddlewareState: true. Without it, the user the authentication middleware sets does not reach the routes, so every request whose context has no ctx.user is answered 401, except GET /memory/health. The routes read ctx.user directly, so a custom adapter or a direct handler call that sets it itself is served as that user either way.

What an admin reaches depends on the memory manager:

  • In-memory manager. Sessions are keyed by id alone, so an admin reads, lists and deletes any session, including one created without a userId (which no ordinary user can see). GET /memory/sessions lists every session, or one user's with ?userId=. A manager that can only report statistics and cannot list sessions shows an ordinary user an empty list with a total of 0 (never the count of everyone's sessions); an admin gets the total.
  • Redis manager. Sessions are keyed per user, so a route can only look a session up once it knows the user. An admin reaches another user's session, listing and delete by naming that user in ?userId=; without it the admin is scoped to their own sessions, and GET /memory/sessions does not list everyone's. The routes do not enumerate users, so there is no "list every user's sessions" call on Redis. A session created without a userId is stored at <keyPrefix>session-only:<sessionId>. session-only is a reserved user id: the routes refuse it with 400 as the authenticated user's id or an admin's ?userId=, so no request reads or deletes such a session by name, and an admin's DELETE /memory/sessions removes it with the rest.

Under perUserAuth the routes refuse a : in any part of a session's key. The Redis manager builds the key as <keyPrefix><userId>:<sessionId> without escaping either part, so two different pairs would share a key when their ids contain :: user a with session b:c and user a:b with session c are both <keyPrefix>a:b:c, and with a namespaced id scheme such as tenant:user the user tenant would reach the session c of user tenant:x by requesting session id x:c. The key scheme is not changed, because stored data depends on it. Instead, every memory route but GET /memory/health answers 400 VALIDATION_ERROR when the authenticated user's id, an admin's ?userId= or the session id contains :, on every manager, so whether a request is accepted does not depend on the backend. A deployment whose user ids contain : cannot use the scoped memory routes, and a session whose id contains : cannot be read or deleted through them. The same routes answer 400 VALIDATION_ERROR when the authenticated user's id or an admin's ?userId= is session-only, the user id a session created without a userId is stored under on Redis, which the Redis manager reserves; do not issue it to a user. Without perUserAuth nothing is refused, as before.

The suite that covers these routes, pnpm run test:memory-route-scope, runs its Redis cases against a throwaway redis-server it starts on a free port. Nothing in CI installs that binary, so on a runner without it the Redis cases report as skipped and only the in-memory half gates; run the suite on a machine with redis-server to cover the Redis behaviour above.

registerAllRoutes(adapter, "/api", {
perUserAuth: true,
memory: { adminRoles: ["admin", "support"] },
});

Proxy Route Factories​

These are only included by createAllRoutes when a proxy flag is set, and they take their own dependencies rather than just a basePath.

FactoryIncluded whenEndpoints
createClaudeProxyRoutes(modelRouter?, basePath?, strategy?, passthrough?, primaryAccountKey?, options?)proxy or claudeProxyPOST /v1/messages — Anthropic Messages passthrough with account pooling
GET /limits — per-account quota (?snapshot=true for stored state)
GET /accounts — one row per account joining status, quota and per-account token totals and cost
createOpenAIProxyRoutes(modelRouter?, basePath?, loopbackPort?, runtimeConfigProvider?)proxy or openaiProxyPOST /v1/chat/completions — OpenAI-compatible surface; Anthropic-targeted models loop back through /v1/messages
createCodexProxyRoutes(basePath?)never — CLI onlyPOST /backend-api/codex/responses — Codex (ChatGPT) pool engine

createCodexProxyRoutes is not wired into createAllRoutes. The CLI proxy assembles it by hand, so an SDK consumer embedding the proxy gets the Claude and OpenAI surfaces but not Codex. Adding a new proxy surface means editing both src/cli/commands/proxy.ts and src/lib/server/routes/index.ts — they are separate hand-maintained lists, which is exactly why Codex is in one and not the other.


OpenAPI Generation​

OpenAPIGenerator​

Class that generates OpenAPI 3.1 specifications from route definitions.

class OpenAPIGenerator {
constructor(config?: OpenAPIGeneratorConfig);
addRoutes(routes: RouteDefinition[]): void;
addRoute(route: RouteDefinition): void;
generate(): OpenAPISpec;
toJSON(pretty?: boolean): string;
toYAML(): string;
}

OpenAPIGeneratorConfig​

type OpenAPIGeneratorConfig = {
info?: { title?; version?; description? };
servers?: Array<{ url; description? }>;
includeSecurity?: boolean; // default: true
basePath?: string; // default: "/api"
additionalTags?: Array<{ name; description }>;
customSchemas?: Record<string, JsonObject>;
routes?: RouteDefinition[];
};

OpenAPISpec​

type OpenAPISpec = {
openapi: "3.1.0";
info: JsonObject;
servers: JsonObject[];
tags: JsonObject[];
paths: Record<string, JsonObject>;
components: {
schemas: Record<string, JsonObject>;
securitySchemes?: Record<string, JsonObject>;
parameters?: Record<string, JsonObject>;
};
security?: JsonObject[];
};

Factory Functions​

FunctionSignatureDescription
createOpenAPIGenerator(config?) => OpenAPIGeneratorCreate generator with defaults
generateOpenAPISpec(routes, config?) => OpenAPISpecOne-shot spec from routes
generateOpenAPIFromConfig(serverConfig, routes?) => OpenAPISpecGenerate from ServerAdapterConfig

Pre-built Schemas​

All schemas are plain JSON Schema objects exported from ./openapi/schemas.ts:

SchemaDescription
ErrorResponseSchemaStandard error response
TokenUsageSchemaToken usage breakdown
AgentInputSchemaAgent input (string or multimodal object)
AgentExecuteRequestSchema (OpenAPI)Agent execute request body
AgentExecuteResponseSchemaAgent execute response
ToolCallSchemaTool call object
ProviderInfoSchemaProvider information
ToolParameterSchemaTool parameter definition
ToolDefinitionSchemaFull tool definition
ToolListResponseSchemaTool list response
ToolExecuteRequestSchema (OpenAPI)Tool execute request body
ToolExecuteResponseSchemaTool execute response
MCPServerToolSchemaMCP server tool
MCPServerStatusSchemaMCP server status
MCPServersListResponseSchemaMCP servers list
ConversationMessageSchemaConversation message
SessionSchemaSession object
SessionsListResponseSchemaSessions list
HealthResponseSchemaHealth check response
ReadyResponseSchemaReadiness check response
MetricsResponseSchemaMetrics response
OpenAPISchemasRegistry object containing all schemas above

Templates​

ExportDescription
createSuccessResponse(schemaRef)Build a 200 response object
createOpenAPIErrorResponse(code, description)Build an error response object
createStreamingResponse(description)Build a streaming (SSE) response
StandardErrorResponsesMap of 400/401/403/404/429/500 responses
createPathParameter(name, description?)Build a path parameter
createQueryParameter(name, schema, description?)Build a query parameter
createHeaderParameter(name, schema, description?)Build a header parameter
CommonParametersPre-built parameters: sessionId, serverName, toolName, limitQuery, offsetQuery, searchQuery
createGetOperation(...)Build a GET operation
createPostOperation(...)Build a POST operation
createStreamingPostOperation(...)Build a streaming POST operation
createDeleteOperation(...)Build a DELETE operation
BearerSecuritySchemeBearer token security scheme object
ApiKeySecuritySchemeAPI key security scheme object
BasicSecuritySchemeHTTP basic security scheme object
StandardTagsDefault tag definitions (agent, tools, mcp, memory, health, streaming)
createOpenAPIServer(url, description)Build a server object
DefaultServersDefault server list
createApiInfo(title, version, description)Build an info object
NeuroLinkApiInfoDefault NeuroLink API info object

Streaming Utilities​

Event Types​

type DataStreamEventType =
| "text-start"
| "text-delta"
| "text-end"
| "tool-call"
| "tool-result"
| "data"
| "error"
| "finish";

type DataStreamEvent = {
type: DataStreamEventType;
id?: string;
timestamp: number;
data: unknown;
};

Specialized event types: TextStartEvent, TextDeltaEvent, TextEndEvent, ToolCallEvent, ToolResultEvent, DataEvent, ErrorEvent, FinishEvent.

createDataStreamWriter(config)​

Creates a DataStreamWriter that writes events in SSE or NDJSON format.

function createDataStreamWriter(
config: DataStreamWriterConfig,
): DataStreamWriter;

type DataStreamWriterConfig = {
write: (chunk: string) => void | Promise<void>;
close?: () => void | Promise<void>;
format?: "sse" | "ndjson"; // default: "sse"
includeTimestamps?: boolean; // default: true
};

DataStreamWriter interface​

MethodSignature
writeTextStart(id: string) => Promise<void>
writeTextDelta(id: string, delta: string) => Promise<void>
writeTextEnd(id: string) => Promise<void>
writeToolCall(toolCall: { id, name, arguments }) => Promise<void>
writeToolResult(toolResult: { id, name, result }) => Promise<void>
writeData(data: unknown) => Promise<void>
writeError(error: { message, code? }) => Promise<void>
close() => Promise<void>

DataStreamResponse​

High-level class that creates a ReadableStream<Uint8Array> with a DataStreamWriter interface.

class DataStreamResponse {
constructor(config?: DataStreamResponseConfig);
readonly stream: ReadableStream<Uint8Array>;
readonly headers: Record<string, string>;
getWriter(): DataStreamWriter;
writeTextStart(id: string): Promise<void>;
writeTextDelta(id: string, delta: string): Promise<void>;
writeTextEnd(id: string): Promise<void>;
writeToolCall(toolCall): Promise<void>;
writeToolResult(toolResult): Promise<void>;
writeData(data: unknown): Promise<void>;
writeError(error: { message; code? }): Promise<void>;
finish(options?: { reason?; usage? }): Promise<void>;
close(): void;
isClosed(): boolean;
}

type DataStreamResponseConfig = {
contentType?: "text/event-stream" | "application/x-ndjson";
headers?: Record<string, string>;
keepAliveInterval?: number;
includeTimestamps?: boolean;
};

createDataStreamResponse(config?)​

Factory function for DataStreamResponse.

Helper Functions​

FunctionSignatureDescription
pipeAsyncIterableToDataStream(iterable, response, options?) => Promise<void>Pipe an async iterable into a DataStreamResponse
createSSEHeaders(additionalHeaders?) => Record<string, string>Standard SSE headers
createNDJSONHeaders(additionalHeaders?) => Record<string, string>Standard NDJSON headers
formatSSEEvent(options: SSEEventOptions) => stringFormat a single SSE message

SSEEventOptions​

type SSEEventOptions = {
event?: string;
data: string;
id?: string;
retry?: number;
};

BaseDataStreamWriter​

Abstract base class providing isClosed(), onClose(handler), and close().

WebStreamWriter​

Concrete class extending BaseDataStreamWriter. Writes SSE events to a ReadableStream<Uint8Array>.

Property/MethodTypeDescription
streamReadableStream<Uint8Array>The readable stream
writeData(data)voidWrite a data event
writeError(message)voidWrite an error event
writeDone()voidWrite a done event
writeEvent(eventType, data)voidWrite a custom event
close()voidClose the stream
isClosed()booleanCheck if closed
onClose(handler)voidRegister a close handler

WebSocket​

WebSocketConnectionManager​

Manages WebSocket connections, ping/pong, and handler dispatch.

class WebSocketConnectionManager {
constructor(config?: WebSocketConfig);
registerHandler(path: string, handler: WebSocketHandler): void;
getHandler(path: string): WebSocketHandler | undefined;
handleConnection(socket, path, user?): Promise<WebSocketConnection>;
handleMessage(connectionId, data, isBinary): Promise<void>;
handleClose(connectionId, code, reason): Promise<void>;
handleError(connectionId, error): Promise<void>;
getConnection(connectionId): WebSocketConnection | undefined;
getAllConnections(): WebSocketConnection[];
getConnectionsByUser(userId): WebSocketConnection[];
getConnectionsByPath(path): WebSocketConnection[];
send(connectionId, data): void;
broadcast(data, filter?): void;
close(connectionId, code?, reason?): Promise<void>;
closeAll(code?, reason?): Promise<void>;
getConnectionCount(): number;
}

WebSocketConfig​

type WebSocketConfig = {
path?: string; // default: "/ws"
maxConnections?: number; // default: 1000
pingInterval?: number; // default: 30000
pongTimeout?: number; // default: 10000
maxMessageSize?: number; // default: 1 MB
auth?: AuthConfig;
};

WebSocketMessageRouter​

Routes JSON messages by type field to registered handlers.

class WebSocketMessageRouter {
route(type: string, handler: (conn, payload) => Promise<unknown>): void;
handle(connection, message: WebSocketMessage): Promise<unknown>;
getRoutes(): string[];
}

Creates a WebSocketHandler with pre-registered routes for generate, stream, and tool_call messages.

function createAgentWebSocketHandler(neurolink: unknown): WebSocketHandler;

Validation Utilities (Zod)​

Zod schemas and helpers exported from ./utils/validation.ts. Used internally by route handlers.

Zod Schemas​

SchemaValidates
AgentExecuteRequestSchemaAgent execute request body
ToolExecuteRequestSchemaTool execute request body
ToolArgumentsSchemaTool arguments (Record<string, unknown>)
SessionIdParamSchema{ sessionId: string }
ServerNameParamSchema{ name: string }
ToolNameParamSchema{ name: string }
ToolSearchQuerySchema{ q?, source?, limit? }

Validation Functions​

FunctionSignatureDescription
validateRequest<T>(schema: ZodSchema<T>, data, requestId?) => ValidationResult<T>Validate request body
validateQuery<T>(schema: ZodSchema<T>, query, requestId?) => ValidationResult<T>Validate query params
validateParams<T>(schema: ZodSchema<T>, params, requestId?) => ValidationResult<T>Validate path params
createErrorResponse(code, message, details?, requestId?, httpStatus?) => ErrorResponseBuild a standardized error response
type ValidationResult<T> =
| { success: true; data: T }
| { success: false; error: ErrorResponse };

type ErrorResponse = {
error: { code: string; message: string; details?: unknown };
metadata?: { timestamp: string; requestId?: string };
httpStatus?: number;
};

Error Classes​

All error classes extend ServerAdapterError, which extends Error. Every error carries code, category, severity, retryable, and optional context fields (retryAfterMs, requestId, path, method, details, cause).

ServerAdapterError provides:

  • toJSON() -- serializes to { error: { code, message, category, requestId, details, retryAfter } }
  • getHttpStatus() -- maps error code to HTTP status

Error Class Table​

ClassHTTP StatusCategoryRetryableDescription
ServerAdapterErrorvariesEXECUTIONnoBase error class
ConfigurationError400CONFIGnoInvalid server configuration
MissingDependencyError500CONFIGnoMissing framework dependency (e.g., express)
RouteConflictError500CONFIGnoDuplicate route registration
RouteNotFoundError404VALIDATIONnoRoute not found
ServerValidationError400VALIDATIONnoRequest validation failed; carries errors[]
AuthenticationError401AUTHENTICATIONnoAuthentication required
InvalidAuthenticationError401AUTHENTICATIONnoInvalid credentials
AuthorizationError403AUTHORIZATIONnoInsufficient permissions
ServerRateLimitError429RATE_LIMITyesRate limit exceeded
HandlerError500EXECUTIONnoRoute handler threw
TimeoutError408EXECUTIONyesOperation timed out
StreamingError500STREAMINGnoStream processing error
StreamAbortedError499STREAMINGnoClient disconnected
WebSocketError500WEBSOCKETyesWebSocket error
WebSocketConnectionError500WEBSOCKETyesWebSocket connection failed
ServerStartError500CONFIGyesServer failed to start
ServerStopError500EXECUTIONnoServer failed to stop
AlreadyRunningError500CONFIGnoServer already running
NotRunningError500CONFIGnoServer not running

Note: ShutdownTimeoutError, DrainTimeoutError, and InvalidLifecycleStateError are used internally by the shutdown lifecycle and are not re-exported from the public index.

wrapError(error, requestId?, path?, method?)​

Wraps any error as a ServerAdapterError. Returns the error as-is if it is already a ServerAdapterError; otherwise wraps it in a HandlerError.

function wrapError(
error: unknown,
requestId?: string,
path?: string,
method?: string,
): ServerAdapterError;

ErrorRecoveryStrategies​

A Record<ErrorCategoryType, { strategy, maxRetries, baseDelayMs }> mapping each error category to a recommended recovery strategy ("retry", "exponentialBackoff", "circuitBreak", or "fail").


Type Exports​

These are type-only exports (no runtime value).

Configuration Types​

TypeDescription
ServerAdapterConfigServer configuration (all optional)
RequiredServerAdapterConfigSame, with defaults applied (all required)
CORSConfigCORS settings
RateLimitConfigRate limit settings
BodyParserConfigBody parser settings
LoggingConfigLogging settings
StreamingConfigStreaming response configuration
RedactionConfigStream redaction settings
ShutdownConfigGraceful shutdown settings

Request/Response Types​

TypeDescription
ServerContextRequest context passed to all handlers and middleware
ServerResponse<T>Generic server response envelope
AgentExecuteRequestAgent execute request body
AgentExecuteResponseAgent execute response
ToolExecuteRequestTool execute request body
ToolExecuteResponseTool execute response
MCPServerStatusResponseMCP server status
HealthResponseHealth check response
ReadyResponseReadiness check response
ErrorResponseStandardized error response
ValidationResult<T>Success/failure discriminated union

Route and Middleware Types​

TypeDescription
HttpMethod"GET" | "POST" | "PUT" | "DELETE" | "PATCH" | "OPTIONS"
RouteDefinitionFull route definition
RouteGroupGroup of routes with prefix and optional middleware
RouteHandler<T>(ctx: ServerContext) => Promise<T | ServerResponse<T> | AsyncIterable<unknown>>
MiddlewareDefinitionMiddleware definition with name, order, handler, paths
MiddlewareHandler(ctx, next) => Promise<unknown>
CreateRoutesOptionsOptions for createAllRoutes

Factory Types​

TypeDescription
ServerFramework"hono" | "express" | "fastify" | "koa"
ServerAdapterFactoryOptions{ framework, neurolink, config? }
ServerStatusServer status snapshot

Streaming Types​

TypeDescription
DataStreamWriterWriter interface for data streams
DataStreamEventTypeEvent type union
DataStreamEventBase event
TextStartEvent / TextDeltaEvent / TextEndEventText streaming events
ToolCallEvent / ToolResultEventTool events
DataEvent / ErrorEvent / FinishEventUtility events
DataStreamWriterConfigWriter factory config
DataStreamResponseConfigResponse factory config
SSEEventOptionsSSE formatting options
SSEWriteOptionsSSE write options

WebSocket Types​

TypeDescription
WebSocketConfigWebSocket server settings
WebSocketConnectionConnection object
WebSocketHandlerEvent handler interface (onOpen, onMessage, onClose, onError)
WebSocketMessageMessage object
WebSocketMessageType"text" | "binary" | "ping" | "pong" | "close"
WebSocketAuthConfigAuth config for WebSocket (same shape as AuthConfig from types)
AuthenticatedUserUser object: id, shared, email, name, roles, permissions, metadata
AuthStrategy"bearer" | "apiKey" | "basic" | "custom" | "none"

Error Types​

TypeDescription
ErrorCategoryTypeError category union
ErrorSeverityTypeError severity union
ServerAdapterErrorCodeTypeError code union
ServerAdapterErrorContextContext object for error construction

Constants​

ErrorCategory​

const ErrorCategory = {
CONFIG,
VALIDATION,
EXECUTION,
EXTERNAL,
RATE_LIMIT,
AUTHENTICATION,
AUTHORIZATION,
STREAMING,
WEBSOCKET,
} as const;

ErrorSeverity​

const ErrorSeverity = {
LOW,
MEDIUM,
HIGH,
CRITICAL,
} as const;

ServerAdapterErrorCode​

const ServerAdapterErrorCode = {
INVALID_CONFIG,
MISSING_DEPENDENCY,
FRAMEWORK_INIT_FAILED,
ROUTE_NOT_FOUND,
ROUTE_CONFLICT,
INVALID_ROUTE,
HANDLER_ERROR,
TIMEOUT,
MIDDLEWARE_ERROR,
RATE_LIMIT_EXCEEDED,
AUTH_REQUIRED,
AUTH_INVALID,
FORBIDDEN,
STREAM_ERROR,
STREAM_ABORTED,
WEBSOCKET_ERROR,
WEBSOCKET_CONNECTION_FAILED,
VALIDATION_ERROR,
SCHEMA_ERROR,
START_FAILED,
STOP_FAILED,
ALREADY_RUNNING,
NOT_RUNNING,
} as const;