feat: align MCP app v0.9 bridge, specs, pong improvements, and data d…#1945
Conversation
There was a problem hiding this comment.
Code Review
This pull request updates the MCP App Proxy community sample to support A2UI v0.9 features, including two-way local data binding, local client-side function execution, and dynamic resizing. It replaces the local score update tool with an AI-generated live commentary tool (commentate_pong_game) and adds a winner modal dialog. Key feedback points out a potential ReferenceError during early initialization in pong_app.html, state overwriting in catalog.ts that deletes commentary, missing parent width updates in mcp-app.ts causing horizontal clipping, redundant inline imports, and a potential AttributeError in tools.py if the LLM response is empty.
bbde0da to
4afc4f1
Compare
…eduplication - docs: add component specification for McpApp to the A2UI catalog - feat: add MCP app v0.9 bridge support with dynamic resizing, local function execution, and two-way data binding - feat: fix pong app layout by enforcing fixed dimensions and updating container constraints - feat: add AI-generated live commentary to the Pong game via a new tool and UI integration - fix: prevent circular data model updates by implementing structural equality deduplication
jacobsimionato
left a comment
There was a problem hiding this comment.
Hey this looks great to me in general - most of my comments are about the way it's expressed and more subtle technical details.
| **Host action** | ||
| The host checks if the requested tool name is in the component's `allowedTools` list. If it is authorized, the host dispatches the tool call as an A2UI action to the agent backend. If not, it rejects the tool call and returns a JSON-RPC error response. | ||
|
|
||
| ### B. Reactive state synchronization (`ui/notifications/data-model-change`) |
There was a problem hiding this comment.
I wonder if this should include a subpath param. There is only one data model binding key, which is used to propagate state from host -> app and app -> host, so if both are overwriting the entire state, I think race conditions and clobbering could occur seeing as there is an async jump over the sandbox boundary.
E.g.
- Establish MCP calculator app bound to
/calculator_state - Server sends
UpdateDataModelmessage to update/calculator_state/modetoscientific - Calculator responds to button press, sending
data-model-changemessage to update/calculator_state/expression_stringto3. However, it actually has to copy the entire existing content of/calculator_stateto update one value, e.g. it is setting it to{"mode": "basic", "expression_string": "3"}. - Host receives server update, updating
/calculator_state/modetoscientific. It sendsui/notifications/data-model-updateto calculator with the updated state. - Host receives
data-model-changesetting/calculator_stateback to{"mode": "basic", "expression_string": "3"}, clobbering its state - Calculator receives
ui/notifications/data-model-updatewith{"mode": "scientific", "expression_string": ""}, clobbering its state
I dunno, WDYT? Maybe I'm nit picking! Another option would be to have two params - one intended for host->app state and another for app->host.
There was a problem hiding this comment.
I didn't think about that so thank you for pointing me to this.
I have extended to accept a more surgical data update by targeting a subpath for the data.
It adds a bit of complexity to the deep-equality checks for preventing loop, but this is contained within the MCP App component.
I have also added a toggle switch to prevent echoing when the data updates from App -> Host, and the Host -> App broadcast is auto-triggered, that the event (downstream) propagation is terminated if the data update was originated form the App.
See cc9b4f6 commit.
| ``` | ||
|
|
||
| **Host action** | ||
| The host checks if the requested tool name is in the component's `allowedTools` list. If it is authorized, the host dispatches the tool call as an A2UI action to the agent backend. If not, it rejects the tool call and returns a JSON-RPC error response. |
There was a problem hiding this comment.
(sorry if I asked this before) is this necessary given that these RPCs will be forwarded to the host anyway, and it can always reject them there?
There was a problem hiding this comment.
It mostly depends on how much trust-worthy the embedded app is.
From the back-end's POV, the request will be coming from the host application and will assume the source is trust-worthy. However, if the request is originating from the embedded app, there may be some calls that the backend might not want to blindly execute (e.g., deleteAccount or readFileFromLocal etc)
| The host receives the `htmlContent` property from the catalog definition: | ||
|
|
||
| 1. It extracts the raw HTML string. | ||
| 2. If the string starts with `url_encoded:`, it decodes the content using `decodeURIComponent`. |
There was a problem hiding this comment.
Is this a standard approach? It seems a bit magical and confusing to me. Could we just support either always URL encoded, or never URL encoded? Or even have alternative input properties for each option (though that would require them to be optional I know).
There was a problem hiding this comment.
So this is actually tricky.
MCP Apps naturally are served from MCP server. As a result, native MCP Apps are fetched from a client directly from a MCP server's resource endpoint (or Tool endpoint). This means the raw HTML doesn't go through LLM's context which is great.
On the other hand, when MCP Apps are inserted within an A2UI payload, they can be included in the Agent's LLM dialog. In such case, the raw HTML is terrible due to the HTML tags and excessive new lines. URL encoding helps make them into a single "mystery string" to maintain its composure.
I may be able to study a bit more from Liad's change and more examples about MCP Apps on how I might be able to get the HTML blob out-of-LLM-context, but as of now, this was the solution I had in place.
There was a problem hiding this comment.
Assuming we want to make A2UI as simple as Client UI <-> A2UI Agent, I need to figure out how I might be able to make MCP Apps A2UI component to asynchronously fetch MCP App resource from A2UI Agent without contaminating the LLM Context.
I will work on this as a follow up since it is pretty critical.
There was a problem hiding this comment.
Hey I agree this is a critical problem!
The architecture I was actually hoping to get to with A2UI is to have a transformer between the actual persisted message context, and any LLM inferences, so that we can store the HTML string in its most basic form, but then omit context as necessary to hide it from the LLM, without needing to introduce a more complex RPC protocol with async fetching etc.
There are multiple reasons we might want to transform A2UI content between the message history and LLM:
- To expose A2UI via a more token efficient format - see https://docs.google.com/document/d/1mH2DJ_jZZA2vZ9EQlWaU4v8_xCAcmqrox_LlElmJ3ws/edit?tab=t.0
- For surfaces that have been updated incrementally, to flatten them to a snapshot of current state
- To include ephemeral state of surfaces pulled from the client
- To omit aspects of the A2UI content which are not relevant to future inferences, to save tokens. E.g. protocol details like catalog ID, surface ID, component IDs even (perhaps we could use a nested format for reading).
Seeing as we need all this translation stuff anyway, maybe we can have a concept of "plugins" that allow you to add additional catalog-specific transformations, or they can even be included as part of the catalog implementation, or annotated on the catalog API in the future "hiddenFromHistory = true" etc.
WDYT?
There was a problem hiding this comment.
I love the idea of having an API to omit certain parts of A2UI payload from the context history, and very happy to onboard on that!
I think there are ways I can leverage architectures learned from MCP to mitigate the problem for MCP Apps and IFrame, but would be great to have a solution overarching the A2UI as a whole :)
That being said, is there something I can add to the current PR? Might I add a Note or a comment on forthcoming changes that might influence this specification?
…eduplication - docs: add component specification for McpApp to the A2UI catalog - feat: add MCP app v0.9 bridge support with dynamic resizing, local function execution, and two-way data binding - feat: fix pong app layout by enforcing fixed dimensions and updating container constraints - feat: add AI-generated live commentary to the Pong game via a new tool and UI integration - fix: prevent circular data model updates by implementing structural equality deduplication
… data, and title while updating the specification documentation.
…ference to official MCP Apps SDK examples
…edback loop prevention using write locks
…ntation spacing in specification file
Description
Overview
This pull request introduces the MCP Apps component specification for A2UI V0.9, and updates the McpApp component and the Pong game demo to the introduced A2UI v0.9 specification. The changes include adding the component specification document, implementing host-guest message routing, updating the Pong application logic, and preventing cyclic updates.
See the updated Pong Demo here https://screencast.googleplex.com/cast/NjE3MDM1NzE3MzA1OTU4NHxkYTRhMjhjNy01Yw
Key changes
Added specification: Added
mcp-apps-component_spec.mdto define security boundaries, message schemas, and catalog properties for the McpApp component.Component properties: Updated McpAppSchema in catalog.ts to replace the older
contentfield withhtmlContent(which was conflicting with a reserved property name in A2UI v0.9), and addedallowedFunctionsand data properties.Double-iframe host logic: Updated
mcp-app.tsto process sizing requests, handle client-side function invocations, and route data model updates.Deduplication: Added structural equality validation using stringified JSON comparisons to break infinite state update loops between host and the embedded app.
Scoreboard commentary: Updated pong-scoreboard.ts to display generated game commentary to demonstrate the Allowed Tool Calls from the embedded MCP App.
Score tracking: Modified pong_app.html to run score tracking using the A2UI V0.9's Tow-way data binding feature (instead of ToolCall routing we did in A2UI V0.8).
Game winner: Implemented a UI overlay that announces the winner of the game in the A2UI catalog to demonstrate the Allowed Function Calls from the embedded MCP App. This is integration with the new FunctionCall feature on A2UI V0.9.
The sections below describe how the specifications are demonstrated in the Pong game demo.
Dynamic resizing
The specification allows sandboxed applications to request window dimension changes. The host implements layout constraints including clamping, throttling, and change thresholds.
In the demo, once
pong_app.html
finishes initializing and completes the handshake, it transmits a ui/notifications/size-change request to resize the wrapper to 728 by 502 pixels. The host component receives this and resizes the iframe and parent container styles. This keeps the canvas fully visible without displaying scrollbars.
Two-way local data binding
The specification defines data synchronization between the sandboxed application and the host data model. This path is configured via data.path in the catalog.
In the demo, this is shown in two ways:
Infinite event loop prevention
Both the host component and the game script check incoming values against the last processed state. If the stringified JSON matches, the update is dropped. This prevents the host and guest from continuously re-triggering updates.
Local client-side function execution
The specification allows the sandboxed application to trigger pre-registered host functions if permitted by allowedFunctions.
In the demo, when a score reaches three points, the game script calls the host function showWinnerModal. The host validates permission and evaluates the function. The function creates and displays an HTML dialog stating the winner. Clicking the restart button inside the modal updates the host data model to reset the scores.
Tool call routing
The specification permits sandboxed applications to request remote tool executions defined by the agent.
In the demo, whenever a point is scored or the match restarts, the game script requests the commentate_pong tool. The host forwards this action to the agent backend. The agent calls the Gemini model to produce a short comment under 15 words. The agent then writes the comment back to /pong_state/commentary, which is updated on the scoreboard.
Pre-launch Checklist
One time:
For this PR:
If you need help, consider asking for advice on the discussion board.