One container, two surfaces
A hybrid AgentBack app can serve HTTP clients and MCP clients from the same process, backed by the same services and schemas.
The runtime shape is intentionally plain. The application is a dependency-injection context. Servers are bindings in that context. Controllers, tool classes, providers, config, and lifecycle observers are also bindings.
const app = new RestApplication();
app.component(MCPComponent);
app.restController(GreetingController);
app.service(EchoTools);
app.service(GreetingService);
await app.start();
At startup, the REST server finds bindings tagged as REST controllers.
The MCP server finds bindings tagged as MCP servers. Both can inject
the same business service, so the transport adapters stay thin. The
process then serves /openapi.json, Swagger UI, the MCP
inspector, and /llms.txt, all derived from the same
bindings it just discovered.
Discovery beats central registries
Adding a new route or tool should not require editing a router file, an OpenAPI document, and a tool manifest. In this model, adding a feature means adding a tagged class binding. The server that cares about that tag discovers it.
- REST controllers carry route metadata through `@api` and verbs.
- MCP tool classes carry tool metadata through `@mcpServer`.
- Shared services stay transport-agnostic.
- Cross-cutting behavior is installed as middleware or components.
Where the diagram helps
The full architecture map shows the pieces as a runtime system: clients at the edges, a single application context in the center, and contract artifacts emitted from the shared schemas.