= Build your apps with AI agents :nav-title: Build with AI agents :description: On Ulabase the backend of your app is a folder of setup files. A coding agent such as Claude Code can write and apply it, reading this documentation over MCP. :keywords: build an app with AI, coding agent, Claude Code, AI-assisted development, backend as code, ulabase setup, agent-native app, MCP documentation :group: Why Ulabase :order: 12 On Ulabase, AI agents have two roles. Your app can have an agent interface, so that AI agents such as Claude use it next to the screens: that is an xref:agent-native-apps.adoc[agent-native app]. And a coding agent, such as Claude Code, can build the app with you. This page is about the second role. The method is the same for an agent-native app and for an app with screens only. An app usually has two parts: the frontend, the screens people use, and the backend, which stores the data and decides who may do what. The backend is usually code, written with Node, Spring Boot, RESTHeart or another framework, in its own repository or in a folder of a monorepo. On Ulabase the backend is already running. What is left is its configuration: collections, schemas, permissions, aggregations, users, features. You can set it up by hand in the console. Or you keep it as a folder of setup files, and xref:cli.adoc[`ulabase setup`] applies it to the service. A coding agent can write that folder for you, because it reads this documentation through its MCP server while it works. What you get: * **A backend you can hand to the agent.** The agent writes the setup files and applies them to a development service. You review the changes and decide what goes to production. * **A backend you can read.** Every setting is a file in git, in the same repository as the screens. * **Every change checked.** A dry run shows what a change will do before it does it, and shows nothing left to do after. == Start in six steps You do not need to read the whole page. These steps are enough to start; the agent follows the method described in the rest of the page. . **Pick a starter.** Clone the starter of your framework, React, Angular, Vue or ecommerce. It has the screens and a first setup file. See xref:getting-started.adoc[Getting started]. . **Create a free service** for development, in the console. . **Install the CLI and sign in**, so the agent can apply the setup: + [source,bash] ---- npm install -g ulabase ulabase login ---- . **Connect your coding agent to the documentation.** You need an agent that writes files and runs commands, such as Claude Code, Cursor or VS Code in agent mode. Add the `ulabase-docs` MCP server. In Claude Code: + [source,bash] ---- claude mcp add --transport http ulabase-docs https://d0c5a1.ulabase.app/mcp ---- + In the other tools, add `https://d0c5a1.ulabase.app/mcp` as an HTTP MCP server in their settings. . **Give the agent its rules.** Paste this in the instructions file of your repository, `CLAUDE.md` or `AGENTS.md`, with the id of your development service: + [source,markdown] ---- ## Backend The backend of this app is a folder of Ulabase setup files: ulabase.setup.ts at the root, the rest in backend/, one folder for each part of the app. `ulabase setup` applies them. - For anything about Ulabase, ask the ulabase-docs MCP server first, before reading code. - Follow the method of the page "Build your apps with AI agents" in ulabase-docs. - Every change: `ulabase setup --srv --dry-run`, then apply, then dry run again; the last dry run must report every step as done. - Run the setup on the development service only. For production, propose the command and wait. ---- . **Ask for what you want**, in plain words. For example: _"Add orders: each user creates orders and reads only their own. Apply it to the development service."_ The agent writes the setup files, applies them, and shows you the last dry run. When the app is ready, create the production service and apply the same folder to it. See <>. == The backend is a folder A starter (see xref:getting-started.adoc[Getting started]) gives you both parts: the screens, and a setup file, `ulabase.setup.ts`, that configures the service for them. As the backend grows, keep it in one folder, split the way the app is split: [source,text] ---- ulabase.setup.ts the root file: every step, in the order they run backend/ access.ts users, teams, guard rules orders/ one folder for each part of the app orders.schema.json steps.ts its collection, indexes, aggregations, permissions reports/ steps.ts emails/ the HTML of the emails the service sends lib/steps.ts the helpers that turn a folder into steps src/ the frontend ---- The root file lists the steps of each folder: [source,typescript] ---- import { defineSetup } from '@ulabase/cli'; import { accessSteps } from './backend/access'; import { ordersSteps } from './backend/orders/steps'; import { reportsSteps } from './backend/reports/steps'; import { retiredPermissionsStep } from './backend/lib/steps'; export default defineSetup('My app', [ ...accessSteps, ...ordersSteps, ...reportsSteps, // delete from the service the permissions you removed from the code retiredPermissionsStep(['oldPermissionId']), ]); ---- A new part of the app is a new folder, with its own schemas, aggregations and permissions. The other folders do not change. == How the agent uses the documentation The `ulabase-docs` server of step 4 has three tools: `search` finds pages by meaning, `chunks` reads a whole page, and `ls` lists the pages. The rules in the instructions file, step 5 above, tell the agent to ask this server first and how to run the setup. Ulabase runs on RESTHeart, which has its own documentation. On some points they differ: on Ulabase, for example, the email templates are part of the service configuration, and on a RESTHeart server they are files. The agent should ask `ulabase-docs` first. == Write every check to compare the content Each step of the setup has a check and an apply. The agent writes a few helpers that turn what a folder declares into steps: one for schemas, one for permissions, one for aggregations. About a hundred lines in all. Make each check compare the content. The check of a permission reads the permission saved on the service and compares it with the one in the code. When you change a permission in git, the next dry run shows it as work to do. [source,typescript] ---- import { step } from '@ulabase/cli'; // one step per permission: done when the saved permission is the same as the one in the code export const permissionStep = (id: string, doc: { roles: string[]; predicate: string; priority: number; mongo?: object }) => step(`permission ${id}`, { async check({ service }) { if (!(await service.permissionExists(id))) return false; const saved = await (await service.fetch(`/acl/${id}`)).json(); return saved.predicate === doc.predicate && saved.priority === doc.priority && JSON.stringify(saved.roles) === JSON.stringify(doc.roles) && JSON.stringify(saved.mongo ?? null) === JSON.stringify(doc.mongo ?? null); }, apply: ({ service }) => service.putPermission(id, doc), }); ---- Two kinds of steps check less, on purpose: * An index is checked only by name, because an index cannot be changed. See xref:managing-indexes.adoc[Indexes]. * Initial data is written once. After that it belongs to the users of the app. Two more rules keep the folders independent: * All the aggregations of a collection are in one list. The helper replaces only the entry with its own `uri`, so two folders can add aggregations to the same collection. See xref:aggregations.adoc[Aggregations]. * A permission you delete from the code stays on the service, and keeps allowing what it allowed. List the deleted ones in one step that removes them from the service. The starters end with a step that points the app at the service. Keep it last. If the setup stops halfway, the app keeps showing the "connect your service" screen, and no page calls a service that is not ready. == Work in short loops Every change to the backend goes through the same loop: . the agent changes a folder: a schema, a permission, an aggregation; . `ulabase setup --srv --dry-run` shows what is missing, step by step; . `ulabase setup --srv ` applies it to the development service; . a second `--dry-run` must report every step as done. The loop takes seconds. The last dry run proves the change: a rule written wrong shows up as a step that is still not done. The same tool reads the service. When the agent needs to see what the service holds, it writes a setup with one step whose check prints what it reads and returns `true`, runs it with `--dry-run`, and deletes it. It needs no other credential or tool. == One folder, two services [[one-folder-two-services]] The same folder configures the free development service and the production service. The setup is TypeScript, so it can read environment variables, for example for the address of the app: [source,bash] ---- ulabase setup --srv APP_URL=https://app.example.com ulabase setup --srv ---- `APP_URL` here is a name of your choice, read by your setup with `process.env`. Secrets, such as the password of an administrator, come in with `fromEnv`, and are needed only on the first run: on later runs the user already exists. == Access is part of the backend Users, roles and rules are in the setup like the rest: * The first administrator is created with `createUser`, with the password from the environment. See xref:managing-users.adoc[Users]. * xref:guards.adoc[Guard] rules block what your app does not offer, such as creating teams. * Permissions can record who wrote a document and when, with `mergeRequest`. See xref:managing-permissions.adoc[Permissions]. * The emails the service sends are HTML files in the folder. See xref:signup-mgmt.adoc#email-templates[The emails]. == When the documentation does not answer Ask the agent to write down exactly what is missing: which key, which shape, how to go back to the default, how long a change takes. Send it to us from the **Support** page of your service. The answer becomes part of this documentation, and the agent reads it over MCP the next time. Keys that the agent guessed never end up on the service. == The method, in short . Start from the starter of your framework. . Keep the backend as a folder of setup files, split like the app. . Give the agent the documentation over MCP, and the rules of the loop in your instructions file. . Make every check compare content, so every change shows up as work to do. . Work in short loops on a free service: change, dry run, apply, dry run. . Apply the same folder to production, with the addresses and the secrets from environment variables. == Related pages * xref:agent-native-apps.adoc[Build your apps for AI agents]: the other role of AI agents, as users of your app. * xref:cli.adoc[The `ulabase` CLI]: commands, the setup file and how to force a step. * xref:getting-started.adoc[Getting started]: create a service and pick a starter.