Live apps over MCP
live_apps is the largest area in the connection: sixteen actions that take an app from "what
data do I even have?" to a published dashboard that refreshes itself. This page is the contract —
the phases, the actions in each one, and the gates that stand between them.
For what a live app is and how you use one day to day, start at Live Apps.
Your assistant builds it. Nirvai inspects it.
There are two ways to get a live app. Inside Nirvai, the built-in builder does the work for you. Over the connection, your assistant is the builder — it writes the interface, the components, the data scripts and the manifest that ties them together, and pushes the finished thing.
Think of a contractor and a building inspector. The contractor does the real work; the inspector never picks up a tool, but holds that work to exactly the same code as the in-house crew, and nothing gets signed off that doesn't pass. There is no "build" action here, because there is nothing to delegate.
Data before theme, theme before build, validation before staging — so a look is picked for data
already seen, and you approve an app that has already rendered. The order is enforced, not advisory:
test_app refuses an app whose sections have no data, and stage refuses one whose last validation
failed.
The sixteen actions, in build order
| Phase | Action | Scope | What it does |
|---|---|---|---|
| Discover | live_apps.list_custom_tools(search) | read | Your connected tools, each with the id a script uses to reach it. Always searched — a real account has hundreds. |
live_apps.list_nirvai_tools() | read | Everything already inside Nirvai: your databases, your memory stores, and Nirvai's own platform data. | |
live_apps.get_database_info(database_uuids) | read | The real columns and types of the databases this app will read, and how to filter them. | |
live_apps.get_memory_info(memory_uuids) | read | What a memory store holds, and the read-only ways a script may query it. | |
live_apps.propose(name, sources, …) | run | Opens a review page listing exactly which sources the app will read. You accept, or send changes back. | |
| Data | live_apps.test_script(code, sources, app, name) | run | Runs one section's script against your live sources and keeps the result as that section's snapshot. One call per section. |
live_apps.get_snapshot(app, script?) | read | Reads those snapshots back as shapes — how many rows, which fields, when it ran. | |
| Theme | live_apps.list_brand_logos(query?) | read | Real brand marks for the services the app covers, so it's branded rather than hand-drawn. |
live_apps.theme_propose(directions, …) | run | Opens a look board with one to three complete designs. You pick one. | |
| Validate | live_apps.test_app(app) | run | The gate battery: structure, then a real headless render across full, empty and error states, at desktop and mobile. Loop until it passes. |
| Stage | live_apps.stage(app) | run | Turns the validated app into a working preview with a Save button. Refuses unless the last test_app succeeded. |
| Save | live_apps.publish(title, app, …) | write | Finalises it into a permanent app. Given an existing app's id, it edits that app in place. |
| Any time | live_apps.list(filter?) | read | Your apps, as slim cards. |
live_apps.get(app) | read | One app's full contract — status, theme, its sections and the sources each declares. | |
live_apps.load(uuid, app?) | read | Copies a published app back into the working session so it can be edited and republished. | |
live_apps.list_data_sources(query?) | read | Deprecated. Superseded by the four discovery actions above; kept only so older work keeps running. |
Everything from propose onwards belongs to one session, threaded all the way
through, so the app accumulates in a single place.
Where you finish this
Two of those actions hand a page back to you, and they are not the same kind of page. Both live inside Nirvai, need you signed in, and are visible to nobody else.
Picking a look. live_apps.theme_propose opens /external/live-app-theme/… — a board of one to
three complete look directions. It is read-only: picking creates nothing, it just tells your
assistant which way to build.
Saving the app. Once live_apps.test_app passes, live_apps.stage opens /external/live-app/…
— the functional one. It is the real, working app with a sticky Save button, plus a Tweak mode
for pinning comments on specific elements. Saving is publishing.
- Nothing reaches you until it renders.
stagerefuses an app whose lasttest_appfailed. - To change something, copy the block the page gives you into your chat — neither page can message your assistant back. See the MCP reference.
live_apps.publishis the direct alternative, used when you've already told your assistant to finish it — no page in between.
The four kinds of data
An app can be built on any mix of these, each declared per section:
| Source | Declared as | What it gives the app |
|---|---|---|
| Your connected tools | tool_ids | Anything you've connected — a store, an ad account, a CRM. |
| Your Nirvai databases | database_uuids | Your own tables, addressed by id. |
| Your memory stores | memory_uuids | The long-term facts your agents keep — read-only. |
| Nirvai's own data | nirvai_tools | Your agents, automations, credit usage and activity. Nothing to connect. |
That declaration is the exact list the section may touch at run time; reaching for anything else fails loudly, naming what it was allowed.
Through the connection, live-app scripts are forced read-only: a section can read your records, never change them. A live app is a lens, not a control panel — see what your apps can read for where that line sits.
Worked examples
Discover what exists, then propose the source set.
nirvai_execute(
alias = "live_apps.list_nirvai_tools",
args = { },
description = "See which databases and memory stores this app could read"
)
nirvai_execute(
alias = "live_apps.propose",
args = { name: "Sales cockpit", app_code: "sales",
description: "Pipeline totals plus the follow-up list.",
tool_ids: [1697], database_uuids: ["8e0438a3-…"],
nirvai_tools: ["track_agents"], session: "sales-app" },
description = "Propose the data this sales app will read"
)
Test one section against real data. Nothing is mocked — the script runs against your live sources, and what comes back becomes that section's snapshot.
nirvai_execute(
alias = "live_apps.test_script",
args = { app: "sales", name: "open_deals",
code: "def main(params):\n from tools import database_ai_execute_select\n …",
database_uuids: ["8e0438a3-…"], session: "sales-app" },
description = "Check the open-deals section returns real rows"
)
Validate, then hand it back to you.
nirvai_execute(alias = "live_apps.test_app",
args = { app: "sales", session: "sales-app" },
description = "Run the gate battery on the finished app")
nirvai_execute(alias = "live_apps.stage",
args = { app: "sales", session: "sales-app" },
description = "Open the working preview so I can save it")
stage returns a link to the real, working app with a Save button on it. Nothing exists in your
account until you press it.
Limits & guarantees
- Read-only, always. No live-app section can modify a record, in any source, by any route.
- Sections are sandboxed. A data script gets no file access, no network and no imports — only the sources it declared, and the data it returns. (The unrestricted runner is a different area entirely: see Automations & skills.)
- Secrets never travel. Proposals carry source ids, names and shapes. The credentials behind them resolve on Nirvai's side, at the moment a script runs.
- You save it. Staging opens a preview. The app exists when you press Save — or when you've
approved it and your assistant calls
publish. - Editing is in place.
load→ change →publishkeeps the same app, id and history. - Yours only, by id. Apps are addressed by id, and only yours resolve; anything else comes back as not found. Deleting an app over the connection is impossible.
- Results stay bounded. Every action returns a preview plus a link. A full script result comes back as a downloadable handle, never pasted into a chat.
- Instant, then live. A published app paints from its stored snapshot immediately, then re-runs against live sources on demand.