You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Currently, the spec assumes a new view is rendered per tool call. Hosts may decide to implement variations of their own layout (e.g., a persistent side panel), but each invocation would still create a new instance under the hood. This approach was chosen to simplify the model for the MVP (#62).
Now that the spec is stable, we can examine deferred approaches, such as referencing previously rendered instances using viewSessionId (#295).
One use case for this is a situation like a board game where the AI plays its turns via direct tool calls and the user plays turns via view interactions. A nice experience would be for the view to not reload when context opens, but just receive new state and update. There may be more serious versions of this, but the concept is the same -- both the user and the model are exchanging interactions with the view being a shared "workspace".
Here is a use case that I am working on - a client-side map data visualization tool. This architecture is similar to Figma or Draw.io - most of the processing is done within the browser itself. Today users interact with the application (change the state) via the UI (add map layers, add filters, change the title), however in the future I envision LLMs/agents also being able to interact with the application (change the state) via postMessage().
I think a nice demo use case would be editing technical diagrams that are written in Mermaid Syntax - AI tools are very good at writing these.
Today the interaction goes one way:
Ask LLM to generate diagram → LLM writes Mermaid Syntax → Renders to show user. User must then export to draw.io/lucidchart to edit with a UI
It could look like this:
Ask LLM to generate diagram → LLM writes Mermaid Syntax → Loads MCP UI instance that renders Mermaid Syntax → User renames boxes in diagram → LLM receives updated state via postMessage() → Interaction loop continues
I think the main thing needed to accomplish this is a persisted UI instance that is loaded once, and then has an internal state and can receive & send with postMessage().
Theoretically web applications can be designed to be stateless and render a given config each time they are loaded. However, there are still significant 'setup' tasks like loading external resources and setting up the DOM that would affect the UI if they had to be repeated every turn in the conversation.
Some questions come to mind:
How is the history of a conversation with multiple turns rendered?
Can a user 'undo' actions? Is branching supported?
How is state persisted between sessions? The MCP server could maintain it's own state ('save my diagram for later'), or would the embedded iframe use localStorage/OPFS, or would the AI host remember tool usage/arguments?
Thank you @idosal for submitting this. Adding another use case:
I'm building an MCP server that embeds an interactive 3D viewer for CAD designs. The intended conversation flow looks like this:
Show me building XYZ
Isolate all doors
Color-code the doors by their material
Each of these steps calls a different tool (preview_design, highlight_objects, color_objects), but all three should act on the same viewer instance. The 3D model is already loaded and initialized, re-loading it from scratch on every tool call takes several seconds and loses transient state like camera position, active selection, and visual overrides.
Same experience here — I built a Mermaid diagram editor as an MCP App and the state loss on each tool call is the main UX issue. Zoom, pan, selection, pending edits all gone. Looking forward to viewSessionId.
There is a second trigger for this class of loss that I think is worth separating from
view reuse, because it bites within a single tool call and it is the host's to fix:
A host that offers more than one display mode destroys the View when it moves it.
Relocating an iframe in the DOM discards its nested browsing context, so a card that
moves from an inline transcript position into a pip strip or a fullscreen region is
reloaded — same tool call, same view instance, no re-render from the model.
Two clauses in the current spec read as if the View survives that move:
"Host MUST return the resulting mode (whether updated or not) in the response to ui/request-display-mode." On our host that MUST was satisfiable and unobservable:
measured, a grant that moved the card reloaded the frame, so the asking document was
gone before its own response arrived. Asking for the mode it already held (no move)
printed the answer fine; refusals always arrived, because a refusal moves nothing.
ui/notifications/host-context-changed says the View "SHOULD merge received fields
with its current context state", and names a display mode change as an example. There
is no current state to merge into if the View was re-created by that same change.
Hosts can fix this today, which is why I'd suggest a note rather than a protocol
change. Element.prototype.moveBefore — the state-preserving atomic move — relocates a
node without discarding it. Measured on Chrome 151 with a sandboxed srcdoc frame with
no allow-same-origin, nested one level inside the wrapper being moved (i.e. the shape
a host actually moves, not the easy case):
move
in-frame state
appendChild into a new parent
3 → none (document replaced)
moveBefore into a new parent
3 → 3 (same document)
Two things cost me time and are worth having in any note:
it is on Element.prototype (the ParentNode mixin), notNode.prototype — a
feature detect on Node reports unsupported in Chrome, where it demonstrably works,
and then silently selects the destructive path;
a detached node cannot be moved atomically (HierarchyRequestError), which is every
view's first mount.
Availability: Chrome/Edge 133+, Firefox 144+; WebKit's standards position is support
and unshipped, so a host needs an appendChild fallback and Safari keeps today's
behaviour there.
Suggestion: a non-normative host note under display modes — a host that relocates a
mounted View in the DOM must move it atomically, or the View is destroyed and the two
clauses above become unobservable. If widgetSessionId lands, the same requirement
applies to a reused View: reuse gets you the same instance across tool calls, and a
subsequent relocation still destroys it unless the move is atomic. Happy to open a PR
against the spec text if that's useful.
Activity
sam-mfb commentedon Feb 3, 2026
One use case for this is a situation like a board game where the AI plays its turns via direct tool calls and the user plays turns via view interactions. A nice experience would be for the view to not reload when context opens, but just receive new state and update. There may be more serious versions of this, but the concept is the same -- both the user and the model are exchanging interactions with the view being a shared "workspace".
carstonhernke commentedon Feb 3, 2026
Here is a use case that I am working on - a client-side map data visualization tool. This architecture is similar to Figma or Draw.io - most of the processing is done within the browser itself. Today users interact with the application (change the state) via the UI (add map layers, add filters, change the title), however in the future I envision LLMs/agents also being able to interact with the application (change the state) via postMessage().
I think a nice demo use case would be editing technical diagrams that are written in Mermaid Syntax - AI tools are very good at writing these.
Today the interaction goes one way:
Ask LLM to generate diagram → LLM writes Mermaid Syntax → Renders to show user. User must then export to draw.io/lucidchart to edit with a UI
It could look like this:
Ask LLM to generate diagram → LLM writes Mermaid Syntax → Loads MCP UI instance that renders Mermaid Syntax → User renames boxes in diagram → LLM receives updated state via postMessage() → Interaction loop continues
I think the main thing needed to accomplish this is a persisted UI instance that is loaded once, and then has an internal state and can receive & send with postMessage().
Theoretically web applications can be designed to be stateless and render a given config each time they are loaded. However, there are still significant 'setup' tasks like loading external resources and setting up the DOM that would affect the UI if they had to be repeated every turn in the conversation.
Some questions come to mind:
petrbroz commentedon Feb 24, 2026
Thank you @idosal for submitting this. Adding another use case:
I'm building an MCP server that embeds an interactive 3D viewer for CAD designs. The intended conversation flow looks like this:
Each of these steps calls a different tool (
preview_design,highlight_objects,color_objects), but all three should act on the same viewer instance. The 3D model is already loaded and initialized, re-loading it from scratch on every tool call takes several seconds and loses transient state like camera position, active selection, and visual overrides.psylch commentedon Feb 24, 2026
Same experience here — I built a Mermaid diagram editor as an MCP App and the state loss on each tool call is the main UX issue. Zoom, pan, selection, pending edits all gone. Looking forward to
viewSessionId.spec(draft): add split display mode
spec(draft): add split display mode
spec(draft): add split display mode
spec(draft): add split display mode
carlos-cubas commentedon Sep 2, 2026
There is a second trigger for this class of loss that I think is worth separating from
view reuse, because it bites within a single tool call and it is the host's to fix:
A host that offers more than one display mode destroys the View when it moves it.
Relocating an iframe in the DOM discards its nested browsing context, so a card that
moves from an inline transcript position into a pip strip or a fullscreen region is
reloaded — same tool call, same view instance, no re-render from the model.
Two clauses in the current spec read as if the View survives that move:
ui/request-display-mode." On our host that MUST was satisfiable and unobservable:measured, a grant that moved the card reloaded the frame, so the asking document was
gone before its own response arrived. Asking for the mode it already held (no move)
printed the answer fine; refusals always arrived, because a refusal moves nothing.
ui/notifications/host-context-changedsays the View "SHOULD merge received fieldswith its current context state", and names a display mode change as an example. There
is no current state to merge into if the View was re-created by that same change.
Hosts can fix this today, which is why I'd suggest a note rather than a protocol
change.
Element.prototype.moveBefore— the state-preserving atomic move — relocates anode without discarding it. Measured on Chrome 151 with a sandboxed
srcdocframe withno
allow-same-origin, nested one level inside the wrapper being moved (i.e. the shapea host actually moves, not the easy case):
appendChildinto a new parentnone(document replaced)moveBeforeinto a new parentTwo things cost me time and are worth having in any note:
Element.prototype(theParentNodemixin), notNode.prototype— afeature detect on
Nodereports unsupported in Chrome, where it demonstrably works,and then silently selects the destructive path;
HierarchyRequestError), which is everyview's first mount.
Availability: Chrome/Edge 133+, Firefox 144+; WebKit's standards position is support
and unshipped, so a host needs an
appendChildfallback and Safari keeps today'sbehaviour there.
Suggestion: a non-normative host note under display modes — a host that relocates a
mounted View in the DOM must move it atomically, or the View is destroyed and the two
clauses above become unobservable. If
widgetSessionIdlands, the same requirementapplies to a reused View: reuse gets you the same instance across tool calls, and a
subsequent relocation still destroys it unless the move is atomic. Happy to open a PR
against the spec text if that's useful.