build: auto-generate Zod schemas from TypeScript types using ts-to-zod (#116)

* feat: add ts-to-zod schema generation from spec.types.ts

Add automated Zod schema generation using ts-to-zod:
- New npm script `generate:schemas` to regenerate schemas from spec.types.ts
- Post-processing script for Zod v4 compatibility and MCP SDK type imports
- src/spec.types.ts: Pure TypeScript interface definitions (source of truth)
- src/schemas.generated.ts: Auto-generated Zod schemas
- src/schemas.ts: Re-exports with PascalCase naming for backwards compatibility

The generated schemas provide a simpler alternative to the hand-written schemas
in types.ts, useful for lighter validation or reference implementations.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* refactor: types.ts now re-exports from spec.types.ts and schemas.generated.ts

- types.ts imports and re-exports types from spec.types.ts (source of truth)
- types.ts imports and re-exports schemas from schemas.generated.ts
- Added compile-time VerifySchemaMatches checks to flag mismatches between
  interfaces and generated schemas
- Removed redundant schemas.ts (types.ts now handles all re-exports)
- Applied prettier formatting to schemas.generated.ts

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* refactor: use ts-to-zod as library with integration tests

Replace CLI-based schema generation with library API:

- scripts/generate-schemas.ts: New script using ts-to-zod library API
  - Detailed documentation of why each post-processing step is needed
  - Generates integration tests via getIntegrationTestFile()
  - Better error reporting and control

- src/schemas.generated.test.ts: Auto-generated integration tests
  - Compile-time verification that schemas match TypeScript types
  - Uses expectType<> pattern for bidirectional type checking

- Removed scripts/postprocess-schemas.ts (merged into generate-schemas.ts)

- Fixed McpUiResourceTeardownResult to include index signature for MCP SDK
  Protocol compatibility (detected by the new integration tests)

Post-processing rationale (documented in generate-schemas.ts):
1. zod → zod/v4: Explicit v4 subpath for version clarity
2. z.any() → MCP SDK schemas: External types need real validation
3. z.record().and() → z.looseObject(): Index signatures need Zod v4 idiom

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* feat: regenerate schemas during build + CI check

- build script now runs generate:schemas before bun build
- CI workflow verifies generated schemas are up-to-date
  (fails if spec.types.ts changed but schemas weren't regenerated)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* refactor: generate PascalCase schemas, move verification to tests

- Add getSchemaName option to generate PascalCase schema names directly
  (McpUiOpenLinkRequestSchema instead of mcpUiOpenLinkRequestSchema)
- Remove redundant compile-time verification from types.ts
  (already handled by generated test file's bidirectional expectType checks)
- Simplify types.ts from 235 to 57 lines (just re-exports)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* fix: add missing csp field, simplify schema generation

- Add missing csp field to McpUiSandboxResourceReadyNotification (regression from merge)
- Simplify generate-schemas.ts - remove complex .describe() conversion
- Document that ts-to-zod supports @description JSDoc tags for .describe()
- Improve z.looseObject() replacement with brace-counting for nested objects

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* feat: add @description JSDoc tags for .describe() on schemas

Added @description tags to all interfaces and fields in spec.types.ts.
ts-to-zod now generates .describe() calls on all schema fields, making
descriptions available at runtime for introspection and JSON Schema generation.

Example:
  /** @description URL to open in the host's browser */
  url: string;

Generates:
  url: z.string().describe("URL to open in the host's browser")

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* feat: generate JSON Schema from Zod schemas

Added JSON Schema generation to the schema generation pipeline:
- Uses Zod v4's built-in toJSONSchema() to convert schemas
- Generates src/schema.json with all message types in $defs
- Includes descriptions from @description JSDoc tags
- Added export "./schema.json" to package.json
- Updated CI to verify schema.json is up-to-date

Use cases:
- Language-agnostic validation (Python, Go, Rust)
- Documentation generation (Redoc, Swagger UI)
- Code generation (quicktype)
- IDE autocomplete for JSON/YAML

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* refactor: rename schema.json to schema.generated.json

Renamed to match other generated files naming convention.
Export path unchanged: ./schema.json -> ./dist/src/schema.generated.json

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* refactor: rename schemas.generated.* to schema.generated.* (singular)

Consistent singular naming for all generated files:
- schema.generated.ts
- schema.generated.test.ts
- schema.generated.json

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* refactor: rename schemas.generated.* to schema.generated.* (singular)

Consistent singular naming for all generated files:
- schema.generated.ts
- schema.generated.test.ts
- schema.generated.json

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* refactor: move generated files to src/generated/ with simpler names

Moved:
- src/schema.generated.ts → src/generated/schema.ts
- src/schema.generated.test.ts → src/generated/schema.test.ts
- src/schema.generated.json → src/generated/schema.json

Benefits:
- Cleaner organization (generated files isolated)
- Simpler names without .generated suffix
- Easier to gitignore if desired later

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* fix: add missing type exports for backwards compatibility

Added missing exports that existed in main:
- McpUiTheme, McpUiThemeSchema
- McpUiDisplayMode, McpUiDisplayModeSchema
- McpUiResourceCsp, McpUiResourceCspSchema
- McpUiResourceMeta, McpUiResourceMetaSchema

Also fixed theme/displayMode to match original values:
- theme: 'light' | 'dark' (removed 'system')
- displayMode: 'inline' | 'fullscreen' | 'pip' (removed 'carousel')

Verified: all 47 exports from main are preserved with compatible types.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* fix: stage generated files after prettier in pre-commit hook

The pre-commit hook runs build (which generates schemas) then prettier
(which formats them). The formatted generated files weren't being staged,
causing CI to fail with 'Generated schemas are out of date'.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* update generated files

* fix: run prettier after regenerating schemas in CI verification

CI was comparing unformatted generated output against formatted committed
files, causing false 'out of date' failures.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
Olivier Chafik 2025-12-10 01:05:49 +01:00 • committed by GitHub
commit f06340b6d9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 5346 additions and 1043 deletions

2151
src/generated/schema.json Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,219 @@
// Generated by ts-to-zod
// Integration tests verifying schemas match TypeScript types
// Run: npm run generate:schemas
import { z } from "zod/v4";
import * as spec from "../spec.types.js";
import * as generated from "./schema.js";
// eslint-disable-next-line @typescript-eslint/no-unused-vars
function expectType<T>(_: T) {
/* noop */
}
export type McpUiThemeSchemaInferredType = z.infer<
typeof generated.McpUiThemeSchema
>;
export type McpUiDisplayModeSchemaInferredType = z.infer<
typeof generated.McpUiDisplayModeSchema
>;
export type McpUiOpenLinkRequestSchemaInferredType = z.infer<
typeof generated.McpUiOpenLinkRequestSchema
>;
export type McpUiOpenLinkResultSchemaInferredType = z.infer<
typeof generated.McpUiOpenLinkResultSchema
>;
export type McpUiMessageResultSchemaInferredType = z.infer<
typeof generated.McpUiMessageResultSchema
>;
export type McpUiSandboxProxyReadyNotificationSchemaInferredType = z.infer<
typeof generated.McpUiSandboxProxyReadyNotificationSchema
>;
export type McpUiSandboxResourceReadyNotificationSchemaInferredType = z.infer<
typeof generated.McpUiSandboxResourceReadyNotificationSchema
>;
export type McpUiSizeChangedNotificationSchemaInferredType = z.infer<
typeof generated.McpUiSizeChangedNotificationSchema
>;
export type McpUiToolInputNotificationSchemaInferredType = z.infer<
typeof generated.McpUiToolInputNotificationSchema
>;
export type McpUiToolInputPartialNotificationSchemaInferredType = z.infer<
typeof generated.McpUiToolInputPartialNotificationSchema
>;
export type McpUiResourceTeardownRequestSchemaInferredType = z.infer<
typeof generated.McpUiResourceTeardownRequestSchema
>;
export type McpUiResourceTeardownResultSchemaInferredType = z.infer<
typeof generated.McpUiResourceTeardownResultSchema
>;
export type McpUiHostCapabilitiesSchemaInferredType = z.infer<
typeof generated.McpUiHostCapabilitiesSchema
>;
export type McpUiAppCapabilitiesSchemaInferredType = z.infer<
typeof generated.McpUiAppCapabilitiesSchema
>;
export type McpUiInitializedNotificationSchemaInferredType = z.infer<
typeof generated.McpUiInitializedNotificationSchema
>;
export type McpUiResourceCspSchemaInferredType = z.infer<
typeof generated.McpUiResourceCspSchema
>;
export type McpUiResourceMetaSchemaInferredType = z.infer<
typeof generated.McpUiResourceMetaSchema
>;
export type McpUiMessageRequestSchemaInferredType = z.infer<
typeof generated.McpUiMessageRequestSchema
>;
export type McpUiToolResultNotificationSchemaInferredType = z.infer<
typeof generated.McpUiToolResultNotificationSchema
>;
export type McpUiHostContextSchemaInferredType = z.infer<
typeof generated.McpUiHostContextSchema
>;
export type McpUiHostContextChangedNotificationSchemaInferredType = z.infer<
typeof generated.McpUiHostContextChangedNotificationSchema
>;
export type McpUiInitializeRequestSchemaInferredType = z.infer<
typeof generated.McpUiInitializeRequestSchema
>;
export type McpUiInitializeResultSchemaInferredType = z.infer<
typeof generated.McpUiInitializeResultSchema
>;
expectType<spec.McpUiTheme>({} as McpUiThemeSchemaInferredType);
expectType<McpUiThemeSchemaInferredType>({} as spec.McpUiTheme);
expectType<spec.McpUiDisplayMode>({} as McpUiDisplayModeSchemaInferredType);
expectType<McpUiDisplayModeSchemaInferredType>({} as spec.McpUiDisplayMode);
expectType<spec.McpUiOpenLinkRequest>(
{} as McpUiOpenLinkRequestSchemaInferredType,
);
expectType<McpUiOpenLinkRequestSchemaInferredType>(
{} as spec.McpUiOpenLinkRequest,
);
expectType<spec.McpUiOpenLinkResult>(
{} as McpUiOpenLinkResultSchemaInferredType,
);
expectType<McpUiOpenLinkResultSchemaInferredType>(
{} as spec.McpUiOpenLinkResult,
);
expectType<spec.McpUiMessageResult>({} as McpUiMessageResultSchemaInferredType);
expectType<McpUiMessageResultSchemaInferredType>({} as spec.McpUiMessageResult);
expectType<spec.McpUiSandboxProxyReadyNotification>(
{} as McpUiSandboxProxyReadyNotificationSchemaInferredType,
);
expectType<McpUiSandboxProxyReadyNotificationSchemaInferredType>(
{} as spec.McpUiSandboxProxyReadyNotification,
);
expectType<spec.McpUiSandboxResourceReadyNotification>(
{} as McpUiSandboxResourceReadyNotificationSchemaInferredType,
);
expectType<McpUiSandboxResourceReadyNotificationSchemaInferredType>(
{} as spec.McpUiSandboxResourceReadyNotification,
);
expectType<spec.McpUiSizeChangedNotification>(
{} as McpUiSizeChangedNotificationSchemaInferredType,
);
expectType<McpUiSizeChangedNotificationSchemaInferredType>(
{} as spec.McpUiSizeChangedNotification,
);
expectType<spec.McpUiToolInputNotification>(
{} as McpUiToolInputNotificationSchemaInferredType,
);
expectType<McpUiToolInputNotificationSchemaInferredType>(
{} as spec.McpUiToolInputNotification,
);
expectType<spec.McpUiToolInputPartialNotification>(
{} as McpUiToolInputPartialNotificationSchemaInferredType,
);
expectType<McpUiToolInputPartialNotificationSchemaInferredType>(
{} as spec.McpUiToolInputPartialNotification,
);
expectType<spec.McpUiResourceTeardownRequest>(
{} as McpUiResourceTeardownRequestSchemaInferredType,
);
expectType<McpUiResourceTeardownRequestSchemaInferredType>(
{} as spec.McpUiResourceTeardownRequest,
);
expectType<spec.McpUiResourceTeardownResult>(
{} as McpUiResourceTeardownResultSchemaInferredType,
);
expectType<McpUiResourceTeardownResultSchemaInferredType>(
{} as spec.McpUiResourceTeardownResult,
);
expectType<spec.McpUiHostCapabilities>(
{} as McpUiHostCapabilitiesSchemaInferredType,
);
expectType<McpUiHostCapabilitiesSchemaInferredType>(
{} as spec.McpUiHostCapabilities,
);
expectType<spec.McpUiAppCapabilities>(
{} as McpUiAppCapabilitiesSchemaInferredType,
);
expectType<McpUiAppCapabilitiesSchemaInferredType>(
{} as spec.McpUiAppCapabilities,
);
expectType<spec.McpUiInitializedNotification>(
{} as McpUiInitializedNotificationSchemaInferredType,
);
expectType<McpUiInitializedNotificationSchemaInferredType>(
{} as spec.McpUiInitializedNotification,
);
expectType<spec.McpUiResourceCsp>({} as McpUiResourceCspSchemaInferredType);
expectType<McpUiResourceCspSchemaInferredType>({} as spec.McpUiResourceCsp);
expectType<spec.McpUiResourceMeta>({} as McpUiResourceMetaSchemaInferredType);
expectType<McpUiResourceMetaSchemaInferredType>({} as spec.McpUiResourceMeta);
expectType<spec.McpUiMessageRequest>(
{} as McpUiMessageRequestSchemaInferredType,
);
expectType<McpUiMessageRequestSchemaInferredType>(
{} as spec.McpUiMessageRequest,
);
expectType<spec.McpUiToolResultNotification>(
{} as McpUiToolResultNotificationSchemaInferredType,
);
expectType<McpUiToolResultNotificationSchemaInferredType>(
{} as spec.McpUiToolResultNotification,
);
expectType<spec.McpUiHostContext>({} as McpUiHostContextSchemaInferredType);
expectType<McpUiHostContextSchemaInferredType>({} as spec.McpUiHostContext);
expectType<spec.McpUiHostContextChangedNotification>(
{} as McpUiHostContextChangedNotificationSchemaInferredType,
);
expectType<McpUiHostContextChangedNotificationSchemaInferredType>(
{} as spec.McpUiHostContextChangedNotification,
);
expectType<spec.McpUiInitializeRequest>(
{} as McpUiInitializeRequestSchemaInferredType,
);
expectType<McpUiInitializeRequestSchemaInferredType>(
{} as spec.McpUiInitializeRequest,
);
expectType<spec.McpUiInitializeResult>(
{} as McpUiInitializeResultSchemaInferredType,
);
expectType<McpUiInitializeResultSchemaInferredType>(
{} as spec.McpUiInitializeResult,
);

467
src/generated/schema.ts Normal file
View file

@ -0,0 +1,467 @@
// Generated by ts-to-zod
// Post-processed for Zod v4 and MCP SDK compatibility
// Run: npm run generate:schemas
import { z } from "zod/v4";
import {
ContentBlockSchema,
CallToolResultSchema,
ImplementationSchema,
RequestIdSchema,
ToolSchema,
} from "@modelcontextprotocol/sdk/types.js";
/**
* @description Color theme preference for the host environment.
*/
export const McpUiThemeSchema = z
.union([z.literal("light"), z.literal("dark")])
.describe("Color theme preference for the host environment.");
/**
* @description Display mode for UI presentation.
*/
export const McpUiDisplayModeSchema = z
.union([z.literal("inline"), z.literal("fullscreen"), z.literal("pip")])
.describe("Display mode for UI presentation.");
/**
* @description Request to open an external URL in the host's default browser.
* @see {@link app.App.sendOpenLink} for the method that sends this request
*/
export const McpUiOpenLinkRequestSchema = z.object({
method: z.literal("ui/open-link"),
params: z.object({
/** @description URL to open in the host's browser */
url: z.string().describe("URL to open in the host's browser"),
}),
});
/**
* @description Result from opening a URL.
* @see {@link McpUiOpenLinkRequest}
*/
export const McpUiOpenLinkResultSchema = z.looseObject({
/** @description True if the host failed to open the URL (e.g., due to security policy). */
isError: z
.boolean()
.optional()
.describe(
"True if the host failed to open the URL (e.g., due to security policy).",
),
});
/**
* @description Result from sending a message.
* @see {@link McpUiMessageRequest}
*/
export const McpUiMessageResultSchema = z.looseObject({
/** @description True if the host rejected or failed to deliver the message. */
isError: z
.boolean()
.optional()
.describe("True if the host rejected or failed to deliver the message."),
});
/**
* @description Notification that the sandbox proxy iframe is ready to receive content.
* @internal
* @see https://github.com/modelcontextprotocol/ext-apps/blob/main/specification/draft/apps.mdx#sandbox-proxy
*/
export const McpUiSandboxProxyReadyNotificationSchema = z.object({
method: z.literal("ui/notifications/sandbox-proxy-ready"),
params: z.object({}),
});
/**
* @description Notification containing HTML resource for the sandbox proxy to load.
* @internal
* @see https://github.com/modelcontextprotocol/ext-apps/blob/main/specification/draft/apps.mdx#sandbox-proxy
*/
export const McpUiSandboxResourceReadyNotificationSchema = z.object({
method: z.literal("ui/notifications/sandbox-resource-ready"),
params: z.object({
/** @description HTML content to load into the inner iframe. */
html: z.string().describe("HTML content to load into the inner iframe."),
/** @description Optional override for the inner iframe's sandbox attribute. */
sandbox: z
.string()
.optional()
.describe("Optional override for the inner iframe's sandbox attribute."),
/** @description CSP configuration from resource metadata. */
csp: z
.object({
/** @description Origins for network requests (fetch/XHR/WebSocket). */
connectDomains: z
.array(z.string())
.optional()
.describe("Origins for network requests (fetch/XHR/WebSocket)."),
/** @description Origins for static resources (scripts, images, styles, fonts). */
resourceDomains: z
.array(z.string())
.optional()
.describe(
"Origins for static resources (scripts, images, styles, fonts).",
),
})
.optional()
.describe("CSP configuration from resource metadata."),
}),
});
/**
* @description Notification of UI size changes (bidirectional: Guest <-> Host).
* @see {@link app.App.sendSizeChanged} for the method to send this from Guest UI
*/
export const McpUiSizeChangedNotificationSchema = z.object({
method: z.literal("ui/notifications/size-changed"),
params: z.object({
/** @description New width in pixels. */
width: z.number().optional().describe("New width in pixels."),
/** @description New height in pixels. */
height: z.number().optional().describe("New height in pixels."),
}),
});
/**
* @description Notification containing complete tool arguments (Host -> Guest UI).
*/
export const McpUiToolInputNotificationSchema = z.object({
method: z.literal("ui/notifications/tool-input"),
params: z.object({
/** @description Complete tool call arguments as key-value pairs. */
arguments: z
.record(
z.string(),
z
.unknown()
.describe("Complete tool call arguments as key-value pairs."),
)
.optional()
.describe("Complete tool call arguments as key-value pairs."),
}),
});
/**
* @description Notification containing partial/streaming tool arguments (Host -> Guest UI).
*/
export const McpUiToolInputPartialNotificationSchema = z.object({
method: z.literal("ui/notifications/tool-input-partial"),
params: z.object({
/** @description Partial tool call arguments (incomplete, may change). */
arguments: z
.record(
z.string(),
z
.unknown()
.describe("Partial tool call arguments (incomplete, may change)."),
)
.optional()
.describe("Partial tool call arguments (incomplete, may change)."),
}),
});
/**
* @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
*/
export const McpUiResourceTeardownRequestSchema = z.object({
method: z.literal("ui/resource-teardown"),
params: z.object({}),
});
/**
* @description Result from graceful shutdown request.
* @see {@link McpUiResourceTeardownRequest}
*/
export const McpUiResourceTeardownResultSchema = z.record(
z.string(),
z.unknown(),
);
/**
* @description Capabilities supported by the host application.
* @see {@link McpUiInitializeResult} for the initialization result that includes these capabilities
*/
export const McpUiHostCapabilitiesSchema = z.object({
/** @description Experimental features (structure TBD). */
experimental: z
.object({})
.optional()
.describe("Experimental features (structure TBD)."),
/** @description Host supports opening external URLs. */
openLinks: z
.object({})
.optional()
.describe("Host supports opening external URLs."),
/** @description Host can proxy tool calls to the MCP server. */
serverTools: z
.object({
/** @description Host supports tools/list_changed notifications. */
listChanged: z
.boolean()
.optional()
.describe("Host supports tools/list_changed notifications."),
})
.optional()
.describe("Host can proxy tool calls to the MCP server."),
/** @description Host can proxy resource reads to the MCP server. */
serverResources: z
.object({
/** @description Host supports resources/list_changed notifications. */
listChanged: z
.boolean()
.optional()
.describe("Host supports resources/list_changed notifications."),
})
.optional()
.describe("Host can proxy resource reads to the MCP server."),
/** @description Host accepts log messages. */
logging: z.object({}).optional().describe("Host accepts log messages."),
});
/**
* @description Capabilities provided by the Guest UI (App).
* @see {@link McpUiInitializeRequest} for the initialization request that includes these capabilities
*/
export const McpUiAppCapabilitiesSchema = z.object({
/** @description Experimental features (structure TBD). */
experimental: z
.object({})
.optional()
.describe("Experimental features (structure TBD)."),
/** @description App exposes MCP-style tools that the host can call. */
tools: z
.object({
/** @description App supports tools/list_changed notifications. */
listChanged: z
.boolean()
.optional()
.describe("App supports tools/list_changed notifications."),
})
.optional()
.describe("App exposes MCP-style tools that the host can call."),
});
/**
* @description Notification that Guest UI has completed initialization (Guest UI -> Host).
* @see {@link app.App.connect} for the method that sends this notification
*/
export const McpUiInitializedNotificationSchema = z.object({
method: z.literal("ui/notifications/initialized"),
params: z.object({}).optional(),
});
/**
* @description Content Security Policy configuration for UI resources.
*/
export const McpUiResourceCspSchema = z.object({
/** @description Origins for network requests (fetch/XHR/WebSocket). */
connectDomains: z
.array(z.string())
.optional()
.describe("Origins for network requests (fetch/XHR/WebSocket)."),
/** @description Origins for static resources (scripts, images, styles, fonts). */
resourceDomains: z
.array(z.string())
.optional()
.describe("Origins for static resources (scripts, images, styles, fonts)."),
});
/**
* @description UI Resource metadata for security and rendering configuration.
*/
export const McpUiResourceMetaSchema = z.object({
/** @description Content Security Policy configuration. */
csp: McpUiResourceCspSchema.optional().describe(
"Content Security Policy configuration.",
),
/** @description Dedicated origin for widget sandbox. */
domain: z
.string()
.optional()
.describe("Dedicated origin for widget sandbox."),
/** @description Visual boundary preference - true if UI prefers a visible border. */
prefersBorder: z
.boolean()
.optional()
.describe(
"Visual boundary preference - true if UI prefers a visible border.",
),
});
/**
* @description Request to send a message to the host's chat interface.
* @see {@link app.App.sendMessage} for the method that sends this request
*/
export const McpUiMessageRequestSchema = z.object({
method: z.literal("ui/message"),
params: z.object({
/** @description Message role, currently only "user" is supported. */
role: z
.literal("user")
.describe('Message role, currently only "user" is supported.'),
/** @description Message content blocks (text, image, etc.). */
content: z
.array(ContentBlockSchema)
.describe("Message content blocks (text, image, etc.)."),
}),
});
/**
* @description Notification containing tool execution result (Host -> Guest UI).
*/
export const McpUiToolResultNotificationSchema = z.object({
method: z.literal("ui/notifications/tool-result"),
/** @description Standard MCP tool execution result. */
params: CallToolResultSchema.describe("Standard MCP tool execution result."),
});
/**
* @description Rich context about the host environment provided to Guest UIs.
*/
export const McpUiHostContextSchema = z.object({
/** @description Metadata of the tool call that instantiated this App. */
toolInfo: z
.object({
/** @description JSON-RPC id of the tools/call request. */
id: RequestIdSchema.describe("JSON-RPC id of the tools/call request."),
/** @description Tool definition including name, inputSchema, etc. */
tool: ToolSchema.describe(
"Tool definition including name, inputSchema, etc.",
),
})
.optional()
.describe("Metadata of the tool call that instantiated this App."),
/** @description Current color theme preference. */
theme: McpUiThemeSchema.optional().describe(
"Current color theme preference.",
),
/** @description How the UI is currently displayed. */
displayMode: McpUiDisplayModeSchema.optional().describe(
"How the UI is currently displayed.",
),
/** @description Display modes the host supports. */
availableDisplayModes: z
.array(z.string())
.optional()
.describe("Display modes the host supports."),
/** @description Current and maximum dimensions available to the UI. */
viewport: z
.object({
/** @description Current viewport width in pixels. */
width: z.number().describe("Current viewport width in pixels."),
/** @description Current viewport height in pixels. */
height: z.number().describe("Current viewport height in pixels."),
/** @description Maximum available height in pixels (if constrained). */
maxHeight: z
.number()
.optional()
.describe("Maximum available height in pixels (if constrained)."),
/** @description Maximum available width in pixels (if constrained). */
maxWidth: z
.number()
.optional()
.describe("Maximum available width in pixels (if constrained)."),
})
.optional()
.describe("Current and maximum dimensions available to the UI."),
/** @description User's language and region preference in BCP 47 format. */
locale: z
.string()
.optional()
.describe("User's language and region preference in BCP 47 format."),
/** @description User's timezone in IANA format. */
timeZone: z.string().optional().describe("User's timezone in IANA format."),
/** @description Host application identifier. */
userAgent: z.string().optional().describe("Host application identifier."),
/** @description Platform type for responsive design decisions. */
platform: z
.union([z.literal("web"), z.literal("desktop"), z.literal("mobile")])
.optional()
.describe("Platform type for responsive design decisions."),
/** @description Device input capabilities. */
deviceCapabilities: z
.object({
/** @description Whether the device supports touch input. */
touch: z
.boolean()
.optional()
.describe("Whether the device supports touch input."),
/** @description Whether the device supports hover interactions. */
hover: z
.boolean()
.optional()
.describe("Whether the device supports hover interactions."),
})
.optional()
.describe("Device input capabilities."),
/** @description Mobile safe area boundaries in pixels. */
safeAreaInsets: z
.object({
/** @description Top safe area inset in pixels. */
top: z.number().describe("Top safe area inset in pixels."),
/** @description Right safe area inset in pixels. */
right: z.number().describe("Right safe area inset in pixels."),
/** @description Bottom safe area inset in pixels. */
bottom: z.number().describe("Bottom safe area inset in pixels."),
/** @description Left safe area inset in pixels. */
left: z.number().describe("Left safe area inset in pixels."),
})
.optional()
.describe("Mobile safe area boundaries in pixels."),
});
/**
* @description Notification that host context has changed (Host -> Guest UI).
* @see {@link McpUiHostContext} for the full context structure
*/
export const McpUiHostContextChangedNotificationSchema = z.object({
method: z.literal("ui/notifications/host-context-changed"),
/** @description Partial context update containing only changed fields. */
params: McpUiHostContextSchema.describe(
"Partial context update containing only changed fields.",
),
});
/**
* @description Initialization request sent from Guest UI to Host.
* @see {@link app.App.connect} for the method that sends this request
*/
export const McpUiInitializeRequestSchema = z.object({
method: z.literal("ui/initialize"),
params: z.object({
/** @description App identification (name and version). */
appInfo: ImplementationSchema.describe(
"App identification (name and version).",
),
/** @description Features and capabilities this app provides. */
appCapabilities: McpUiAppCapabilitiesSchema.describe(
"Features and capabilities this app provides.",
),
/** @description Protocol version this app supports. */
protocolVersion: z.string().describe("Protocol version this app supports."),
}),
});
/**
* @description Initialization result returned from Host to Guest UI.
* @see {@link McpUiInitializeRequest}
*/
export const McpUiInitializeResultSchema = z.looseObject({
/** @description Negotiated protocol version string (e.g., "2025-11-21"). */
protocolVersion: z
.string()
.describe('Negotiated protocol version string (e.g., "2025-11-21").'),
/** @description Host application identification and version. */
hostInfo: ImplementationSchema.describe(
"Host application identification and version.",
),
/** @description Features and capabilities provided by the host. */
hostCapabilities: McpUiHostCapabilitiesSchema.describe(
"Features and capabilities provided by the host.",
),
/** @description Rich context about the host environment. */
hostContext: McpUiHostContextSchema.describe(
"Rich context about the host environment.",
),
});