Concept: Components, Servers & Lifecycle

Dependency injection gives you a container of bindings. This doc covers the three pieces that turn that container into a runnable application: components package bindings, servers expose them over a transport, and the lifecycle starts and stops everything in order.

Package: @agentback/core.

Component — a bundle of bindings

A Component is the unit of reuse. It's a class that declares bindings to add to the application: controllers, providers, classes, servers, and lifecycle observers. Registering it with app.component(C) merges all of that into the container in one call.

import {Component, Application} from '@agentback/core';

class GreetingComponent implements Component {
  controllers = [GreetingController];
  classes = {'services.Clock': Clock};
  providers = {'services.Token': TokenProvider};
  // servers = {MCPServer};               // contribute a server
  // lifeCycleObservers = [MetricsFlush];  // run code on start/stop
}

const app = new Application();
app.component(GreetingComponent);

This is how the framework ships capabilities. MCPComponent, JWTAuthenticationComponent, the health and metrics extensions — each is a Component you drop in with one line. Your own cross-cutting features (a tenant resolver, an audit log, a set of admin tools) become components too, which is what keeps large apps composable: a feature is a component, not a diff against main().

Architecture diagram — text source below
diagram source (mermaid)
graph TD
  C["app.component(MyComponent)"]
  C --> ctl["controllers[] → tagged controller"]
  C --> cls["classes{} → bound by key"]
  C --> prov["providers{} → bound by key"]
  C --> srv["servers{} → bound under servers.*"]
  C --> obs["lifeCycleObservers[] → start/stop hooks"]
  ctl & cls & prov & srv & obs --> App["the one Application context"]

Server — exposes bindings over a transport

A Server is a LifeCycleObserver with a listening flag. The framework ships two:

Both are just bindings under servers.*. You don't instantiate them directly — you register them (often via a component or a RestApplication) and the application's lifecycle starts them.

import {RestApplication} from '@agentback/rest';
import {MCPComponent} from '@agentback/mcp';

const app = new RestApplication(); // binds servers.RestServer for you
app.component(MCPComponent); // binds servers.MCPServer
app.configure('servers.RestServer').to({port: 3000});
await app.start(); // starts BOTH servers

A server discovers controllers/tools at start time by querying the container for the relevant tag. That's why adding a controller is just app.restController(C) — no router edit, no manual registration. The architecture overview traces this discovery step by step.

Writing your own server

Implement Server (extend the lifecycle), bind it under servers.*, and the app will start/stop it with everything else:

import {Server, Application} from '@agentback/core';

class MetricsServer implements Server {
  private _listening = false;
  get listening() {
    return this._listening;
  }
  async start() {
    /* open a port, begin scraping… */ this._listening = true;
  }
  async stop() {
    this._listening = false;
  }
}
app.server(MetricsServer); // bound under servers.MetricsServer, tagged `server`

Lifecycle — start and stop, in order

Application extends a LifeCycleObserver registry. await app.start() and await app.stop() drive every registered observer (servers included) through three optional hooks:

Hook When Typical use
init() once, before first start one-time setup
start() on app.start() open ports, connect transports
stop() on app.stop() (reverse order) graceful shutdown, flush, close
import {lifeCycleObserver, LifeCycleObserver} from '@agentback/core';

@lifeCycleObserver('metrics')
class MetricsFlusher implements LifeCycleObserver {
  async start() {
    /* begin periodic flush */
  }
  async stop() {
    /* flush remaining + clear timer */
  }
}
app.lifeCycleObserver(MetricsFlusher);

Observers run in groups, and stop runs in reverse start order so dependencies shut down after their dependents. RestServer/MCPServer use these same hooks — they have no privileged lifecycle, they're observers like any other. The HTTP server stops gracefully (drains in-flight requests) on stop().

Putting it together

A typical app is: construct an application, add components, register your controllers/tools/services, configure servers, start.

const app = new RestApplication();
app.component(JWTAuthenticationComponent); // a feature bundle
app.component(MCPComponent); // adds servers.MCPServer
app.restController(GreetingController); // REST surface
app.service(WeatherTools); // MCP surface (an MCP_SERVERS extension)
app.configure('servers.RestServer').to({port: 3000});
await app.start(); // both servers up, controllers + tools discovered
// … later
await app.stop(); // graceful shutdown of everything, reverse order

Next