Builder
Status: prototype with per-user sign-in. Each person signs in with Strands Accounts, their AI client signs in as them, and both only ever reach that person's own sites. See WEB_BUILDER_PLAN.md in the repository for the roadmap.
Run it locally
Open http://localhost:9020, the builder's public homepage, and go to your sites (/sites). Create a site, and its workspace shows the MCP URL, the pages, the history and the live preview. Previews live on their own origin per site, http://<site-id>.preview.localhost:9020; browsers resolve *.localhost to your machine, so this needs no setup. Previews are never cached, and links to files in public/ carry a content version (/site.css?v=…), so an edited stylesheet or script shows up on the next reload even behind a caching proxy.
Without TIBI_BUILDER_AUTH_REQUIRED, the local builder has one user and /mcp needs no sign-in, so connecting Claude Code is just:
Configuration
TIBI_BUILDER_DATA: where sites are stored. Defaults todatanext tobuilder/.tibi-config.TIBI_BUILDER_PUBLIC_URL: the builder's public address. The MCP URL is this plus/mcp, and the sign-in callback is this plus/__tibi/auth/callback. Defaults to the URL Coolify assigns the service (SERVICE_URL_TIBI_BUILDER), thenhttp://localhost:<port>. It must be the address people actually use: the builder only accepts changes (anything but reads) from pages on this origin, so a site can't act as whoever is signed in.TIBI_BUILDER_PREVIEW_ORIGIN: where previews live. By default each site previews at<site-id>-tibi-preview.<parent domain of the public URL>, sohttps://builder.lab.example.comgiveshttps://<site-id>-tibi-preview.lab.example.com, one level under the same parent (covered by its wildcard DNS and certificate). Locally it ishttp://<site-id>.preview.localhost:<port>. To override, give a host each site id is put in front of (https://preview.example.com) or a pattern with*for the site id (https://*-preview.example.com).TIBI_BUILDER_MAX_SITES: the most sites one user may own. Defaults to 25.TIBI_BUILDER_TETHER_URL: which Tether site backends live on. Defaults tohttps://tether.strands.gg; point it at Tether staging to test.TIBI_BUILDER_TETHER_TOKEN: the builder's own Tether account token (tthr_at_…), minted by someone signed in to that account on the same Tether. Without it, sites are front-end only.TIBI_BUILDER_AUTH_REQUIRED=1: Strands Accounts sign-in for the builder's pages and API, and per-user sign-in for AI clients (see Auth for theTIBI_STRANDS_OAUTH_*variables). Without it, everything acts as one local user. Previews are public by site id either way.
Behind a reverse proxy, keep response compression off for text/event-stream: previews reload over SSE, and a compressing proxy holds the stream back.
Deploy
The image is built from builder/Dockerfile (from the repository root) and listens on port 9020. Sites are stored in /data; mount a volume there and back it up. The repository's docker-compose.yml has a tibi-builder service with these Coolify variables:
- No URLs: leave
TIBI_BUILDER_PUBLIC_URL,TIBI_BUILDER_PREVIEW_ORIGINandTIBI_BUILDER_OAUTH_REDIRECT_URIempty, and the builder derives all three from the domain Coolify assigns it, so one config works in every environment. - No MCP token: AI clients sign in per user (see "Connecting an AI client" below).
TIBI_BUILDER_OAUTH_CLIENT_ID,TIBI_BUILDER_OAUTH_CLIENT_SECRETandTIBI_BUILDER_OAUTH_SIGNING_KEY: a Strands Accounts client, either the builder's own or the docs site's with the builder's callback (<public url>/__tibi/auth/callback) added to its redirect URIs. Sign-in is on by default (TIBI_BUILDER_AUTH_REQUIRED=1), and the container won't start without the client id and signing key.- Who can sign in: set the Strands client to approval only, or list Strands user ids or usernames in
TIBI_BUILDER_OAUTH_ALLOW_USERS. In this prototype every signed-in user sees every site, so keep it to the people you trust.
In Coolify, add only the builder's own host as the service's domain. Preview hosts reach it through a router label in docker-compose.yml matching preview hosts by pattern (Coolify's domain field can't express a wildcard). One matches any host whose first label ends in -tibi-preview, on any domain, which suits the derived preview origin. The other matches one label under tibi.page, for production's TIBI_BUILDER_PREVIEW_ORIGIN=https://tibi.page. Both patterns are written out in docker-compose.yml, because Coolify doesn't substitute variables inside labels; to serve sites from another domain, change the second pattern there. The proxies in front still need to send the preview hosts to Coolify (for example a *.lab.example.com DNS record and proxy host).
Connecting an AI client
Everyone uses the same MCP URL, <public url>/mcp, shown on the builder's Connect page. The builder is an OAuth 2.1 server for it, following the MCP authorisation spec, so clients set themselves up:
- The client calls
/mcpand gets401with aWWW-Authenticateheader pointing at/.well-known/oauth-protected-resource, which names the builder as the authorisation server (/.well-known/oauth-authorization-server). - The client registers itself (
/oauth/register) and sends you to/oauth/authorize. That page is behind Strands Accounts sign-in, so the Strands client's approval list decides who can connect. - You approve the client on a consent page that names it, says what it will be able to do, and where you'll be sent back to.
- The client exchanges the code for tokens with PKCE (S256). Access tokens last an hour; refresh tokens rotate on every use, and using an old one again disconnects that client.
In claude.ai and the Claude desktop app, add the URL as a custom connector and approve the sign-in window. In Claude Code, run claude mcp add --transport http tibi-builder <mcp url>, then /mcp to sign in. For a client that can't sign in, create a personal token on the Connect page and send it as Authorization: Bearer <token>.
Two scopes: sites:read (see sites, files, builds, history) and sites:write (create sites, change files, restore). A connection with read access only never sees the write tools. The Connect page lists every connected client and personal token, and disconnects or revokes either. Tokens and codes are stored only as SHA-256 hashes.
Every site has an owner, and every tool and API call works on the caller's own sites only; anyone else's site reads as "no such project". The history records who made each change and through what, such as "ada via Claude". Sites made before owners existed go to the first person who signs in.
Site backends (Tether)
A site can have its own backend on Tether: a database and server functions that its pages call through Tibi.tether. It is optional, per user and per site:
- Connect Tether on the Connect page. The builder creates a Tether organisation for that user (
Tibi-Builder-<user id>, owned by the builder's Tether account) and adds them to it as a viewer, so they can look at their projects in Tether's dashboard. Without connecting, a user's sites are front-end only. - Every site gets a Tether project. Once a user has connected Tether, each of their sites has its own Tether project, with development and production environments and its preview allowed as an origin. New sites get one when they are created, and connecting Tether creates them for existing sites, so the backend is ready even if the site never uses it.
- Start the backend (the AI's
builder_enable_backend, or the workspace's "Add a backend" if a site has no project yet). This adds starter files intether/, and creates the project if it's missing. - The backend is files in the site.
tether/schema.tsis the database; every other.tsfile undertether/holds functions, called as<path, dotted>.<export>(tether/posts.tsgivesposts.list); files undertether/_lib/or starting with_are helpers. They share history and restore with the pages. - Deploying (
builder_deploy_backend) sendstether/to the site's development environment, which its preview uses. Everything is checked first, with errors per file and line; a change that would delete a table or column is refused unless the AI explicitly allows it, which it should only do with the user's agreement. Functions can't import npm packages. - The preview is wired to the site's project automatically: the page's
Tibi.tether.useQuery('posts.list', …)just works.
The builder keeps each project's keys outside the site's files (the server keys in a file only the builder reads), so they never reach snapshots, previews or the AI.
New sites and themes
The homepage at / is public; everything else (the sites list at /sites, the workspaces, the Connect page and the API) needs sign-in. A new site starts from a theme chosen when it is created, in the sites list or through the AI's builder_create_project (its optional theme argument, listed at GET /api/themes): clean (the default), warm, midnight or bold.
Every theme starts with the same files, ready to use as a first version of the site:
pages/_layout.tibi: the page title, favicon and stylesheet links in its<head>block, a sticky header with the nav, and the footer.pages/index.tibi,pages/about.tibiandpages/contact.tibi: home, about and contact pages with placeholder copy.public/site.css: the global stylesheet. The theme's design tokens (colours, fonts, corners, shadows) sit on:rootat the top, followed by shared classes such as.section,.cardand.button. Changing the whole look is an edit to this one file.public/favicon.svg: the site's initial on the theme's accent colour.
What the AI can do
The MCP endpoint exposes these tools:
builder_list_projectsandbuilder_create_project: find or create a site.builder_list_filesandbuilder_read_file: read a site's files and its page URLs.builder_write_file,builder_apply_editsandbuilder_delete_file: change files.builder_apply_editschanges several files as one step, and for small changes takesreplace(find/with pairs, each matching exactly once) so the AI doesn't resend the whole file.builder_build: compile every page and report problems, including components that fail inside a page.builder_preview_url: the live preview link for a page.builder_historyandbuilder_restore: every change is a numbered snapshot, and any snapshot can be restored. A restore is itself a snapshot, so it can be undone too.tether_guide,builder_enable_backend,builder_deploy_backend,builder_backend_info,builder_read_tableandbuilder_call_function: the site's backend (see above).tibi_file_formatandtibi_validate: the same.tibiauthoring reference and checker as the Tibi MCP server.
The MCP handshake also sends instructions: how a site is laid out, where the global CSS lives, to prefer small replace edits, and to open the site's preview in the client's built-in browser when it has one (otherwise to give the user the link). In the Claude desktop app that is Claude's browser pane, whose preview_start tool is loaded on demand, so the builder names it and tells the AI to search for it; builder_create_project and builder_preview_url repeat this with the URL.
Tool failures come back as tool results marked as errors, with the reason, so the AI can correct itself.
Rules for builder sites
- No frontmatter. Frontmatter runs code on the machine compiling the site, so the builder refuses it, both when a file is written and when a page is rendered (see Frontmatter). Behaviour goes in
<script tibi>, which runs in the browser. - Checked writes. A
.tibifile must parse and pass the authoring checks before it is saved. In a batch, one rejected file means nothing in the batch is saved. - File types.
.tibi,.css,.js,.ts(fortether/),.json,.md,.txt,.svg, and web images and fonts. Paths are relative to the site, with no..and no hidden files apart from.tibi-config. - Limits. 1 MiB per file, 500 files, and 25 MiB per site, and 25 sites per user (
TIBI_BUILDER_MAX_SITES). - CSS. Every site keeps a CSS reset at the top of
public/site.css. Layouts respond with container queries:.containerand.cardare query containers (container-type: inline-size), and the AI is told to prefer@containerrules, keeping@mediafor viewport and device features such asprefers-color-scheme. - Layout. The usual Tibi project layout: pages under
pages/, components undercomponents/, static files underpublic/(served from the site root).
How it is built
The builder is itself a Tibi app (builder/ in the repository). Sites are stored with tibi-project-store, compiled straight from the store with FrontmatterPolicy::Deny, and routed with tibi_router::scan_page_paths, which applies the normal page rules to a list of stored paths. The MCP endpoint is the tibi_mcp library with the builder's own tools (see MCP, "Embed it in your own service"). See Architecture for the compiler side.