Separate viewport and containerDimensions

This commit is contained in:
martinalong 2025-12-26 14:04:05 -08:00
commit 82e64d56bb
No known key found for this signature in database
5 changed files with 320 additions and 110 deletions

View file

@ -113,7 +113,8 @@ describe("App <-> AppBridge integration", () => {
const testHostContext = {
theme: "dark" as const,
locale: "en-US",
viewport: { width: 800, height: 600, maxHeight: 600 },
viewport: { width: 800, height: 600 },
containerDimensions: { width: 800, maxHeight: 600 },
};
const newBridge = new AppBridge(
createMockClient() as Client,
@ -337,7 +338,8 @@ describe("App <-> AppBridge integration", () => {
const initialContext = {
theme: "light" as const,
locale: "en-US",
viewport: { width: 800, height: 600, maxHeight: 600 },
viewport: { width: 800, height: 600 },
containerDimensions: { width: 800, maxHeight: 600 },
};
const newBridge = new AppBridge(
createMockClient() as Client,
@ -354,22 +356,26 @@ describe("App <-> AppBridge integration", () => {
newBridge.sendHostContextChange({ theme: "dark" });
await flush();
// Send another partial update: only viewport changes
// Send another partial update: only viewport and containerDimensions change
newBridge.sendHostContextChange({
viewport: { width: 1024, height: 768, maxHeight: 768 },
viewport: { width: 1024, height: 768 },
containerDimensions: { width: 1024, maxHeight: 768 },
});
await flush();
// getHostContext should have accumulated all updates:
// - locale from initial (unchanged)
// - theme from first partial update
// - viewport from second partial update
// - viewport and containerDimensions from second partial update
const context = newApp.getHostContext();
expect(context?.theme).toBe("dark");
expect(context?.locale).toBe("en-US");
expect(context?.viewport).toEqual({
width: 1024,
height: 768,
});
expect(context?.containerDimensions).toEqual({
width: 1024,
maxHeight: 768,
});

View file

@ -1008,7 +1008,8 @@ export class AppBridge extends Protocol<
* ```typescript
* bridge.setHostContext({
* theme: "dark",
* viewport: { width: 800, maxHeight: 600 }
* viewport: { width: 800, height: 600 },
* containerDimensions: { maxHeight: 600, width: 800 }
* });
* ```
*

View file

@ -632,12 +632,77 @@
"type": "string"
}
},
"containerDimensions": {
"description": "Container dimensions. Represents the dimensions of the iframe or other\ncontainer holding the app. Specify either width or maxWidth, and either height or maxHeight.",
"allOf": [
{
"anyOf": [
{
"type": "object",
"properties": {
"height": {
"description": "Fixed container height in pixels.",
"type": "number"
}
},
"required": ["height"],
"additionalProperties": false
},
{
"type": "object",
"properties": {
"maxHeight": {
"description": "Maximum container height in pixels.",
"anyOf": [
{
"type": "number"
},
{}
]
}
},
"additionalProperties": false
}
]
},
{
"anyOf": [
{
"type": "object",
"properties": {
"width": {
"description": "Fixed container width in pixels.",
"type": "number"
}
},
"required": ["width"],
"additionalProperties": false
},
{
"type": "object",
"properties": {
"maxWidth": {
"description": "Maximum container width in pixels.",
"anyOf": [
{
"type": "number"
},
{}
]
}
},
"additionalProperties": false
}
]
}
]
},
"viewport": {
"description": "Current and maximum dimensions available to the UI.",
"description": "Window viewport dimensions. Represents the host window's viewport size,\nwhich provides additional information apps can use to make responsive layout decisions.",
"type": "object",
"properties": {
"width": {
"description": "Viewport width in pixels (if fixed). Only pass width or maxWidth, not both.",
"description": "Window viewport width in pixels.",
"anyOf": [
{
"type": "number"
@ -646,25 +711,7 @@
]
},
"height": {
"description": "Viewport height in pixels (if fixed). Only pass height or maxHeight, not both.",
"anyOf": [
{
"type": "number"
},
{}
]
},
"maxWidth": {
"description": "Maximum available viewport width in pixels (if constrained). Only pass width or maxWidth, not both.",
"anyOf": [
{
"type": "number"
},
{}
]
},
"maxHeight": {
"description": "Maximum available viewport height in pixels (if constrained). Only pass height or maxHeight, not both.",
"description": "Window viewport height in pixels.",
"anyOf": [
{
"type": "number"
@ -1281,12 +1328,77 @@
"type": "string"
}
},
"containerDimensions": {
"description": "Container dimensions. Represents the dimensions of the iframe or other\ncontainer holding the app. Specify either width or maxWidth, and either height or maxHeight.",
"allOf": [
{
"anyOf": [
{
"type": "object",
"properties": {
"height": {
"description": "Fixed container height in pixels.",
"type": "number"
}
},
"required": ["height"],
"additionalProperties": false
},
{
"type": "object",
"properties": {
"maxHeight": {
"description": "Maximum container height in pixels.",
"anyOf": [
{
"type": "number"
},
{}
]
}
},
"additionalProperties": false
}
]
},
{
"anyOf": [
{
"type": "object",
"properties": {
"width": {
"description": "Fixed container width in pixels.",
"type": "number"
}
},
"required": ["width"],
"additionalProperties": false
},
{
"type": "object",
"properties": {
"maxWidth": {
"description": "Maximum container width in pixels.",
"anyOf": [
{
"type": "number"
},
{}
]
}
},
"additionalProperties": false
}
]
}
]
},
"viewport": {
"description": "Current and maximum dimensions available to the UI.",
"description": "Window viewport dimensions. Represents the host window's viewport size,\nwhich provides additional information apps can use to make responsive layout decisions.",
"type": "object",
"properties": {
"width": {
"description": "Viewport width in pixels (if fixed). Only pass width or maxWidth, not both.",
"description": "Window viewport width in pixels.",
"anyOf": [
{
"type": "number"
@ -1295,25 +1407,7 @@
]
},
"height": {
"description": "Viewport height in pixels (if fixed). Only pass height or maxHeight, not both.",
"anyOf": [
{
"type": "number"
},
{}
]
},
"maxWidth": {
"description": "Maximum available viewport width in pixels (if constrained). Only pass width or maxWidth, not both.",
"anyOf": [
{
"type": "number"
},
{}
]
},
"maxHeight": {
"description": "Maximum available viewport height in pixels (if constrained). Only pass height or maxHeight, not both.",
"description": "Window viewport height in pixels.",
"anyOf": [
{
"type": "number"
@ -2467,12 +2561,77 @@
"type": "string"
}
},
"containerDimensions": {
"description": "Container dimensions. Represents the dimensions of the iframe or other\ncontainer holding the app. Specify either width or maxWidth, and either height or maxHeight.",
"allOf": [
{
"anyOf": [
{
"type": "object",
"properties": {
"height": {
"description": "Fixed container height in pixels.",
"type": "number"
}
},
"required": ["height"],
"additionalProperties": false
},
{
"type": "object",
"properties": {
"maxHeight": {
"description": "Maximum container height in pixels.",
"anyOf": [
{
"type": "number"
},
{}
]
}
},
"additionalProperties": false
}
]
},
{
"anyOf": [
{
"type": "object",
"properties": {
"width": {
"description": "Fixed container width in pixels.",
"type": "number"
}
},
"required": ["width"],
"additionalProperties": false
},
{
"type": "object",
"properties": {
"maxWidth": {
"description": "Maximum container width in pixels.",
"anyOf": [
{
"type": "number"
},
{}
]
}
},
"additionalProperties": false
}
]
}
]
},
"viewport": {
"description": "Current and maximum dimensions available to the UI.",
"description": "Window viewport dimensions. Represents the host window's viewport size,\nwhich provides additional information apps can use to make responsive layout decisions.",
"type": "object",
"properties": {
"width": {
"description": "Viewport width in pixels (if fixed). Only pass width or maxWidth, not both.",
"description": "Window viewport width in pixels.",
"anyOf": [
{
"type": "number"
@ -2481,25 +2640,7 @@
]
},
"height": {
"description": "Viewport height in pixels (if fixed). Only pass height or maxHeight, not both.",
"anyOf": [
{
"type": "number"
},
{}
]
},
"maxWidth": {
"description": "Maximum available viewport width in pixels (if constrained). Only pass width or maxWidth, not both.",
"anyOf": [
{
"type": "number"
},
{}
]
},
"maxHeight": {
"description": "Maximum available viewport height in pixels (if constrained). Only pass height or maxHeight, not both.",
"description": "Window viewport height in pixels.",
"anyOf": [
{
"type": "number"

View file

@ -563,40 +563,64 @@ export const McpUiHostContextSchema = z.looseObject({
.array(z.string())
.optional()
.describe("Display modes the host supports."),
/** @description Current and maximum dimensions available to the UI. */
/**
* @description Container dimensions. Represents the dimensions of the iframe or other
* container holding the app. Specify either width or maxWidth, and either height or maxHeight.
*/
containerDimensions: z
.union([
z.object({
/** @description Fixed container height in pixels. */
height: z.number().describe("Fixed container height in pixels."),
}),
z.object({
/** @description Maximum container height in pixels. */
maxHeight: z
.union([z.number(), z.undefined()])
.optional()
.describe("Maximum container height in pixels."),
}),
])
.and(
z.union([
z.object({
/** @description Fixed container width in pixels. */
width: z.number().describe("Fixed container width in pixels."),
}),
z.object({
/** @description Maximum container width in pixels. */
maxWidth: z
.union([z.number(), z.undefined()])
.optional()
.describe("Maximum container width in pixels."),
}),
]),
)
.optional()
.describe(
"Container dimensions. Represents the dimensions of the iframe or other\ncontainer holding the app. Specify either width or maxWidth, and either height or maxHeight.",
),
/**
* @description Window viewport dimensions. Represents the host window's viewport size,
* which provides additional information apps can use to make responsive layout decisions.
*/
viewport: z
.object({
/** @description Viewport width in pixels (if fixed). Only pass width or maxWidth, not both. */
/** @description Window viewport width in pixels. */
width: z
.union([z.number(), z.undefined()])
.optional()
.describe(
"Viewport width in pixels (if fixed). Only pass width or maxWidth, not both.",
),
/** @description Viewport height in pixels (if fixed). Only pass height or maxHeight, not both. */
.describe("Window viewport width in pixels."),
/** @description Window viewport height in pixels. */
height: z
.union([z.number(), z.undefined()])
.optional()
.describe(
"Viewport height in pixels (if fixed). Only pass height or maxHeight, not both.",
),
/** @description Maximum available viewport width in pixels (if constrained). Only pass width or maxWidth, not both.*/
maxWidth: z
.union([z.number(), z.undefined()])
.optional()
.describe(
"Maximum available viewport width in pixels (if constrained). Only pass width or maxWidth, not both.",
),
/** @description Maximum available viewport height in pixels (if constrained). Only pass height or maxHeight, not both. */
maxHeight: z
.union([z.number(), z.undefined()])
.optional()
.describe(
"Maximum available viewport height in pixels (if constrained). Only pass height or maxHeight, not both.",
),
.describe("Window viewport height in pixels."),
})
.optional()
.describe("Current and maximum dimensions available to the UI."),
.describe(
"Window viewport dimensions. Represents the host window's viewport size,\nwhich provides additional information apps can use to make responsive layout decisions.",
),
/** @description User's language and region preference in BCP 47 format. */
locale: z
.string()

View file

@ -325,16 +325,39 @@ export interface McpUiHostContext {
displayMode?: McpUiDisplayMode;
/** @description Display modes the host supports. */
availableDisplayModes?: string[];
/** @description Current and maximum dimensions available to the UI. */
/**
* @description Container dimensions. Represents the dimensions of the iframe or other
* container holding the app. Specify either width or maxWidth, and either height or maxHeight.
*/
containerDimensions?: (
| {
/** @description Fixed container height in pixels. */
height: number;
}
| {
/** @description Maximum container height in pixels. */
maxHeight?: number | undefined;
}
) &
(
| {
/** @description Fixed container width in pixels. */
width: number;
}
| {
/** @description Maximum container width in pixels. */
maxWidth?: number | undefined;
}
);
/**
* @description Window viewport dimensions. Represents the host window's viewport size,
* which provides additional information apps can use to make responsive layout decisions.
*/
viewport?: {
/** @description Viewport width in pixels (if fixed). Only pass width or maxWidth, not both. */
/** @description Window viewport width in pixels. */
width?: number | undefined;
/** @description Viewport height in pixels (if fixed). Only pass height or maxHeight, not both. */
/** @description Window viewport height in pixels. */
height?: number | undefined;
/** @description Maximum available viewport width in pixels (if constrained). Only pass width or maxWidth, not both.*/
maxWidth?: number | undefined;
/** @description Maximum available viewport height in pixels (if constrained). Only pass height or maxHeight, not both. */
maxHeight?: number | undefined;
};
/** @description User's language and region preference in BCP 47 format. */
locale?: string;