One tRPC router, three ways to call it: tRPC, REST with OpenAPI, and MCP for AI agents.
The application API is tRPC, defined in src/server/api/routers/ and registered in the root router. Procedures come in three flavors:
publicProcedure: no session requiredprotectedProcedure: requires an authenticated sessionadminProcedure: requires the admin roleContext carries the enriched session (with role and onboarding state always fresh from the database). Client components call procedures through typed hooks via the tRPC provider; route loaders can call them server-side.
Adding an endpoint:
export const widgetRouter = router({
list: protectedProcedure.query(({ ctx }) =>
db.widget.findMany({ where: { userId: ctx.session.user.id } }),
),
});Register it in src/server/api/root.ts and the client types update immediately.
With ENABLE_REST_API=true, the same router serves REST:
/api/rest/*: REST endpoints generated from procedures with openapi metadata/api/openapi.json: the OpenAPI spec/api/docs: interactive API referenceExpose a procedure by adding .meta({ openapi: { method: 'GET', path: '/users/me' } }) plus explicit zod .input() and .output() schemas (the generator requires both). Auth uses bearer tokens.
With ENABLE_MCP=true, /api/mcp serves a Model Context Protocol endpoint so AI agents can call your backend with real authentication. The baseline ships a curated tool set: profile lookup plus admin-gated flag and user-count tools, in src/server/mcp/server.ts. Tools are an explicit allowlist by design; add tools deliberately rather than exposing the whole router.
/api/health returns 200 with a live database ping, or 503 when the database is unreachable. Wire it into your deployment platform's health checks.