ποΈ The model in one picture
- You author neutral JSON definitions and token files.
- The SDK on each platform renders them natively. It hardcodes no feature, no style, no UI concept β it resolves tokens and moves your state keys, nothing else.
- The engine runs workflows whose agents pick your components (by
whenToUse) and stream data into them.
Why SDUI
π§± The closed primitive set
Definitions compose ONLY these. The set is frozen β adding to it is an SDK change, and a build-failing guard test enforces that.
β Wrong instinct: βI need a
Chart / Accordion / Carousel primitive.β
β
Right instinct: compose it β bars are Box + Each over data; an accordion is visibleWhen on a dev-named state key. If it genuinely cannot be composed, thatβs a platform conversation, not a definition.
π MCP Apps β the standard
A template is an MCP App. This is not an integration detail; it is the contract every client and every tool in the ecosystem shares:
The rule: a host must NEVER hand-roll its own transport β no bespoke REST send, no custom
user_action message, no side-channel state push. The SDK owns the one interaction path; every consumer (Studio, a native app, an external MCP client) shares it. If your channel needs something the path doesnβt do, thatβs a platform gap to raise β not a workaround to build.
The org endpoint + its default app (the front door)
Each org is its own MCP endpoint β a self-contained connector / βexperienceβ:manifest.json sets "default": true β the orgβs home (typically its chat template, e.g. acmechatlayout). The endpoint tags that tool with _meta["unoverse/default"], so a client knows which app to open first as the conversationβs entry point. A lint rule enforces at most one default per org.
MCP is pull-based (no βauto-open on connectβ): our SDK reads that flag and opens the home app immediately, rendering its arrival defaultState; a foreign host (ChatGPT) surfaces it when the user first engages. default only answers βwhich app is the front doorβ β the appβs own defaultState/autoTrigger/binding behave exactly as usual. (The old per-app expose flag is removed β org scoping is the boundary now.)
The two lanes (know which carries what)
Everything you author reads from the first lane. The second lane belongs to native services (like voice) β covered in 04 β State, because itβs where βlockedβ state comes from.
π¨ The four ways a component renders
Every design component reaches the chat by one of four paths β two in the workflow world, two discovered natively from Spatial. Every path ends identically: the SDK renders your definition into the conversation. Knowing which path youβre on tells you where the data comes from and whether a workflow is involved. (Canonical:docs/unoverse/UNOVERSE_MCP_TEMPLATE_PROTOCOL.md Β§βThe four ways UI reaches the chatβ.)
Two worlds:
- A + D β the workflow world. Assign a workflow; it drives the surface (A) and streams its pieces in (D). Streaming is the standard runtime paint path, not a legacy one.
- B + C β the native-MCP-from-spatial world. The component is discovered, not pushed. B carries its own data; C is a reusable card shell a Spatial node fills.
ui:// resource); the agent does an ordinary tools/call, the result carries the UI, and the SDK renders it. Nothing component-specific is invented.
This axis is orthogonal to presentation. The four ways are only how the data arrives. Once a component is in the store β by any of the four β how itβs shown is the reaction contract: the component carries its own defaultState and owns its faces (inline, focused, product, β¦); a template reacts to that state via a ComponentSlot.select.where surface and frames the matching face β it never reaches into the component. So a streamed (D) product card can write defaultState: "product" and a chat templateβs product surface frames it, exactly the same way a self-contained (B) one would. Which of the four delivered it, and how a template presents it, are independent choices β see 04 β State.
π One path, dev and prod
Studio is not a special harness. It is just another MCP client: it subscribes to the same definition resources, receives the same component stream, and runs the same native renderers as production channels. Thatβs why:- βWorks in Studioβ provably means βworks in production.β
- Hot reload in Studio is the same
resources/subscribemechanism that live-updates production channels. - Live mode in Studio is literally production, pointed at local renderers.
Next: coming from React or Flutter? Read the 02a translation table first. Then 03 β Components β composition, props, and reuse.

