App
The workspace that owns pages, components, stores, and theme.
An App is the unit you build against on the hosted MCP (https://mcp.codelab.app/mcp). It is identity, published URL, and the parent of every page, component, store, and theme token in the project.
Nothing on the canvas exists outside that app. Element trees hang off pages and components the app owns; stores, hooks, schemas, env vars, and theme rules are app-scoped too.
App
├── Pages routed element trees
├── Components reusable element trees + fields
├── Stores runtime state (app-wide or attached to a page/component)
└── Theme tokens + kind stylesWhat it is
The app's metadata is id, name, handle, optional customSubdomain / customDomain, description, and SEO defaults (defaultOgImage, faviconUrl, siteTitleSuffix). Pages belong to the app but are listed separately (list_pages) — they are not embedded on the app record.
packages/modules/app/core/src/domain/app.model.types.ts
Every app has a handle (<handle>.codelab.app). That is the guaranteed URL. An admin-granted customSubdomain overrides the handle on the same apex; a customDomain (your host) wins over both. TLS for a custom domain shows as customDomainStatus: "pending" while the cert issues, "active" once live, null if none.
packages/modules/app/core/src/domain/published-url.ts
create_app does not leave an empty shell. It persists the app, a default Home page (isHome: true, routePattern: ""), a chat, variant presets, and heading kind styles.
packages/modules/app/server/src/services/create-app.service.ts
An app must keep at least one page. Home is the page flagged isHome, else the first page.
packages/modules/app/core/src/domain/app.model.ts
How it relates
| Child | Role |
|---|---|
| Page | A routed tree visitors hit. Shared chrome lives on a layout page, not copied onto every leaf. |
| Component | A reusable tree. Place one on a page as an element whose kind is { _tag: "Component", value: componentId }. |
| Element | A node inside a page or component. The app never owns an element directly. |
| Store | Named runtime state. container: null is app-wide; { _tag: "Page" | "Component", id } scopes it to that tree (and the name can be a plain state per owner). Store actions[] are Action chains. |
| Theme | App-level tokens (theme_tokens) and kind styles (style_rules). Brand here before per-element style_*. |
packages/modules/store/core/src/domain/store.model.ts
packages/modules/mcp/server/src/theme-token.mcp.ts
Hooks, reusable schemas, and env vars are also app-scoped (packages/modules/mcp/server/src/hook.mcp.ts, schema.mcp.ts). Hook methods bind as "<hookId>.<method>" actions; field shapes are the type system.
MCP tools
Connect to https://mcp.codelab.app/mcp. App CRUD addresses the app by id (you can only touch your own). Everything else that lists or mutates inside an app needs a conversation context.
packages/modules/mcp/server/src/app.mcp.ts
packages/modules/mcp/server/src/context.mcp.ts
| Tool | What it does |
|---|---|
list_apps | Discover appId, name, subdomain, domain, customDomainStatus. Call this before set_context. |
set_context | Activate an app. Omit contextId on the first call; keep the returned contextId and pass it to every app-scoped tool. Send the same id when switching apps. |
get_context | Verify the active app. |
clear_context | Drop this conversation's handle. |
create_app | { name, description? } — scaffolds Home + chat + presets. Then set_context with the new appId. |
update_app | Partial metadata (name, description, customDomain, SEO defaults). Omitted fields stay. customSubdomain is admin-only. |
delete_app | Cascades pages, elements, stores, … Irreversible. If it was the active context, set_context again. |
App-scoped follow-ups (all take contextId): list_pages, list_components, list_stores, list_hooks, list_schemas, list_env_vars, list_theme_tokens, list_theme_styles.
Element tools take a container: { _tag: "Page" \| "Component", id } instead of appId. Ownership is checked from that id, not from context — but you still need context to find those ids.
packages/modules/mcp/server/src/mcp-ownership.service.ts
Typical first calls:
list_apps
set_context { appId }
get_context { contextId } # confirm
list_pages { contextId } # Home is already thereGotchas
- No context, no canvas.
list_pages,create_store,add_element(once you need a page id), and the rest fail untilset_context. RetaincontextId; do not expect a hidden session app. (packages/modules/mcp/server/src/context.mcp.ts) create_appalready made Home. Do notcreate_pagefor the landing page.list_pagesfirst. (packages/modules/app/adapter/src/ai/create-app.tool.ts)- Theme before elements. Tokens (
list_theme_tokens/update_theme_tokens) restyle every Button and Card. Kind styles (list_theme_styles/update_theme_style) cover extras CVA does not set. Per-elementstyle_*is last. Token keys are fixed — you change values, not names. (packages/modules/mcp/server/src/theme-token.mcp.ts) previewDatais builder-only. Sample{{session.*}}for the canvas. The published site never reads it. (packages/modules/app/core/src/domain/app.model.types.ts)delete_appis total. Every page, element, and store goes with it. (packages/modules/app/adapter/src/ai/delete-app.tool.ts)