From 969c69d5317eb7da330631a4665fb2b4cabf9133 Mon Sep 17 00:00:00 2001 From: Yuzuru Tanahashi Date: Tue, 21 Jul 2026 17:17:33 +0000 Subject: [PATCH 1/3] feat: implement host-driven container dimension management via ResizeObserver and host-context-changed notification --- .../agent/adk/mcp_app_proxy/pong_app.html | 23 ++++++++++++++++- .../src/a2ui-catalog/mcp-app.ts | 22 ++++++++++++++++ .../a2ui-catalog/mcp-apps-component_spec.md | 25 ++++++++++++++++++- 3 files changed, 68 insertions(+), 2 deletions(-) diff --git a/samples/community/agent/adk/mcp_app_proxy/pong_app.html b/samples/community/agent/adk/mcp_app_proxy/pong_app.html index 8169296100..78f239410e 100644 --- a/samples/community/agent/adk/mcp_app_proxy/pong_app.html +++ b/samples/community/agent/adk/mcp_app_proxy/pong_app.html @@ -242,12 +242,27 @@ window.parent.postMessage({jsonrpc: '2.0', method, params}, '*'); } + function applyContainerDimensions(containerDimensions) { + if (!containerDimensions) return; + if (containerDimensions.width) { + document.documentElement.style.width = `${containerDimensions.width}px`; + document.body.style.width = `${containerDimensions.width}px`; + } + if (containerDimensions.height) { + document.documentElement.style.height = `${containerDimensions.height}px`; + document.body.style.height = `${containerDimensions.height}px`; + } + if (typeof resize === 'function') resize(); + } + window.addEventListener('message', event => { const data = event.data; if (data?.method === 'ping') { if (data.id) { window.parent.postMessage({jsonrpc: '2.0', id: data.id, result: {}}, '*'); } + } else if (data?.method === 'ui/notifications/host-context-changed') { + applyContainerDimensions(data.params?.containerDimensions); } else if (data?.method === 'ui/notifications/data-model-update') { // TODO: Refactor message handlers to use `@modelcontextprotocol/ext-apps/app-bridge` App client. const params = data.params; @@ -307,7 +322,13 @@ .then(result => { console.log('Initialized with host:', result); sendNotification('ui/notifications/initialized', {}); - sendNotification('ui/notifications/size-changed', {width: 728, height: 502}); + + // The app is fully reactive to the host dimensions. + // The host determines the container dimensions and passes them to the app. + applyContainerDimensions(result.hostContext?.containerDimensions); + + // Alternatively, if the app is to control the dimensions, uncomment and use: + // sendNotification('ui/notifications/size-changed', {width: 728, height: 502}); }) .catch(err => { console.error('Initialization failed:', err); diff --git a/samples/community/client/angular/projects/mcp_calculator/src/a2ui-catalog/mcp-app.ts b/samples/community/client/angular/projects/mcp_calculator/src/a2ui-catalog/mcp-app.ts index 2d390674a5..f72b03f838 100644 --- a/samples/community/client/angular/projects/mcp_calculator/src/a2ui-catalog/mcp-app.ts +++ b/samples/community/client/angular/projects/mcp_calculator/src/a2ui-catalog/mcp-app.ts @@ -118,6 +118,7 @@ export class McpApp extends CatalogComponent implements OnDestroy, OnInit { private lastHeight?: number; private lastBoundRootValue: string | null = null; private isProcessingAppWrite = false; + private hostResizeObserver: ResizeObserver | null = null; ngOnInit() { this.setupSandbox(); @@ -135,6 +136,10 @@ export class McpApp extends CatalogComponent implements OnDestroy, OnInit { if (this.messageHandler) { window.removeEventListener('message', this.messageHandler); } + if (this.hostResizeObserver) { + this.hostResizeObserver.disconnect(); + this.hostResizeObserver = null; + } const bridge = this.appBridge(); if (bridge) { bridge.close().catch(e => console.error('Error closing AppBridge on destroy:', e)); @@ -400,6 +405,23 @@ export class McpApp extends CatalogComponent implements OnDestroy, OnInit { return {content: []}; }; + // Inform the app of its dimensions + if (this.hostResizeObserver) { + this.hostResizeObserver.disconnect(); + } + this.hostResizeObserver = new ResizeObserver(entries => { + const entry = entries[0]; + if (entry && bridge) { + bridge.setHostContext({ + containerDimensions: { + width: entry.contentRect.width, + height: entry.contentRect.height, + } + }); + } + }); + this.hostResizeObserver.observe(iframe); + // Connect the bridge // We must pass the iframe's contentWindow as the target const transport = new PostMessageTransport( diff --git a/samples/community/client/angular/projects/mcp_calculator/src/a2ui-catalog/mcp-apps-component_spec.md b/samples/community/client/angular/projects/mcp_calculator/src/a2ui-catalog/mcp-apps-component_spec.md index aa633d6ca4..0480656f77 100644 --- a/samples/community/client/angular/projects/mcp_calculator/src/a2ui-catalog/mcp-apps-component_spec.md +++ b/samples/community/client/angular/projects/mcp_calculator/src/a2ui-catalog/mcp-apps-component_spec.md @@ -298,7 +298,30 @@ Sent as a response to a `ui/requests/function-call` request. **Embedded app action** The embedded app processes the result or error based on its own logic. -### C. Resource update (`ui/notifications/sandbox-resource-ready`) +### C. Host context update (`ui/notifications/host-context-changed`) + +Sent when the host's context changes, such as when the container dimensions are resized or the theme is toggled. This aligns with the [Model Context Protocol Apps Specification: Host Context](https://github.com/modelcontextprotocol/ext-apps/blob/main/specification/2026-01-26/apps.mdx). + +**Message schema** + +```json +{ + "jsonrpc": "2.0", + "method": "ui/notifications/host-context-changed", + "params": { + "theme": "string", + "containerDimensions": { + "width": "number", + "height": "number" + } + } +} +``` + +**Embedded app action** +The embedded app SHOULD merge the received context fields with its current state. If `containerDimensions` are provided, the app SHOULD adapt its internal layout to fit within the newly specified fixed or flexible boundaries. + +### D. Resource update (`ui/notifications/sandbox-resource-ready`) Sent when the application HTML content is modified or updated by the A2UI agent. From 09fafeb0d1c0bfd5dc81423d49385a37affb4a4e Mon Sep 17 00:00:00 2001 From: Yuzuru Tanahashi Date: Tue, 21 Jul 2026 17:27:38 +0000 Subject: [PATCH 2/3] refactor: cleanup whitespace and trailing comma in mcp-app and pong-app configurations --- samples/community/agent/adk/mcp_app_proxy/pong_app.html | 6 +++--- .../projects/mcp_calculator/src/a2ui-catalog/mcp-app.ts | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/samples/community/agent/adk/mcp_app_proxy/pong_app.html b/samples/community/agent/adk/mcp_app_proxy/pong_app.html index 78f239410e..57bac69d53 100644 --- a/samples/community/agent/adk/mcp_app_proxy/pong_app.html +++ b/samples/community/agent/adk/mcp_app_proxy/pong_app.html @@ -322,11 +322,11 @@ .then(result => { console.log('Initialized with host:', result); sendNotification('ui/notifications/initialized', {}); - - // The app is fully reactive to the host dimensions. + + // The app is fully reactive to the host dimensions. // The host determines the container dimensions and passes them to the app. applyContainerDimensions(result.hostContext?.containerDimensions); - + // Alternatively, if the app is to control the dimensions, uncomment and use: // sendNotification('ui/notifications/size-changed', {width: 728, height: 502}); }) diff --git a/samples/community/client/angular/projects/mcp_calculator/src/a2ui-catalog/mcp-app.ts b/samples/community/client/angular/projects/mcp_calculator/src/a2ui-catalog/mcp-app.ts index f72b03f838..9f09319161 100644 --- a/samples/community/client/angular/projects/mcp_calculator/src/a2ui-catalog/mcp-app.ts +++ b/samples/community/client/angular/projects/mcp_calculator/src/a2ui-catalog/mcp-app.ts @@ -416,7 +416,7 @@ export class McpApp extends CatalogComponent implements OnDestroy, OnInit { containerDimensions: { width: entry.contentRect.width, height: entry.contentRect.height, - } + }, }); } }); From 3cf11e3d73f1b550fbcead07e3b48b77009b7134 Mon Sep 17 00:00:00 2001 From: Yuzuru Tanahashi Date: Tue, 21 Jul 2026 17:40:40 +0000 Subject: [PATCH 3/3] fix: update container dimension handling to support explicit initialization and numeric validation --- .../agent/adk/mcp_app_proxy/pong_app.html | 17 +++++++++-------- .../mcp_calculator/src/a2ui-catalog/mcp-app.ts | 11 ++++++++++- 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/samples/community/agent/adk/mcp_app_proxy/pong_app.html b/samples/community/agent/adk/mcp_app_proxy/pong_app.html index 57bac69d53..af601da271 100644 --- a/samples/community/agent/adk/mcp_app_proxy/pong_app.html +++ b/samples/community/agent/adk/mcp_app_proxy/pong_app.html @@ -244,11 +244,11 @@ function applyContainerDimensions(containerDimensions) { if (!containerDimensions) return; - if (containerDimensions.width) { + if (typeof containerDimensions.width === 'number') { document.documentElement.style.width = `${containerDimensions.width}px`; document.body.style.width = `${containerDimensions.width}px`; } - if (containerDimensions.height) { + if (typeof containerDimensions.height === 'number') { document.documentElement.style.height = `${containerDimensions.height}px`; document.body.style.height = `${containerDimensions.height}px`; } @@ -323,12 +323,13 @@ console.log('Initialized with host:', result); sendNotification('ui/notifications/initialized', {}); - // The app is fully reactive to the host dimensions. - // The host determines the container dimensions and passes them to the app. - applyContainerDimensions(result.hostContext?.containerDimensions); - - // Alternatively, if the app is to control the dimensions, uncomment and use: - // sendNotification('ui/notifications/size-changed', {width: 728, height: 502}); + // The app is reactive to host dimensions if provided; otherwise, it falls back + // to requesting default dimensions to maintain backward compatibility. + if (result.hostContext?.containerDimensions) { + applyContainerDimensions(result.hostContext.containerDimensions); + } else { + sendNotification('ui/notifications/size-changed', {width: 728, height: 502}); + } }) .catch(err => { console.error('Initialization failed:', err); diff --git a/samples/community/client/angular/projects/mcp_calculator/src/a2ui-catalog/mcp-app.ts b/samples/community/client/angular/projects/mcp_calculator/src/a2ui-catalog/mcp-app.ts index 9f09319161..ff345f2eaf 100644 --- a/samples/community/client/angular/projects/mcp_calculator/src/a2ui-catalog/mcp-app.ts +++ b/samples/community/client/angular/projects/mcp_calculator/src/a2ui-catalog/mcp-app.ts @@ -405,7 +405,16 @@ export class McpApp extends CatalogComponent implements OnDestroy, OnInit { return {content: []}; }; - // Inform the app of its dimensions + // Set initial host context synchronously so it is available during the handshake + const rect = iframe.getBoundingClientRect(); + bridge.setHostContext({ + containerDimensions: { + width: rect.width, + height: rect.height, + }, + }); + + // Inform the app of subsequent dimension changes if (this.hostResizeObserver) { this.hostResizeObserver.disconnect(); }