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..af601da271 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 (typeof containerDimensions.width === 'number') { + document.documentElement.style.width = `${containerDimensions.width}px`; + document.body.style.width = `${containerDimensions.width}px`; + } + if (typeof containerDimensions.height === 'number') { + 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,14 @@ .then(result => { console.log('Initialized with host:', result); sendNotification('ui/notifications/initialized', {}); - 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 2d390674a5..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 @@ -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,32 @@ export class McpApp extends CatalogComponent implements OnDestroy, OnInit { return {content: []}; }; + // 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(); + } + 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.