| name | agentback |
| description | Build HTTP (REST) and MCP services from one Zod schema set and one DI container with AgentBack — an ESM/Zod/MCP fork of LoopBack 4. Use when building TypeScript/Node.js apps with @agentback/* packages: Zod-first REST controllers (@api, @get/@post/... with path/query/body/response schemas), MCP tool servers (@mcpServer, @tool with input/output schemas), OpenAPI 3.1 emission, a schema-typed HTTP client, authentication strategies / @authorize voters, rate limiting, MCP-over-HTTP auth, the agent runtime, or actors (stateful entities behind a stable address). Triggers on @agentback/core, RestApplication, MCPApplication, @api, @get, @tool, @mcpServer, installMcpHttp, @authenticate, @authorize, @actor, @actorCommand, @actorQuery, ActorRegistry, @injectActor, z.infer, toHostTools, installAgent, running a Vercel AI SDK ToolLoopAgent/HarnessAgent with the app's own @tool classes as host tools (@agentback/agents), rendering an MCP Apps (SEP-1865) ui:// widget for a tool result (@tool({ui})), or building a hybrid REST+MCP app where both ends share the same Zod schemas. Also covers scaffolding a new app with `npm create agentback` / the `create-agentback` CLI (rest | mcp | hybrid templates). |
Build REST + MCP Services with AgentBack
AgentBack is an ESM/Zod/MCP fork of LoopBack 4 — a TypeScript DI
framework (@agentback/core + @agentback/context) with HTTP and MCP
servers that discover your code by tag in one Context. Its bet: a single Zod
schema is the validator, the z.infer type, the OpenAPI contract, the MCP
input/output, and the rendered docs — simultaneously. Change the schema and
every contract changes in one edit; disagreements surface as a TypeScript error
at the decorator, a startup throw, or a failing test.
ESM-only, Node 22.13+, TypeScript 7, pnpm workspaces. Relative imports use
.js extensions. This is a slim modern subset — no LB4 sequences/actions, no
@loopback/repository, no per-parameter @param/@requestBody.
Architecture Decision Tree
- DI container, bindings, components, lifecycle? → Dependency injection &
components (dependency-injection.md).
(Same model as
@loopback/core; this layer is a faithful port.)
- HTTP/REST API with validation + OpenAPI (incl. typed SSE/JSONL streaming
via
streamOf)? → Zod-first REST
(rest-and-openapi.md)
- Tools / resources / prompts for MCP clients (Claude, Cursor, agents),
over stdio or HTTP — incl. MCP Apps
ui:// widgets rendered inline by the
host? → MCP tools (mcp-tools.md)
- Share schemas/types between server and a typed client (no codegen)? →
Schema sharing & client (schema-sharing-and-client.md)
- Authentication, authorization, scopes, rate limiting (REST or MCP/HTTP)?
→ Auth & rate limiting (auth-and-rate-limiting.md)
- Health/metrics, middleware, subclassing the dispatcher, packaging? →
Composition & operations (composition-and-operations.md)
- Stateful entity behind a stable address — one writer at a time (cart,
conversation, counter, room)? → Actors (actors.md)
- Let an agent see/understand the live running app (bindings, schema,
routes, tools) read-only? → Introspection
(introspection.md)
- Add an agent chat dock to the developer console so a coding agent can see
AND evolve the app (source edits + permission prompts)? → Agent console
(agent-console.md)
- Run an AI SDK agent (ToolLoopAgent / HarnessAgent) whose tools ARE the
app's own
@tool classes — per-turn identity, quota, metering? →
Agents (agents.md)
- Turn the app's
@tool classes into a runnable command-line tool
(my-svc forecast --city Tokyo) for a human operator or a shell script? →
Operator CLI (command.md)
Getting Started: scaffold a new app
Don't hand-write package.json/tsconfig.json. Scaffold with the
create-agentback CLI — npm create agentback resolves to it:
npm create agentback my-service
npm create agentback my-service -- --template rest
npm create agentback my-service -- --template mcp
The three templates are hybrid (default), rest, and mcp. The --
matters with npm — npm create passes everything before -- to itself, so
npm create agentback my-service --template rest sends the flag to npm, not the
scaffolder. With pnpm/yarn/bun the -- is unnecessary
(pnpm create agentback my-service --template rest). Short flag -t also works.
The app name must be a valid npm package name; a scoped name like
@acme/my-service creates the directory my-service. The CLI refuses to
overwrite a non-empty directory, and detects your package manager (from npm's
user-agent) to print the right next steps.
Each template lands a complete, runnable workspace — application.ts +
main.ts, a sample controller and/or tool class, a passing vitest test under
src/__tests__/, tsconfig.json, and vitest.config.ts. The
@agentback/* deps are pinned to a caret range. Then:
cd my-service
npm install
npm run build
npm start
npm test
Programmatic use (e.g. from another tool) is available too — import scaffold
from create-agentback and pass {name, template?, cwd?, version?}.
Quick Start: a hybrid REST + MCP app from shared schemas
import {z} from 'zod';
import {api, get, post} from '@agentback/openapi';
import {RestApplication} from '@agentback/rest';
import {mcpServer, tool, MCPComponent} from '@agentback/mcp';
import {installMcpHttp} from '@agentback/mcp-http';
import {isMain} from '@agentback/core';
const AddIn = z.object({a: z.number().int(), b: z.number().int()});
const AddOut = z.object({sum: z.number().int()});
const HelloPath = z.object({name: z.string().min(1).max(64)});
const Greeting = z.object({greeting: z.string()});
@api({basePath: '/greet'})
class GreetingController {
@get('/hello/{name}', {path: HelloPath, response: Greeting})
async hello(input: {path: z.infer<typeof HelloPath>}) {
return {greeting: `Hello, ${input.path.name}!`};
}
@post('/add', {body: AddIn, response: AddOut})
async add(input: {body: z.infer<typeof AddIn>}) {
return {sum: input.body.a + input.body.b};
}
}
@mcpServer()
class MathTools {
@tool('add', {input: AddIn, output: AddOut})
async add(input: z.infer<typeof AddIn>) {
return {sum: input.a + input.b};
}
}
async function main() {
const app = new RestApplication({});
app.restController(GreetingController);
app.component(MCPComponent);
app.service(MathTools);
await installMcpHttp(app);
await app.start();
}
if (isMain(import.meta)) await main();
Core Concepts Summary
| Concept | Key APIs | Notes |
|---|
| DI container | Context, BindingKey.create<T>(), @inject, @injectable | Ported from @loopback/core; import from @agentback/core |
| App + servers | RestApplication/ExpressRestApplication, EdgeRestApplication, MCPApplication, Application, Server | RestApplication for the Node/Express HTTP host; EdgeRestApplication for fetch/edge (Workers/Bun/Deno, no express install); MCPApplication for a stdio MCP server. Servers discover bindings by tag at start() |
| Components | Component with components[] / services[] / bindings[] | Composable packaging; app.component(X) |
| Plugins | @agentback/plugin: loadPlugins(app) (declarative), loadPlugin(app, specifier) (imperative) | Mount Component-contributing packages with fail-closed DI-key collision governance; agentback:{plugin,component} marker |
| REST routing | @api, @get/@post/@put/@patch/@del, {path,query,body,headers,response} | Zod on the decorator; slot 0 = validated input bundle |
| MCP tools | @mcpServer, @tool('name', {input, output, scope?}), @resource, @prompt | Zod on the decorator; stdio + HTTP transport |
| OpenAPI | emitted from Zod via z.toJSONSchema({target:'draft-2020-12'}) | /openapi.json, Swagger at /explorer |
| Schema-typed client | @agentback/client (defineRoute, routeGroup, safeCall) | Browser-safe; shares the SAME Zod schemas; no codegen |
| Auth | @authenticate('jwt'|'api-key'|...), @authorize({...}), voters | Strategies + voter pipeline; client-app scope governance |
| Rate limiting | installRateLimit(app), per-tool limits for MCP-over-HTTP | rate-limiter-flexible; in-memory or Redis |
| Operations | app.middleware(), installHealth, installMetrics, CORS | Subclass RestServer.dispatch/sendResult/sendError for deep changes |
| Actors | @actor/@actorCommand/@actorQuery, ACTOR_REGISTRY, @injectActor, InMemoryActorsComponent | Stateful entity at {type,id}: serialized turns, idempotent requestId, lease-free queries, optional event log; @agentback/actors(-redis) |
| Agent runtime | @agentback/agent-* + agent-messaging | LLM agent stack on the DI substrate: engine + triggers, turn loop, tools, durable jobs |
Key Rules
- Import from
@agentback/core (it re-exports context, which
re-exports metadata). Relative imports use .js extensions (ESM).
- Tests run against built
dist/ — pnpm build before pnpm test.
- Zod schemas go on the decorator; never use
@param/@requestBody/@response
(removed). Derive the handler input via z.infer.
- REST slot 0 = the validated input bundle when any schema is declared:
{body, path, query, headers} (only declared keys). With no schema, slot 0 is
unconstrained. @inject goes at slot 1+.
- MCP slot 0 =
z.infer<typeof input> when input is declared; @inject
at slot 1+. @tool('ping') with no input is valid.
response:/output: constrain the return type and are validated at
runtime (logged on mismatch for REST, thrown for MCP). status: overrides 200;
204 sends an empty body.
- URL placeholders must match the
path: schema keys (checked at
app.start()). Header schemas use lowercase keys.
- Discovery is by tag, not a router file.
@api tags a controller,
@mcpServer tags a tool class; servers findByTag at start. "Add a feature"
= "add a binding."
@mcpServer() is built on @injectable — it makes the class an extension
of the MCP_SERVERS extension point (extensionFor: MCP_SERVERS, singleton by
default); app.service() / app.controller() read that metadata and tag the
binding automatically. Never call .tag() manually for these.
- Register MCP tool classes with
app.service() — a tool is a service. The
server discovers it as an MCP_SERVERS extension and resolves it through its
binding, so constructor @inject works regardless of registration (service,
controller, or a manual bind().apply(extensionFor(MCP_SERVERS))). A dual
REST+MCP class (@api + @mcpServer) needs both restController (the
routes) and service (the MCP extension). See
mcp-tools.md.
- The REST core is host-portable.
RestServer.fetchHandler() is a
runtime-neutral Request → Response handler. Pick the host by class:
RestApplication / ExpressRestApplication (Node/Express host, express on
^5) or EdgeRestApplication (fetch/edge: Workers/Bun/Deno — pinned to
listener: 'native', installs no express/cors). The neutral
middleware machinery lives in @agentback/middleware; express/cors/multer
are optional peer deps of @agentback/rest. Deploy via agentback deploy vercel|cloudflare (@agentback/cli). The schema-typed client depends on
nothing but zod (browser-safe).
- Every source file carries the three-line MIT header
(
// Copyright NineMind, Inc. 2026. All Rights Reserved.).
References
- Dependency injection & components:
references/dependency-injection.md —
Context/Binding,
@inject, providers, scopes, components, servers, lifecycle
observers, tag-based discovery.
- Zod-first REST:
references/rest-and-openapi.md —
@api +
verb decorators, the input bundle, response/status, streaming (streamOf,
SSE/JSONL), OpenAPI emission, Swagger, CORS/middleware, subclassing dispatch.
- MCP tools: references/mcp-tools.md —
@mcpServer/@tool/@resource/@prompt, dispatch, confirm: gating, MCP
Apps widgets (ui:), stdio vs HTTP transport, scope-gated tools, the
inspector.
- Schema sharing & client:
references/schema-sharing-and-client.md
— one schema for both ends,
defineRoute/routeGroup/safeCall, no codegen.
- Auth & rate limiting:
references/auth-and-rate-limiting.md —
strategies (jwt/api-key/client-credentials/anonymous),
@authorize voters +
presets, client-application scope governance, REST + MCP-over-HTTP auth, rate
limiting.
- Composition & operations:
references/composition-and-operations.md
— components, middleware/interceptors, health/metrics, lifecycle, packaging a
new workspace package.
- Actors: references/actors.md —
@actor services,
commands + lease-free @actorQuery, the typed ref(Class, id) proxy /
@injectActor, per-identity serialization + requestId idempotency, the event
log, and the in-memory / event-sourced / Redis runtimes.
- Introspection: references/introspection.md —
@agentback/introspection, the read-only MCP server that exposes the live app
(bindings/schema/routes/tools + OKF) to any agent via inventory/get/
get_okf_bundle.