Skip to main content

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.

Why the phases are ordered

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

PhaseActionScopeWhat it does
Discoverlive_apps.list_custom_tools(search)readYour connected tools, each with the id a script uses to reach it. Always searched — a real account has hundreds.
live_apps.list_nirvai_tools()readEverything already inside Nirvai: your databases, your memory stores, and Nirvai's own platform data.
live_apps.get_database_info(database_uuids)readThe real columns and types of the databases this app will read, and how to filter them.
live_apps.get_memory_info(memory_uuids)readWhat a memory store holds, and the read-only ways a script may query it.
live_apps.propose(name, sources, …)runOpens a review page listing exactly which sources the app will read. You accept, or send changes back.
Datalive_apps.test_script(code, sources, app, name)runRuns 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?)readReads those snapshots back as shapes — how many rows, which fields, when it ran.
Themelive_apps.list_brand_logos(query?)readReal brand marks for the services the app covers, so it's branded rather than hand-drawn.
live_apps.theme_propose(directions, …)runOpens a look board with one to three complete designs. You pick one.
Validatelive_apps.test_app(app)runThe gate battery: structure, then a real headless render across full, empty and error states, at desktop and mobile. Loop until it passes.
Stagelive_apps.stage(app)runTurns the validated app into a working preview with a Save button. Refuses unless the last test_app succeeded.
Savelive_apps.publish(title, app, …)writeFinalises it into a permanent app. Given an existing app's id, it edits that app in place.
Any timelive_apps.list(filter?)readYour apps, as slim cards.
live_apps.get(app)readOne app's full contract — status, theme, its sections and the sources each declares.
live_apps.load(uuid, app?)readCopies a published app back into the working session so it can be edited and republished.
live_apps.list_data_sources(query?)readDeprecated. 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. stage refuses an app whose last test_app failed.
  • 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.publish is 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:

SourceDeclared asWhat it gives the app
Your connected toolstool_idsAnything you've connected — a store, an ad account, a CRM.
Your Nirvai databasesdatabase_uuidsYour own tables, addressed by id.
Your memory storesmemory_uuidsThe long-term facts your agents keep — read-only.
Nirvai's own datanirvai_toolsYour 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.

A live app can only look

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 → publish keeps 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.

What's next