From 445a5836cfafe029a594e12b5c51b7e577a1ba43 Mon Sep 17 00:00:00 2001 From: martinalong Date: Sun, 14 Dec 2025 12:28:42 -0800 Subject: [PATCH] Make it styles.variables instead of styles --- .../src/mcp-app.ts | 10 +- specification/draft/apps.mdx | 19 +- src/generated/schema.json | 1046 ++++++++++------- src/generated/schema.test.ts | 6 + src/generated/schema.ts | 16 +- src/react/useHostStyles.ts | 12 +- src/spec.types.ts | 12 +- src/styles.ts | 10 +- src/types.ts | 2 + 9 files changed, 677 insertions(+), 456 deletions(-) diff --git a/examples/customer-segmentation-server/src/mcp-app.ts b/examples/customer-segmentation-server/src/mcp-app.ts index b3a4852..f17dc20 100644 --- a/examples/customer-segmentation-server/src/mcp-app.ts +++ b/examples/customer-segmentation-server/src/mcp-app.ts @@ -453,11 +453,11 @@ app.onhostcontextchanged = (params) => { if (params.theme) { applyDocumentTheme(params.theme); } - if (params.styles) { - applyHostStyles(params.styles); + if (params.styles?.variables) { + applyHostStyles(params.styles.variables); } // Recreate chart to pick up new colors - if (state.chart && (params.theme || params.styles)) { + if (state.chart && (params.theme || params.styles?.variables)) { state.chart.destroy(); state.chart = initChart(); } @@ -469,8 +469,8 @@ app.connect(new PostMessageTransport(window.parent)).then(() => { if (ctx?.theme) { applyDocumentTheme(ctx.theme); } - if (ctx?.styles) { - applyHostStyles(ctx.styles); + if (ctx?.styles?.variables) { + applyHostStyles(ctx.styles.variables); } }); diff --git a/specification/draft/apps.mdx b/specification/draft/apps.mdx index d4eb6d9..9d4ba91 100644 --- a/specification/draft/apps.mdx +++ b/specification/draft/apps.mdx @@ -404,8 +404,11 @@ interface HostContext { }; /** Current color theme preference */ theme?: "light" | "dark"; - /** CSS variables for theming. See Theming section for standardized variable names. */ - styles?: Record; + /** Style configuration for theming. See Theming section for standardized variable names. */ + styles?: { + /** CSS variables for theming */ + variables?: Record; + }; /** How the UI is currently displayed */ displayMode?: "inline" | "fullscreen" | "pip"; /** Display modes the host supports */ @@ -454,10 +457,12 @@ Example: "hostContext": { "theme": "dark", "styles": { - "--color-background-primary": "light-dark(#ffffff, #171717)", - "--color-text-primary": "light-dark(#171717, #fafafa)", - "--font-family-sans": "system-ui, sans-serif", - ... + "variables": { + "--color-background-primary": "light-dark(#ffffff, #171717)", + "--color-text-primary": "light-dark(#171717, #fafafa)", + "--font-family-sans": "system-ui, sans-serif", + ... + } }, "displayMode": "inline", "viewport": { "width": 400, "height": 300 } @@ -468,7 +473,7 @@ Example: ### Theming -Hosts can optionally pass CSS custom properties via `HostContext.styles` for visual cohesion with the host environment. +Hosts can optionally pass CSS custom properties via `HostContext.styles.variables` for visual cohesion with the host environment. #### Current Standardized Variables diff --git a/src/generated/schema.json b/src/generated/schema.json index 6a54ce0..a93fceb 100644 --- a/src/generated/schema.json +++ b/src/generated/schema.json @@ -265,166 +265,173 @@ ] }, "styles": { - "description": "CSS variables for theming the app.", + "description": "Style configuration for theming the app.", "type": "object", - "propertyNames": { - "description": "Style variables for theming MCP apps.\n\nIndividual style keys are optional - hosts may provide any subset of these values.\nValues are strings containing CSS values (colors, sizes, font stacks, etc.).\n\nNote: This type uses `Record` rather than `Partial>`\nfor compatibility with Zod schema generation. Both are functionally equivalent for validation.", - "anyOf": [ - { - "type": "string", - "const": "--color-background-primary" + "properties": { + "variables": { + "description": "CSS variables for theming the app.", + "type": "object", + "propertyNames": { + "description": "Style variables for theming MCP apps.\n\nIndividual style keys are optional - hosts may provide any subset of these values.\nValues are strings containing CSS values (colors, sizes, font stacks, etc.).\n\nNote: This type uses `Record` rather than `Partial>`\nfor compatibility with Zod schema generation. Both are functionally equivalent for validation.", + "anyOf": [ + { + "type": "string", + "const": "--color-background-primary" + }, + { + "type": "string", + "const": "--color-background-secondary" + }, + { + "type": "string", + "const": "--color-background-tertiary" + }, + { + "type": "string", + "const": "--color-background-inverted" + }, + { + "type": "string", + "const": "--color-text-primary" + }, + { + "type": "string", + "const": "--color-text-secondary" + }, + { + "type": "string", + "const": "--color-text-tertiary" + }, + { + "type": "string", + "const": "--color-text-inverted" + }, + { + "type": "string", + "const": "--color-icon-primary" + }, + { + "type": "string", + "const": "--color-icon-secondary" + }, + { + "type": "string", + "const": "--color-icon-tertiary" + }, + { + "type": "string", + "const": "--color-icon-inverted" + }, + { + "type": "string", + "const": "--color-border-primary" + }, + { + "type": "string", + "const": "--color-border-secondary" + }, + { + "type": "string", + "const": "--color-accent-info" + }, + { + "type": "string", + "const": "--color-accent-danger" + }, + { + "type": "string", + "const": "--color-accent-success" + }, + { + "type": "string", + "const": "--color-accent-warning" + }, + { + "type": "string", + "const": "--font-family-sans" + }, + { + "type": "string", + "const": "--font-size-heading" + }, + { + "type": "string", + "const": "--font-size-body" + }, + { + "type": "string", + "const": "--font-size-caption" + }, + { + "type": "string", + "const": "--font-weight-regular" + }, + { + "type": "string", + "const": "--font-weight-emphasized" + }, + { + "type": "string", + "const": "--font-leading-regular" + }, + { + "type": "string", + "const": "--font-leading-tight" + }, + { + "type": "string", + "const": "--font-style-heading" + }, + { + "type": "string", + "const": "--font-style-body" + }, + { + "type": "string", + "const": "--font-style-body-emphasized" + }, + { + "type": "string", + "const": "--font-style-caption" + }, + { + "type": "string", + "const": "--font-style-caption-emphasized" + }, + { + "type": "string", + "const": "--border-radius-small" + }, + { + "type": "string", + "const": "--border-radius-medium" + }, + { + "type": "string", + "const": "--border-radius-large" + }, + { + "type": "string", + "const": "--border-radius-full" + }, + { + "type": "string", + "const": "--border-width-regular" + } + ] }, - { - "type": "string", - "const": "--color-background-secondary" - }, - { - "type": "string", - "const": "--color-background-tertiary" - }, - { - "type": "string", - "const": "--color-background-inverted" - }, - { - "type": "string", - "const": "--color-text-primary" - }, - { - "type": "string", - "const": "--color-text-secondary" - }, - { - "type": "string", - "const": "--color-text-tertiary" - }, - { - "type": "string", - "const": "--color-text-inverted" - }, - { - "type": "string", - "const": "--color-icon-primary" - }, - { - "type": "string", - "const": "--color-icon-secondary" - }, - { - "type": "string", - "const": "--color-icon-tertiary" - }, - { - "type": "string", - "const": "--color-icon-inverted" - }, - { - "type": "string", - "const": "--color-border-primary" - }, - { - "type": "string", - "const": "--color-border-secondary" - }, - { - "type": "string", - "const": "--color-accent-info" - }, - { - "type": "string", - "const": "--color-accent-danger" - }, - { - "type": "string", - "const": "--color-accent-success" - }, - { - "type": "string", - "const": "--color-accent-warning" - }, - { - "type": "string", - "const": "--font-family-sans" - }, - { - "type": "string", - "const": "--font-size-heading" - }, - { - "type": "string", - "const": "--font-size-body" - }, - { - "type": "string", - "const": "--font-size-caption" - }, - { - "type": "string", - "const": "--font-weight-regular" - }, - { - "type": "string", - "const": "--font-weight-emphasized" - }, - { - "type": "string", - "const": "--font-leading-regular" - }, - { - "type": "string", - "const": "--font-leading-tight" - }, - { - "type": "string", - "const": "--font-style-heading" - }, - { - "type": "string", - "const": "--font-style-body" - }, - { - "type": "string", - "const": "--font-style-body-emphasized" - }, - { - "type": "string", - "const": "--font-style-caption" - }, - { - "type": "string", - "const": "--font-style-caption-emphasized" - }, - { - "type": "string", - "const": "--border-radius-small" - }, - { - "type": "string", - "const": "--border-radius-medium" - }, - { - "type": "string", - "const": "--border-radius-large" - }, - { - "type": "string", - "const": "--border-radius-full" - }, - { - "type": "string", - "const": "--border-width-regular" + "additionalProperties": { + "description": "Style variables for theming MCP apps.\n\nIndividual style keys are optional - hosts may provide any subset of these values.\nValues are strings containing CSS values (colors, sizes, font stacks, etc.).\n\nNote: This type uses `Record` rather than `Partial>`\nfor compatibility with Zod schema generation. Both are functionally equivalent for validation.", + "anyOf": [ + { + "type": "string" + }, + {} + ] } - ] + } }, - "additionalProperties": { - "description": "Style variables for theming MCP apps.\n\nIndividual style keys are optional - hosts may provide any subset of these values.\nValues are strings containing CSS values (colors, sizes, font stacks, etc.).\n\nNote: This type uses `Record` rather than `Partial>`\nfor compatibility with Zod schema generation. Both are functionally equivalent for validation.", - "anyOf": [ - { - "type": "string" - }, - {} - ] - } + "additionalProperties": false }, "displayMode": { "description": "How the UI is currently displayed.", @@ -713,6 +720,298 @@ ] }, "styles": { + "description": "Style configuration for theming the app.", + "type": "object", + "properties": { + "variables": { + "description": "CSS variables for theming the app.", + "type": "object", + "propertyNames": { + "description": "Style variables for theming MCP apps.\n\nIndividual style keys are optional - hosts may provide any subset of these values.\nValues are strings containing CSS values (colors, sizes, font stacks, etc.).\n\nNote: This type uses `Record` rather than `Partial>`\nfor compatibility with Zod schema generation. Both are functionally equivalent for validation.", + "anyOf": [ + { + "type": "string", + "const": "--color-background-primary" + }, + { + "type": "string", + "const": "--color-background-secondary" + }, + { + "type": "string", + "const": "--color-background-tertiary" + }, + { + "type": "string", + "const": "--color-background-inverted" + }, + { + "type": "string", + "const": "--color-text-primary" + }, + { + "type": "string", + "const": "--color-text-secondary" + }, + { + "type": "string", + "const": "--color-text-tertiary" + }, + { + "type": "string", + "const": "--color-text-inverted" + }, + { + "type": "string", + "const": "--color-icon-primary" + }, + { + "type": "string", + "const": "--color-icon-secondary" + }, + { + "type": "string", + "const": "--color-icon-tertiary" + }, + { + "type": "string", + "const": "--color-icon-inverted" + }, + { + "type": "string", + "const": "--color-border-primary" + }, + { + "type": "string", + "const": "--color-border-secondary" + }, + { + "type": "string", + "const": "--color-accent-info" + }, + { + "type": "string", + "const": "--color-accent-danger" + }, + { + "type": "string", + "const": "--color-accent-success" + }, + { + "type": "string", + "const": "--color-accent-warning" + }, + { + "type": "string", + "const": "--font-family-sans" + }, + { + "type": "string", + "const": "--font-size-heading" + }, + { + "type": "string", + "const": "--font-size-body" + }, + { + "type": "string", + "const": "--font-size-caption" + }, + { + "type": "string", + "const": "--font-weight-regular" + }, + { + "type": "string", + "const": "--font-weight-emphasized" + }, + { + "type": "string", + "const": "--font-leading-regular" + }, + { + "type": "string", + "const": "--font-leading-tight" + }, + { + "type": "string", + "const": "--font-style-heading" + }, + { + "type": "string", + "const": "--font-style-body" + }, + { + "type": "string", + "const": "--font-style-body-emphasized" + }, + { + "type": "string", + "const": "--font-style-caption" + }, + { + "type": "string", + "const": "--font-style-caption-emphasized" + }, + { + "type": "string", + "const": "--border-radius-small" + }, + { + "type": "string", + "const": "--border-radius-medium" + }, + { + "type": "string", + "const": "--border-radius-large" + }, + { + "type": "string", + "const": "--border-radius-full" + }, + { + "type": "string", + "const": "--border-width-regular" + } + ] + }, + "additionalProperties": { + "description": "Style variables for theming MCP apps.\n\nIndividual style keys are optional - hosts may provide any subset of these values.\nValues are strings containing CSS values (colors, sizes, font stacks, etc.).\n\nNote: This type uses `Record` rather than `Partial>`\nfor compatibility with Zod schema generation. Both are functionally equivalent for validation.", + "anyOf": [ + { + "type": "string" + }, + {} + ] + } + } + }, + "additionalProperties": false + }, + "displayMode": { + "description": "How the UI is currently displayed.", + "anyOf": [ + { + "type": "string", + "const": "inline" + }, + { + "type": "string", + "const": "fullscreen" + }, + { + "type": "string", + "const": "pip" + } + ] + }, + "availableDisplayModes": { + "description": "Display modes the host supports.", + "type": "array", + "items": { + "type": "string" + } + }, + "viewport": { + "description": "Current and maximum dimensions available to the UI.", + "type": "object", + "properties": { + "width": { + "description": "Current viewport width in pixels.", + "type": "number" + }, + "height": { + "description": "Current viewport height in pixels.", + "type": "number" + }, + "maxHeight": { + "description": "Maximum available height in pixels (if constrained).", + "type": "number" + }, + "maxWidth": { + "description": "Maximum available width in pixels (if constrained).", + "type": "number" + } + }, + "required": ["width", "height"], + "additionalProperties": false + }, + "locale": { + "description": "User's language and region preference in BCP 47 format.", + "type": "string" + }, + "timeZone": { + "description": "User's timezone in IANA format.", + "type": "string" + }, + "userAgent": { + "description": "Host application identifier.", + "type": "string" + }, + "platform": { + "description": "Platform type for responsive design decisions.", + "anyOf": [ + { + "type": "string", + "const": "web" + }, + { + "type": "string", + "const": "desktop" + }, + { + "type": "string", + "const": "mobile" + } + ] + }, + "deviceCapabilities": { + "description": "Device input capabilities.", + "type": "object", + "properties": { + "touch": { + "description": "Whether the device supports touch input.", + "type": "boolean" + }, + "hover": { + "description": "Whether the device supports hover interactions.", + "type": "boolean" + } + }, + "additionalProperties": false + }, + "safeAreaInsets": { + "description": "Mobile safe area boundaries in pixels.", + "type": "object", + "properties": { + "top": { + "description": "Top safe area inset in pixels.", + "type": "number" + }, + "right": { + "description": "Right safe area inset in pixels.", + "type": "number" + }, + "bottom": { + "description": "Bottom safe area inset in pixels.", + "type": "number" + }, + "left": { + "description": "Left safe area inset in pixels.", + "type": "number" + } + }, + "required": ["top", "right", "bottom", "left"], + "additionalProperties": false + } + }, + "additionalProperties": false + }, + "McpUiHostStyles": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "variables": { "description": "CSS variables for theming the app.", "type": "object", "propertyNames": { @@ -873,122 +1172,6 @@ {} ] } - }, - "displayMode": { - "description": "How the UI is currently displayed.", - "anyOf": [ - { - "type": "string", - "const": "inline" - }, - { - "type": "string", - "const": "fullscreen" - }, - { - "type": "string", - "const": "pip" - } - ] - }, - "availableDisplayModes": { - "description": "Display modes the host supports.", - "type": "array", - "items": { - "type": "string" - } - }, - "viewport": { - "description": "Current and maximum dimensions available to the UI.", - "type": "object", - "properties": { - "width": { - "description": "Current viewport width in pixels.", - "type": "number" - }, - "height": { - "description": "Current viewport height in pixels.", - "type": "number" - }, - "maxHeight": { - "description": "Maximum available height in pixels (if constrained).", - "type": "number" - }, - "maxWidth": { - "description": "Maximum available width in pixels (if constrained).", - "type": "number" - } - }, - "required": ["width", "height"], - "additionalProperties": false - }, - "locale": { - "description": "User's language and region preference in BCP 47 format.", - "type": "string" - }, - "timeZone": { - "description": "User's timezone in IANA format.", - "type": "string" - }, - "userAgent": { - "description": "Host application identifier.", - "type": "string" - }, - "platform": { - "description": "Platform type for responsive design decisions.", - "anyOf": [ - { - "type": "string", - "const": "web" - }, - { - "type": "string", - "const": "desktop" - }, - { - "type": "string", - "const": "mobile" - } - ] - }, - "deviceCapabilities": { - "description": "Device input capabilities.", - "type": "object", - "properties": { - "touch": { - "description": "Whether the device supports touch input.", - "type": "boolean" - }, - "hover": { - "description": "Whether the device supports hover interactions.", - "type": "boolean" - } - }, - "additionalProperties": false - }, - "safeAreaInsets": { - "description": "Mobile safe area boundaries in pixels.", - "type": "object", - "properties": { - "top": { - "description": "Top safe area inset in pixels.", - "type": "number" - }, - "right": { - "description": "Right safe area inset in pixels.", - "type": "number" - }, - "bottom": { - "description": "Bottom safe area inset in pixels.", - "type": "number" - }, - "left": { - "description": "Left safe area inset in pixels.", - "type": "number" - } - }, - "required": ["top", "right", "bottom", "left"], - "additionalProperties": false } }, "additionalProperties": {} @@ -1343,166 +1526,173 @@ ] }, "styles": { - "description": "CSS variables for theming the app.", + "description": "Style configuration for theming the app.", "type": "object", - "propertyNames": { - "description": "Style variables for theming MCP apps.\n\nIndividual style keys are optional - hosts may provide any subset of these values.\nValues are strings containing CSS values (colors, sizes, font stacks, etc.).\n\nNote: This type uses `Record` rather than `Partial>`\nfor compatibility with Zod schema generation. Both are functionally equivalent for validation.", - "anyOf": [ - { - "type": "string", - "const": "--color-background-primary" + "properties": { + "variables": { + "description": "CSS variables for theming the app.", + "type": "object", + "propertyNames": { + "description": "Style variables for theming MCP apps.\n\nIndividual style keys are optional - hosts may provide any subset of these values.\nValues are strings containing CSS values (colors, sizes, font stacks, etc.).\n\nNote: This type uses `Record` rather than `Partial>`\nfor compatibility with Zod schema generation. Both are functionally equivalent for validation.", + "anyOf": [ + { + "type": "string", + "const": "--color-background-primary" + }, + { + "type": "string", + "const": "--color-background-secondary" + }, + { + "type": "string", + "const": "--color-background-tertiary" + }, + { + "type": "string", + "const": "--color-background-inverted" + }, + { + "type": "string", + "const": "--color-text-primary" + }, + { + "type": "string", + "const": "--color-text-secondary" + }, + { + "type": "string", + "const": "--color-text-tertiary" + }, + { + "type": "string", + "const": "--color-text-inverted" + }, + { + "type": "string", + "const": "--color-icon-primary" + }, + { + "type": "string", + "const": "--color-icon-secondary" + }, + { + "type": "string", + "const": "--color-icon-tertiary" + }, + { + "type": "string", + "const": "--color-icon-inverted" + }, + { + "type": "string", + "const": "--color-border-primary" + }, + { + "type": "string", + "const": "--color-border-secondary" + }, + { + "type": "string", + "const": "--color-accent-info" + }, + { + "type": "string", + "const": "--color-accent-danger" + }, + { + "type": "string", + "const": "--color-accent-success" + }, + { + "type": "string", + "const": "--color-accent-warning" + }, + { + "type": "string", + "const": "--font-family-sans" + }, + { + "type": "string", + "const": "--font-size-heading" + }, + { + "type": "string", + "const": "--font-size-body" + }, + { + "type": "string", + "const": "--font-size-caption" + }, + { + "type": "string", + "const": "--font-weight-regular" + }, + { + "type": "string", + "const": "--font-weight-emphasized" + }, + { + "type": "string", + "const": "--font-leading-regular" + }, + { + "type": "string", + "const": "--font-leading-tight" + }, + { + "type": "string", + "const": "--font-style-heading" + }, + { + "type": "string", + "const": "--font-style-body" + }, + { + "type": "string", + "const": "--font-style-body-emphasized" + }, + { + "type": "string", + "const": "--font-style-caption" + }, + { + "type": "string", + "const": "--font-style-caption-emphasized" + }, + { + "type": "string", + "const": "--border-radius-small" + }, + { + "type": "string", + "const": "--border-radius-medium" + }, + { + "type": "string", + "const": "--border-radius-large" + }, + { + "type": "string", + "const": "--border-radius-full" + }, + { + "type": "string", + "const": "--border-width-regular" + } + ] }, - { - "type": "string", - "const": "--color-background-secondary" - }, - { - "type": "string", - "const": "--color-background-tertiary" - }, - { - "type": "string", - "const": "--color-background-inverted" - }, - { - "type": "string", - "const": "--color-text-primary" - }, - { - "type": "string", - "const": "--color-text-secondary" - }, - { - "type": "string", - "const": "--color-text-tertiary" - }, - { - "type": "string", - "const": "--color-text-inverted" - }, - { - "type": "string", - "const": "--color-icon-primary" - }, - { - "type": "string", - "const": "--color-icon-secondary" - }, - { - "type": "string", - "const": "--color-icon-tertiary" - }, - { - "type": "string", - "const": "--color-icon-inverted" - }, - { - "type": "string", - "const": "--color-border-primary" - }, - { - "type": "string", - "const": "--color-border-secondary" - }, - { - "type": "string", - "const": "--color-accent-info" - }, - { - "type": "string", - "const": "--color-accent-danger" - }, - { - "type": "string", - "const": "--color-accent-success" - }, - { - "type": "string", - "const": "--color-accent-warning" - }, - { - "type": "string", - "const": "--font-family-sans" - }, - { - "type": "string", - "const": "--font-size-heading" - }, - { - "type": "string", - "const": "--font-size-body" - }, - { - "type": "string", - "const": "--font-size-caption" - }, - { - "type": "string", - "const": "--font-weight-regular" - }, - { - "type": "string", - "const": "--font-weight-emphasized" - }, - { - "type": "string", - "const": "--font-leading-regular" - }, - { - "type": "string", - "const": "--font-leading-tight" - }, - { - "type": "string", - "const": "--font-style-heading" - }, - { - "type": "string", - "const": "--font-style-body" - }, - { - "type": "string", - "const": "--font-style-body-emphasized" - }, - { - "type": "string", - "const": "--font-style-caption" - }, - { - "type": "string", - "const": "--font-style-caption-emphasized" - }, - { - "type": "string", - "const": "--border-radius-small" - }, - { - "type": "string", - "const": "--border-radius-medium" - }, - { - "type": "string", - "const": "--border-radius-large" - }, - { - "type": "string", - "const": "--border-radius-full" - }, - { - "type": "string", - "const": "--border-width-regular" + "additionalProperties": { + "description": "Style variables for theming MCP apps.\n\nIndividual style keys are optional - hosts may provide any subset of these values.\nValues are strings containing CSS values (colors, sizes, font stacks, etc.).\n\nNote: This type uses `Record` rather than `Partial>`\nfor compatibility with Zod schema generation. Both are functionally equivalent for validation.", + "anyOf": [ + { + "type": "string" + }, + {} + ] } - ] + } }, - "additionalProperties": { - "description": "Style variables for theming MCP apps.\n\nIndividual style keys are optional - hosts may provide any subset of these values.\nValues are strings containing CSS values (colors, sizes, font stacks, etc.).\n\nNote: This type uses `Record` rather than `Partial>`\nfor compatibility with Zod schema generation. Both are functionally equivalent for validation.", - "anyOf": [ - { - "type": "string" - }, - {} - ] - } + "additionalProperties": false }, "displayMode": { "description": "How the UI is currently displayed.", diff --git a/src/generated/schema.test.ts b/src/generated/schema.test.ts index 3576dc2..47f2a37 100644 --- a/src/generated/schema.test.ts +++ b/src/generated/schema.test.ts @@ -63,6 +63,10 @@ export type McpUiToolCancelledNotificationSchemaInferredType = z.infer< typeof generated.McpUiToolCancelledNotificationSchema >; +export type McpUiHostStylesSchemaInferredType = z.infer< + typeof generated.McpUiHostStylesSchema +>; + export type McpUiResourceTeardownRequestSchemaInferredType = z.infer< typeof generated.McpUiResourceTeardownRequestSchema >; @@ -177,6 +181,8 @@ expectType( expectType( {} as spec.McpUiToolCancelledNotification, ); +expectType({} as McpUiHostStylesSchemaInferredType); +expectType({} as spec.McpUiHostStyles); expectType( {} as McpUiResourceTeardownRequestSchemaInferredType, ); diff --git a/src/generated/schema.ts b/src/generated/schema.ts index 07b82cf..6a571ec 100644 --- a/src/generated/schema.ts +++ b/src/generated/schema.ts @@ -246,6 +246,16 @@ export const McpUiToolCancelledNotificationSchema = z.object({ }), }); +/** + * @description Style configuration for theming MCP apps. + */ +export const McpUiHostStylesSchema = z.object({ + /** @description CSS variables for theming the app. */ + variables: McpUiStylesSchema.optional().describe( + "CSS variables for theming the app.", + ), +}); + /** * @description Request for graceful shutdown of the Guest UI (Host -> Guest UI). * @see {@link app-bridge.AppBridge.sendResourceTeardown} for the host method that sends this @@ -422,9 +432,9 @@ export const McpUiHostContextSchema = z.looseObject({ theme: McpUiThemeSchema.optional().describe( "Current color theme preference.", ), - /** @description CSS variables for theming the app. */ - styles: McpUiStylesSchema.optional().describe( - "CSS variables for theming the app.", + /** @description Style configuration for theming the app. */ + styles: McpUiHostStylesSchema.optional().describe( + "Style configuration for theming the app.", ), /** @description How the UI is currently displayed. */ displayMode: McpUiDisplayModeSchema.optional().describe( diff --git a/src/react/useHostStyles.ts b/src/react/useHostStyles.ts index 0a2e32c..a259542 100644 --- a/src/react/useHostStyles.ts +++ b/src/react/useHostStyles.ts @@ -7,7 +7,7 @@ import { McpUiHostContext } from "../types"; * React hook that applies host styles and theme as CSS custom properties. * * This hook listens to host context changes and automatically applies: - * - `styles` CSS variables to `document.documentElement` (e.g., `--color-background-primary`) + * - `styles.variables` CSS variables to `document.documentElement` (e.g., `--color-background-primary`) * - `theme` via `color-scheme` CSS property, enabling `light-dark()` CSS function support * * The hook also applies styles and theme from the initial host context when @@ -70,10 +70,10 @@ export function useHostStyles( if (initialContext?.theme) { applyDocumentTheme(initialContext.theme); } - if (initialContext?.styles) { - applyHostStyles(initialContext.styles); + if (initialContext?.styles?.variables) { + applyHostStyles(initialContext.styles.variables); } - if (initialContext?.theme || initialContext?.styles) { + if (initialContext?.theme || initialContext?.styles?.variables) { initialApplied.current = true; } }, [initialContext]); @@ -88,8 +88,8 @@ export function useHostStyles( if (params.theme) { applyDocumentTheme(params.theme); } - if (params.styles) { - applyHostStyles(params.styles); + if (params.styles?.variables) { + applyHostStyles(params.styles.variables); } }; }, [app]); diff --git a/src/spec.types.ts b/src/spec.types.ts index 5450f79..7d14291 100644 --- a/src/spec.types.ts +++ b/src/spec.types.ts @@ -244,6 +244,14 @@ export interface McpUiToolCancelledNotification { }; } +/** + * @description Style configuration for theming MCP apps. + */ +export interface McpUiHostStyles { + /** @description CSS variables for theming the app. */ + variables?: McpUiStyles; +} + /** * @description Rich context about the host environment provided to Guest UIs. */ @@ -259,8 +267,8 @@ export interface McpUiHostContext { }; /** @description Current color theme preference. */ theme?: McpUiTheme; - /** @description CSS variables for theming the app. */ - styles?: McpUiStyles; + /** @description Style configuration for theming the app. */ + styles?: McpUiHostStyles; /** @description How the UI is currently displayed. */ displayMode?: McpUiDisplayMode; /** @description Display modes the host supports. */ diff --git a/src/styles.ts b/src/styles.ts index 45957be..882f6c5 100644 --- a/src/styles.ts +++ b/src/styles.ts @@ -76,12 +76,12 @@ export function applyDocumentTheme(theme: McpUiTheme): void { /** * Apply host styles as CSS custom properties on an element. * - * This function takes the `styles` object from `McpUiHostContext` and sets + * This function takes the `variables` object from `McpUiHostContext.styles` and sets * each CSS variable on the specified root element (defaults to `document.documentElement`). * This allows apps to use the host's theming values via CSS variables like * `var(--color-background-primary)`. * - * @param styles - The styles object from `McpUiHostContext.styles` + * @param styles - The styles object from `McpUiHostContext.styles.variables` * @param root - The element to apply styles to (defaults to `document.documentElement`) * * @example Apply styles from host context @@ -89,8 +89,8 @@ export function applyDocumentTheme(theme: McpUiTheme): void { * import { applyHostStyles } from '@modelcontextprotocol/ext-apps'; * * app.onhostcontextchanged = (params) => { - * if (params.styles) { - * applyHostStyles(params.styles); + * if (params.styles?.variables) { + * applyHostStyles(params.styles.variables); * } * }; * ``` @@ -98,7 +98,7 @@ export function applyDocumentTheme(theme: McpUiTheme): void { * @example Apply to a specific element * ```typescript * const container = document.getElementById('app-root'); - * applyHostStyles(hostContext.styles, container); + * applyHostStyles(hostContext.styles?.variables, container); * ``` * * @see {@link McpUiStyles} for the available CSS variables diff --git a/src/types.ts b/src/types.ts index fa87085..ac6eb1b 100644 --- a/src/types.ts +++ b/src/types.ts @@ -16,6 +16,7 @@ export { type McpUiDisplayMode, type McpUiStyleVariableKey, type McpUiStyles, + type McpUiHostStyles, type McpUiOpenLinkRequest, type McpUiOpenLinkResult, type McpUiMessageRequest, @@ -65,6 +66,7 @@ import type { export { McpUiThemeSchema, McpUiDisplayModeSchema, + McpUiHostStylesSchema, McpUiOpenLinkRequestSchema, McpUiOpenLinkResultSchema, McpUiMessageRequestSchema,