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
10 changed files with 5346 additions and 1043 deletions

View file

@ -27,6 +27,12 @@ jobs:
- run: npm run build:all
- name: Verify generated schemas are up-to-date
run: |
npm run generate:schemas
npm run prettier:fix
git diff --exit-code src/generated/ || (echo "Generated schemas are out of date. Run 'npm run generate:schemas' and commit." && exit 1)
- run: npm test
- run: npm run prettier

View file

@ -1,2 +1,5 @@
npm run build:all
npm run prettier:fix
# Stage any changes to generated files (they may have been reformatted by prettier)
git add src/generated/

1644
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -22,7 +22,8 @@
"./app-bridge": {
"types": "./dist/src/app-bridge.d.ts",
"default": "./dist/src/app-bridge.js"
}
},
"./schema.json": "./dist/src/generated/schema.json"
},
"files": [
"dist"
@ -31,7 +32,8 @@
"examples/*"
],
"scripts": {
"build": "bun build.bun.ts",
"generate:schemas": "tsx scripts/generate-schemas.ts",
"build": "npm run generate:schemas && bun build.bun.ts",
"prepack": "npm run build",
"build:all": "npm run build && npm run examples:build",
"test": "bun test",
@ -59,6 +61,8 @@
"husky": "^9.1.7",
"nodemon": "^3.1.0",
"prettier": "^3.6.2",
"ts-to-zod": "^5.1.0",
"tsx": "^4.21.0",
"typedoc": "^0.28.14",
"typescript": "^5.9.3"
},

281
scripts/generate-schemas.ts Normal file
View file

@ -0,0 +1,281 @@
/**
* Schema Generation Script using ts-to-zod as a library
*
* This script generates Zod schemas from spec.types.ts and performs necessary
* post-processing for compatibility with this project.
*
* ## Why Post-Processing is Needed
*
* ts-to-zod is a powerful tool but has limitations that require post-processing:
*
* ### 1. Zod Import Path (`"zod"` → `"zod/v4"`)
*
* **Problem**: ts-to-zod generates `import { z } from "zod"` but this project
* uses the Zod v4 subpath import `"zod/v4"` for explicit version targeting.
*
* **Solution**: Replace the import path in the generated output.
*
* ### 2. External Type References (`z.any()` → actual schemas)
*
* **Problem**: ts-to-zod cannot resolve types imported from external packages.
* When it encounters types like `ContentBlock`, `CallToolResult`, `Implementation`,
* `RequestId`, and `Tool` from `@modelcontextprotocol/sdk`, it generates `z.any()`
* as a placeholder.
*
* **Solution**: Import the schemas from MCP SDK and remove the z.any() placeholders.
*
* ### 3. Index Signatures (`z.record().and()` → `z.looseObject()`)
*
* **Problem**: TypeScript index signatures like `[key: string]: unknown` are
* translated by ts-to-zod to `z.record(z.string(), z.unknown()).and(z.object({...}))`.
* This creates a `ZodIntersection` type which doesn't support `.extend()` etc.
*
* **Solution**: Replace the intersection pattern with `z.looseObject()`.
*
* ## Adding Schema Descriptions
*
* ts-to-zod supports `@description` JSDoc tags to generate `.describe()` calls:
*
* ```typescript
* interface MyType {
* /​** @description The user's full name *​/
* name: string;
* }
* ```
*
* Generates: `name: z.string().describe("The user's full name")`
*
* @see https://github.com/fabien0102/ts-to-zod
*/
import { readFileSync, writeFileSync } from "node:fs";
import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";
import { generate } from "ts-to-zod";
import { toJSONSchema, type $ZodType } from "zod/v4/core";
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const PROJECT_ROOT = join(__dirname, "..");
const SPEC_TYPES_FILE = join(PROJECT_ROOT, "src", "spec.types.ts");
const GENERATED_DIR = join(PROJECT_ROOT, "src", "generated");
const SCHEMA_OUTPUT_FILE = join(GENERATED_DIR, "schema.ts");
const SCHEMA_TEST_OUTPUT_FILE = join(GENERATED_DIR, "schema.test.ts");
const JSON_SCHEMA_OUTPUT_FILE = join(GENERATED_DIR, "schema.json");
/**
* External types from MCP SDK that ts-to-zod can't resolve.
* With PascalCase naming (via getSchemaName), generated placeholders match MCP SDK exports.
*/
const EXTERNAL_TYPE_SCHEMAS = [
"ContentBlockSchema",
"CallToolResultSchema",
"ImplementationSchema",
"RequestIdSchema",
"ToolSchema",
];
async function main() {
console.log("🔧 Generating Zod schemas from spec.types.ts...\n");
const sourceText = readFileSync(SPEC_TYPES_FILE, "utf-8");
const result = generate({
sourceText,
keepComments: true,
skipParseJSDoc: false,
// Generate PascalCase schema names: McpUiOpenLinkRequest → McpUiOpenLinkRequestSchema
getSchemaName: (typeName: string) => `${typeName}Schema`,
});
if (result.errors.length > 0) {
console.error("❌ Generation errors:");
for (const error of result.errors) {
console.error(` - ${error}`);
}
process.exit(1);
}
if (result.hasCircularDependencies) {
console.warn("⚠️ Warning: Circular dependencies detected in types");
}
let schemasContent = result.getZodSchemasFile("../spec.types.js");
schemasContent = postProcess(schemasContent);
writeFileSync(SCHEMA_OUTPUT_FILE, schemasContent, "utf-8");
console.log(`✅ Written: ${SCHEMA_OUTPUT_FILE}`);
const testsContent = result.getIntegrationTestFile(
"../spec.types.js",
"./schema.js",
);
if (testsContent) {
const processedTests = postProcessTests(testsContent);
writeFileSync(SCHEMA_TEST_OUTPUT_FILE, processedTests, "utf-8");
console.log(`✅ Written: ${SCHEMA_TEST_OUTPUT_FILE}`);
}
// Generate JSON Schema from the Zod schemas
await generateJsonSchema();
console.log("\n🎉 Schema generation complete!");
}
/**
* Generate JSON Schema from the Zod schemas.
* Uses dynamic import to load the generated schemas after they're written.
*/
async function generateJsonSchema() {
// Dynamic import of the generated schemas
// tsx handles TypeScript imports at runtime
const schemas = await import("../src/generated/schema.js");
const jsonSchema: {
$schema: string;
$id: string;
title: string;
description: string;
$defs: Record<string, unknown>;
} = {
$schema: "https://json-schema.org/draft/2020-12/schema",
$id: "https://modelcontextprotocol.io/ext-apps/schema.json",
title: "MCP Apps Protocol",
description: "JSON Schema for MCP Apps UI protocol messages",
$defs: {},
};
// Convert each exported Zod schema to JSON Schema
for (const [name, schema] of Object.entries(schemas)) {
if (
name.endsWith("Schema") &&
typeof schema === "object" &&
schema !== null
) {
const typeName = name.replace(/Schema$/, "");
try {
// Use unrepresentable: "any" to handle external types (MCP SDK schemas)
// that can't be directly represented in JSON Schema
jsonSchema.$defs[typeName] = toJSONSchema(schema as $ZodType, {
unrepresentable: "any",
});
} catch (error) {
console.warn(`⚠️ Could not convert ${name} to JSON Schema: ${error}`);
}
}
}
writeFileSync(
JSON_SCHEMA_OUTPUT_FILE,
JSON.stringify(jsonSchema, null, 2) + "\n",
"utf-8",
);
console.log(`✅ Written: ${JSON_SCHEMA_OUTPUT_FILE}`);
}
/**
* Post-process generated schemas for project compatibility.
*/
function postProcess(content: string): string {
// 1. Update import to use zod/v4
content = content.replace(
'import { z } from "zod";',
'import { z } from "zod/v4";',
);
// 2. Add MCP SDK schema imports
const mcpImports = EXTERNAL_TYPE_SCHEMAS.join(",\n ");
content = content.replace(
'import { z } from "zod/v4";',
`import { z } from "zod/v4";
import {
${mcpImports},
} from "@modelcontextprotocol/sdk/types.js";`,
);
// 3. Remove z.any() placeholders for external types (now imported from MCP SDK)
for (const schema of EXTERNAL_TYPE_SCHEMAS) {
content = content.replace(
new RegExp(`(?:export )?const ${schema} = z\\.any\\(\\);\\n?`, "g"),
"",
);
}
// 4. Replace z.record().and(z.object({...})) with z.looseObject({...})
// Uses brace-counting to handle nested objects correctly.
content = replaceRecordAndWithLooseObject(content);
// 5. Add header comment
content = content.replace(
"// Generated by ts-to-zod",
`// Generated by ts-to-zod
// Post-processed for Zod v4 and MCP SDK compatibility
// Run: npm run generate:schemas`,
);
return content;
}
/**
* Replace z.record(z.string(), z.unknown()).and(z.object({...})) with z.looseObject({...})
* Uses brace-counting to handle nested objects correctly.
*/
function replaceRecordAndWithLooseObject(content: string): string {
const pattern = "z.record(z.string(), z.unknown()).and(z.object({";
let result = content;
let startIndex = 0;
while (true) {
const matchStart = result.indexOf(pattern, startIndex);
if (matchStart === -1) break;
// Find the matching closing brace for z.object({
const objectStart = matchStart + pattern.length;
let braceCount = 1;
let i = objectStart;
while (i < result.length && braceCount > 0) {
if (result[i] === "{") braceCount++;
else if (result[i] === "}") braceCount--;
i++;
}
// i now points after the closing } of z.object({...})
// Check if followed by ))
if (result.slice(i, i + 2) === "))") {
const objectContent = result.slice(objectStart, i - 1);
const replacement = `z.looseObject({${objectContent}})`;
result = result.slice(0, matchStart) + replacement + result.slice(i + 2);
startIndex = matchStart + replacement.length;
} else {
startIndex = i;
}
}
return result;
}
/**
* Post-process generated integration tests.
*/
function postProcessTests(content: string): string {
content = content.replace(
'import { z } from "zod";',
'import { z } from "zod/v4";',
);
content = content.replace(
"// Generated by ts-to-zod",
`// Generated by ts-to-zod
// Integration tests verifying schemas match TypeScript types
// Run: npm run generate:schemas`,
);
return content;
}
main().catch((error) => {
console.error("❌ Schema generation failed:", error);
process.exit(1);
});

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.",
),
});

358
src/spec.types.ts Normal file
View file

@ -0,0 +1,358 @@
/**
* MCP Apps Protocol Types (spec.types.ts)
*
* This file contains pure TypeScript interface definitions for the MCP Apps protocol.
* These types are the source of truth and are used to generate Zod schemas via ts-to-zod.
*
* - Use `@description` JSDoc tags to generate `.describe()` calls on schemas
* - Run `npm run generate:schemas` to regenerate schemas from these types
*
* @see https://github.com/modelcontextprotocol/ext-apps/blob/main/specification/draft/apps.mdx
*/
import type {
CallToolResult,
ContentBlock,
Implementation,
RequestId,
Tool,
} from "@modelcontextprotocol/sdk/types.js";
/**
* Current protocol version supported by this SDK.
*
* The SDK automatically handles version negotiation during initialization.
* Apps and hosts don't need to manage protocol versions manually.
*/
export const LATEST_PROTOCOL_VERSION = "2025-11-21";
/**
* @description Color theme preference for the host environment.
*/
export type McpUiTheme = "light" | "dark";
/**
* @description Display mode for UI presentation.
*/
export type McpUiDisplayMode = "inline" | "fullscreen" | "pip";
/**
* @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 interface McpUiOpenLinkRequest {
method: "ui/open-link";
params: {
/** @description URL to open in the host's browser */
url: string;
};
}
/**
* @description Result from opening a URL.
* @see {@link McpUiOpenLinkRequest}
*/
export interface McpUiOpenLinkResult {
/** @description True if the host failed to open the URL (e.g., due to security policy). */
isError?: boolean;
/**
* Index signature required for MCP SDK `Protocol` class compatibility.
* Note: The schema intentionally omits this to enforce strict validation.
*/
[key: string]: unknown;
}
/**
* @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 interface McpUiMessageRequest {
method: "ui/message";
params: {
/** @description Message role, currently only "user" is supported. */
role: "user";
/** @description Message content blocks (text, image, etc.). */
content: ContentBlock[];
};
}
/**
* @description Result from sending a message.
* @see {@link McpUiMessageRequest}
*/
export interface McpUiMessageResult {
/** @description True if the host rejected or failed to deliver the message. */
isError?: boolean;
/**
* Index signature required for MCP SDK `Protocol` class compatibility.
* Note: The schema intentionally omits this to enforce strict validation.
*/
[key: string]: unknown;
}
/**
* @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 interface McpUiSandboxProxyReadyNotification {
method: "ui/notifications/sandbox-proxy-ready";
params: {};
}
/**
* @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 interface McpUiSandboxResourceReadyNotification {
method: "ui/notifications/sandbox-resource-ready";
params: {
/** @description HTML content to load into the inner iframe. */
html: string;
/** @description Optional override for the inner iframe's sandbox attribute. */
sandbox?: string;
/** @description CSP configuration from resource metadata. */
csp?: {
/** @description Origins for network requests (fetch/XHR/WebSocket). */
connectDomains?: string[];
/** @description Origins for static resources (scripts, images, styles, fonts). */
resourceDomains?: string[];
};
};
}
/**
* @description Notification of UI size changes (bidirectional: Guest <-> Host).
* @see {@link app.App.sendSizeChanged} for the method to send this from Guest UI
*/
export interface McpUiSizeChangedNotification {
method: "ui/notifications/size-changed";
params: {
/** @description New width in pixels. */
width?: number;
/** @description New height in pixels. */
height?: number;
};
}
/**
* @description Notification containing complete tool arguments (Host -> Guest UI).
*/
export interface McpUiToolInputNotification {
method: "ui/notifications/tool-input";
params: {
/** @description Complete tool call arguments as key-value pairs. */
arguments?: Record<string, unknown>;
};
}
/**
* @description Notification containing partial/streaming tool arguments (Host -> Guest UI).
*/
export interface McpUiToolInputPartialNotification {
method: "ui/notifications/tool-input-partial";
params: {
/** @description Partial tool call arguments (incomplete, may change). */
arguments?: Record<string, unknown>;
};
}
/**
* @description Notification containing tool execution result (Host -> Guest UI).
*/
export interface McpUiToolResultNotification {
method: "ui/notifications/tool-result";
/** @description Standard MCP tool execution result. */
params: CallToolResult;
}
/**
* @description Rich context about the host environment provided to Guest UIs.
*/
export interface McpUiHostContext {
/** @description Metadata of the tool call that instantiated this App. */
toolInfo?: {
/** @description JSON-RPC id of the tools/call request. */
id: RequestId;
/** @description Tool definition including name, inputSchema, etc. */
tool: Tool;
};
/** @description Current color theme preference. */
theme?: McpUiTheme;
/** @description How the UI is currently displayed. */
displayMode?: McpUiDisplayMode;
/** @description Display modes the host supports. */
availableDisplayModes?: string[];
/** @description Current and maximum dimensions available to the UI. */
viewport?: {
/** @description Current viewport width in pixels. */
width: number;
/** @description Current viewport height in pixels. */
height: number;
/** @description Maximum available height in pixels (if constrained). */
maxHeight?: number;
/** @description Maximum available width in pixels (if constrained). */
maxWidth?: number;
};
/** @description User's language and region preference in BCP 47 format. */
locale?: string;
/** @description User's timezone in IANA format. */
timeZone?: string;
/** @description Host application identifier. */
userAgent?: string;
/** @description Platform type for responsive design decisions. */
platform?: "web" | "desktop" | "mobile";
/** @description Device input capabilities. */
deviceCapabilities?: {
/** @description Whether the device supports touch input. */
touch?: boolean;
/** @description Whether the device supports hover interactions. */
hover?: boolean;
};
/** @description Mobile safe area boundaries in pixels. */
safeAreaInsets?: {
/** @description Top safe area inset in pixels. */
top: number;
/** @description Right safe area inset in pixels. */
right: number;
/** @description Bottom safe area inset in pixels. */
bottom: number;
/** @description Left safe area inset in pixels. */
left: number;
};
}
/**
* @description Notification that host context has changed (Host -> Guest UI).
* @see {@link McpUiHostContext} for the full context structure
*/
export interface McpUiHostContextChangedNotification {
method: "ui/notifications/host-context-changed";
/** @description Partial context update containing only changed fields. */
params: McpUiHostContext;
}
/**
* @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 interface McpUiResourceTeardownRequest {
method: "ui/resource-teardown";
params: {};
}
/**
* @description Result from graceful shutdown request.
* @see {@link McpUiResourceTeardownRequest}
*/
export interface McpUiResourceTeardownResult {
/**
* Index signature required for MCP SDK `Protocol` class compatibility.
*/
[key: string]: unknown;
}
/**
* @description Capabilities supported by the host application.
* @see {@link McpUiInitializeResult} for the initialization result that includes these capabilities
*/
export interface McpUiHostCapabilities {
/** @description Experimental features (structure TBD). */
experimental?: {};
/** @description Host supports opening external URLs. */
openLinks?: {};
/** @description Host can proxy tool calls to the MCP server. */
serverTools?: {
/** @description Host supports tools/list_changed notifications. */
listChanged?: boolean;
};
/** @description Host can proxy resource reads to the MCP server. */
serverResources?: {
/** @description Host supports resources/list_changed notifications. */
listChanged?: boolean;
};
/** @description Host accepts log messages. */
logging?: {};
}
/**
* @description Capabilities provided by the Guest UI (App).
* @see {@link McpUiInitializeRequest} for the initialization request that includes these capabilities
*/
export interface McpUiAppCapabilities {
/** @description Experimental features (structure TBD). */
experimental?: {};
/** @description App exposes MCP-style tools that the host can call. */
tools?: {
/** @description App supports tools/list_changed notifications. */
listChanged?: boolean;
};
}
/**
* @description Initialization request sent from Guest UI to Host.
* @see {@link app.App.connect} for the method that sends this request
*/
export interface McpUiInitializeRequest {
method: "ui/initialize";
params: {
/** @description App identification (name and version). */
appInfo: Implementation;
/** @description Features and capabilities this app provides. */
appCapabilities: McpUiAppCapabilities;
/** @description Protocol version this app supports. */
protocolVersion: string;
};
}
/**
* @description Initialization result returned from Host to Guest UI.
* @see {@link McpUiInitializeRequest}
*/
export interface McpUiInitializeResult {
/** @description Negotiated protocol version string (e.g., "2025-11-21"). */
protocolVersion: string;
/** @description Host application identification and version. */
hostInfo: Implementation;
/** @description Features and capabilities provided by the host. */
hostCapabilities: McpUiHostCapabilities;
/** @description Rich context about the host environment. */
hostContext: McpUiHostContext;
/**
* Index signature required for MCP SDK `Protocol` class compatibility.
* Note: The schema intentionally omits this to enforce strict validation.
*/
[key: string]: unknown;
}
/**
* @description Notification that Guest UI has completed initialization (Guest UI -> Host).
* @see {@link app.App.connect} for the method that sends this notification
*/
export interface McpUiInitializedNotification {
method: "ui/notifications/initialized";
params?: {};
}
/**
* @description Content Security Policy configuration for UI resources.
*/
export interface McpUiResourceCsp {
/** @description Origins for network requests (fetch/XHR/WebSocket). */
connectDomains?: string[];
/** @description Origins for static resources (scripts, images, styles, fonts). */
resourceDomains?: string[];
}
/**
* @description UI Resource metadata for security and rendering configuration.
*/
export interface McpUiResourceMeta {
/** @description Content Security Policy configuration. */
csp?: McpUiResourceCsp;
/** @description Dedicated origin for widget sandbox. */
domain?: string;
/** @description Visual boundary preference - true if UI prefers a visible border. */
prefersBorder?: boolean;
}

View file

@ -1,895 +1,65 @@
import {
CallToolResult,
CallToolResultSchema,
ContentBlock,
ContentBlockSchema,
EmptyResultSchema,
Implementation,
ImplementationSchema,
RequestId,
RequestIdSchema,
RequestSchema,
Tool,
ToolSchema,
} from "@modelcontextprotocol/sdk/types.js";
import { z } from "zod/v4";
/**
* Type-level assertion that validates a Zod schema produces the expected interface.
* MCP Apps Protocol Types and Schemas
*
* This helper is used for request and notification schemas that cannot use
* `z.ZodType<Interface>` type annotations. Adding `: z.ZodType<Interface>` to
* schemas widens the type from the specific `ZodObject` to the generic `ZodType`,
* which breaks MCP SDK's `setRequestHandler()` and `setNotificationHandler()`
* methods that require the specific `ZodObject` type.
* This file re-exports types from spec.types.ts and schemas from generated/schema.ts.
* Compile-time verification is handled by generated/schema.test.ts.
*
* By using this type-level assertion instead, we get compile-time validation that
* the schema matches the interface without affecting the runtime schema type.
*
* @internal
* @see spec.types.ts for the source of truth TypeScript interfaces
* @see generated/schema.ts for auto-generated Zod schemas
* @see generated/schema.test.ts for compile-time verification
*/
type VerifySchemaMatches<TSchema extends z.ZodTypeAny, TInterface> =
z.infer<TSchema> extends TInterface
? TInterface extends z.infer<TSchema>
? true
: never
: never;
/**
* Current protocol version supported by this SDK.
*
* The SDK automatically handles version negotiation during initialization.
* Apps and hosts don't need to manage protocol versions manually.
*/
export const LATEST_PROTOCOL_VERSION = "2025-11-21";
// Re-export all types from spec.types.ts
export {
LATEST_PROTOCOL_VERSION,
type McpUiTheme,
type McpUiDisplayMode,
type McpUiOpenLinkRequest,
type McpUiOpenLinkResult,
type McpUiMessageRequest,
type McpUiMessageResult,
type McpUiSandboxProxyReadyNotification,
type McpUiSandboxResourceReadyNotification,
type McpUiSizeChangedNotification,
type McpUiToolInputNotification,
type McpUiToolInputPartialNotification,
type McpUiToolResultNotification,
type McpUiHostContext,
type McpUiHostContextChangedNotification,
type McpUiResourceTeardownRequest,
type McpUiResourceTeardownResult,
type McpUiHostCapabilities,
type McpUiAppCapabilities,
type McpUiInitializeRequest,
type McpUiInitializeResult,
type McpUiInitializedNotification,
type McpUiResourceCsp,
type McpUiResourceMeta,
} from "./spec.types.js";
/**
* Color theme preference for the host environment.
* @see {@link McpUiHostContext.theme}
*/
export type McpUiTheme = "light" | "dark";
/** Runtime validation schema for {@link McpUiTheme}. */
export const McpUiThemeSchema = z.enum(["light", "dark"]);
/**
* Display mode for UI presentation.
* - `inline`: Embedded within the conversation flow
* - `fullscreen`: Expanded to fill the available viewport
* - `pip`: Picture-in-picture floating window
*
* @see {@link McpUiHostContext.displayMode}
*/
export type McpUiDisplayMode = "inline" | "fullscreen" | "pip";
/** Runtime validation schema for {@link McpUiDisplayMode}. */
export const McpUiDisplayModeSchema = z.enum(["inline", "fullscreen", "pip"]);
/**
* Request to open an external URL in the host's default browser.
*
* Sent from the Guest UI to the Host when requesting to open an external link.
* The host may deny the request based on user preferences or security policy.
*
* @see {@link app.App.sendOpenLink} for the method that sends this request
*/
export interface McpUiOpenLinkRequest {
method: "ui/open-link";
params: {
/** URL to open in the host's browser */
url: string;
};
}
/**
* Runtime validation schema for {@link McpUiOpenLinkRequest}.
* @internal
*/
export const McpUiOpenLinkRequestSchema = RequestSchema.extend({
method: z.literal("ui/open-link"),
params: z.object({
url: z.string().url(),
}),
});
/** @internal - Compile-time verification that schema matches interface */
type _VerifyOpenLinkRequest = VerifySchemaMatches<
typeof McpUiOpenLinkRequestSchema,
McpUiOpenLinkRequest
>;
/**
* Result from a {@link McpUiOpenLinkRequest}.
*
* The host returns this result after attempting to open the requested URL.
*
* @see {@link McpUiOpenLinkRequest}
*/
export interface McpUiOpenLinkResult {
/**
* True if the host failed to open the URL (e.g., due to security policy,
* user cancellation, or system error). False or undefined indicates success.
*/
isError?: boolean;
/**
* Index signature required for MCP SDK `Protocol` class compatibility.
* Note: The schema intentionally omits this to enforce strict validation.
*/
[key: string]: unknown;
}
/**
* Runtime validation schema for {@link McpUiOpenLinkResult}.
* @internal
*/
export const McpUiOpenLinkResultSchema: z.ZodType<McpUiOpenLinkResult> =
z.object({
isError: z.boolean().optional(),
});
/**
* Request to send a message to the host's chat interface.
*
* Sent from the Guest UI to the Host when the app wants to add a message to the
* conversation thread. This enables interactive apps to communicate with the user
* through the host's chat interface.
*
* @see {@link app.App.sendMessage} for the method that sends this request
*/
export interface McpUiMessageRequest {
method: "ui/message";
params: {
/** Message role, currently only "user" is supported */
role: "user";
/** Message content blocks (text, image, etc.) */
content: ContentBlock[];
};
}
/**
* Runtime validation schema for {@link McpUiMessageRequest}.
* @internal
*/
export const McpUiMessageRequestSchema = RequestSchema.extend({
method: z.literal("ui/message"),
params: z.object({
role: z.literal("user"),
content: z.array(ContentBlockSchema),
}),
});
/** @internal - Compile-time verification that schema matches interface */
type _VerifyMessageRequest = VerifySchemaMatches<
typeof McpUiMessageRequestSchema,
McpUiMessageRequest
>;
/**
* Result from a {@link McpUiMessageRequest}.
*
* Note: The host does not return message content or follow-up results to prevent
* leaking information from the conversation. Only error status is provided.
*
* @see {@link McpUiMessageRequest}
*/
export interface McpUiMessageResult {
/**
* True if the host rejected or failed to deliver the message (e.g., due to
* rate limiting, content policy, or system error). False or undefined
* indicates the message was accepted.
*/
isError?: boolean;
/**
* Index signature required for MCP SDK `Protocol` class compatibility.
* Note: The schema intentionally omits this to enforce strict validation.
*/
[key: string]: unknown;
}
/**
* Runtime validation schema for {@link McpUiMessageResult}.
* @internal
*/
export const McpUiMessageResultSchema: z.ZodType<McpUiMessageResult> = z.object(
{
isError: z.boolean().optional(),
},
);
// McpUiIframeReadyNotification removed - replaced by standard MCP initialization
// The SDK's oninitialized callback now handles the ready signal
/**
* Notification that the sandbox proxy iframe is ready to receive content.
*
* This is an internal message used by web-based hosts implementing the
* double-iframe sandbox architecture. The sandbox proxy sends this to the host
* after it loads and is ready to receive HTML content via
* {@link McpUiSandboxResourceReadyNotification}.
*
* @internal
* @see https://github.com/modelcontextprotocol/ext-apps/blob/main/specification/draft/apps.mdx#sandbox-proxy
*/
export interface McpUiSandboxProxyReadyNotification {
method: "ui/notifications/sandbox-proxy-ready";
params: {};
}
/**
* Runtime validation schema for {@link McpUiSandboxProxyReadyNotification}.
* @internal
*/
export const McpUiSandboxProxyReadyNotificationSchema = z.object({
method: z.literal("ui/notifications/sandbox-proxy-ready"),
params: z.object({}),
});
/** @internal - Compile-time verification that schema matches interface */
type _VerifySandboxProxyReadyNotification = VerifySchemaMatches<
typeof McpUiSandboxProxyReadyNotificationSchema,
McpUiSandboxProxyReadyNotification
>;
/**
* Notification containing HTML resource for the sandbox proxy to load.
*
* This is an internal message used by web-based hosts implementing the
* double-iframe sandbox architecture. After the sandbox proxy signals readiness,
* the host sends this notification with the HTML content and optional sandbox
* attributes to load into the inner iframe.
*
* @internal
* @see https://github.com/modelcontextprotocol/ext-apps/blob/main/specification/draft/apps.mdx#sandbox-proxy
*/
export interface McpUiSandboxResourceReadyNotification {
method: "ui/notifications/sandbox-resource-ready";
params: {
/** HTML content to load into the inner iframe */
html: string;
/** Optional override for the inner iframe's sandbox attribute */
sandbox?: string;
/** CSP configuration from resource metadata */
csp?: {
/** Origins for network requests (fetch/XHR/WebSocket) */
connectDomains?: string[];
/** Origins for static resources (scripts, images, styles, fonts) */
resourceDomains?: string[];
};
};
}
/**
* Runtime validation schema for {@link McpUiSandboxResourceReadyNotification}.
* @internal
*/
export const McpUiSandboxResourceReadyNotificationSchema = z.object({
method: z.literal("ui/notifications/sandbox-resource-ready"),
params: z.object({
html: z.string(),
sandbox: z.string().optional(),
csp: z
.object({
connectDomains: z.array(z.string()).optional(),
resourceDomains: z.array(z.string()).optional(),
})
.optional(),
}),
});
/** @internal - Compile-time verification that schema matches interface */
type _VerifySandboxResourceReadyNotification = VerifySchemaMatches<
typeof McpUiSandboxResourceReadyNotificationSchema,
McpUiSandboxResourceReadyNotification
>;
/**
* Notification of UI size changes (bidirectional: Guest ↔ Host).
*
* **Guest UI → Host**: Sent by the Guest UI when its rendered content size changes,
* typically using ResizeObserver. This helps the host adjust the iframe container.
* If {@link app.App} is configured with `autoResize: true` (default), this is sent
* automatically.
*
* **Host → Guest UI**: Sent by the Host when the viewport size changes (e.g.,
* window resize, orientation change). This allows the Guest UI to adjust its layout.
*
* @see {@link app.App.sendSizeChanged} for the method to send this from Guest UI
* @see {@link app.App.setupSizeChangedNotifications} for automatic size reporting
*/
export interface McpUiSizeChangedNotification {
method: "ui/notifications/size-changed";
params: {
/** New width in pixels */
width?: number;
/** New height in pixels */
height?: number;
};
}
/**
* Runtime validation schema for {@link McpUiSizeChangedNotification}.
* @internal
*/
export const McpUiSizeChangedNotificationSchema = z.object({
method: z.literal("ui/notifications/size-changed"),
params: z.object({
width: z.number().optional(),
height: z.number().optional(),
}),
});
/** @internal - Compile-time verification that schema matches interface */
type _VerifySizeChangeNotification = VerifySchemaMatches<
typeof McpUiSizeChangedNotificationSchema,
McpUiSizeChangedNotification
>;
/**
* Notification containing complete tool arguments (Host → Guest UI).
*
* The host MUST send this notification after the Guest UI's initialize request
* completes, when complete tool arguments become available. This notification is
* sent exactly once and is required before {@link McpUiToolResultNotification}.
*
* The arguments object contains the complete tool call parameters that triggered
* this App instance.
*/
export interface McpUiToolInputNotification {
method: "ui/notifications/tool-input";
params: {
/** Complete tool call arguments as key-value pairs */
arguments?: Record<string, unknown>;
};
}
/**
* Runtime validation schema for {@link McpUiToolInputNotification}.
* @internal
*/
export const McpUiToolInputNotificationSchema = z.object({
method: z.literal("ui/notifications/tool-input"),
params: z.object({
arguments: z.record(z.string(), z.unknown()).optional(),
}),
});
/** @internal - Compile-time verification that schema matches interface */
type _VerifyToolInputNotification = VerifySchemaMatches<
typeof McpUiToolInputNotificationSchema,
McpUiToolInputNotification
>;
/**
* Notification containing partial/streaming tool arguments (Host → Guest UI).
*
* The host MAY send this notification zero or more times while the agent is
* streaming tool arguments, before {@link McpUiToolInputNotification} is sent
* with complete arguments.
*
* The arguments object represents best-effort recovery of incomplete JSON, with
* unclosed structures automatically closed to produce valid JSON. Guest UIs may
* ignore these notifications or use them to render progressive loading states.
*
* Guest UIs MUST NOT rely on partial arguments for critical operations and SHOULD
* gracefully handle missing or changing fields between notifications.
*/
export interface McpUiToolInputPartialNotification {
method: "ui/notifications/tool-input-partial";
params: {
/** Partial tool call arguments (incomplete, may change) */
arguments?: Record<string, unknown>;
};
}
/**
* Runtime validation schema for {@link McpUiToolInputPartialNotification}.
* @internal
*/
export const McpUiToolInputPartialNotificationSchema = z.object({
method: z.literal("ui/notifications/tool-input-partial"),
params: z.object({
arguments: z.record(z.string(), z.unknown()).optional(),
}),
});
/** @internal - Compile-time verification that schema matches interface */
type _VerifyToolInputPartialNotification = VerifySchemaMatches<
typeof McpUiToolInputPartialNotificationSchema,
McpUiToolInputPartialNotification
>;
/**
* Notification containing tool execution result (Host → Guest UI).
*
* The host MUST send this notification when tool execution completes successfully,
* provided the UI is still displayed. If the UI was closed before execution
* completes, the host MAY skip this notification. This notification is sent after
* {@link McpUiToolInputNotification}.
*
* The result follows the standard MCP CallToolResult format, containing content
* for the model and optionally structuredContent optimized for UI rendering.
*/
export interface McpUiToolResultNotification {
method: "ui/notifications/tool-result";
/** Standard MCP tool execution result */
params: CallToolResult;
}
/**
* Runtime validation schema for {@link McpUiToolResultNotification}.
* @internal
*/
export const McpUiToolResultNotificationSchema = z.object({
method: z.literal("ui/notifications/tool-result"),
params: CallToolResultSchema,
});
/** @internal - Compile-time verification that schema matches interface */
type _VerifyToolResultNotification = VerifySchemaMatches<
typeof McpUiToolResultNotificationSchema,
McpUiToolResultNotification
>;
/**
* Rich context about the host environment provided to Guest UIs.
*
* Hosts provide this context in the {@link McpUiInitializeResult} response and send
* updates via {@link McpUiHostContextChangedNotification} when values change.
* All fields are optional and Guest UIs should handle missing fields gracefully.
*
* @example
* ```typescript
* // Received during initialization
* const result = await app.connect(transport);
* const context = result.hostContext;
*
* if (context.theme === "dark") {
* document.body.classList.add("dark-mode");
* }
* ```
*/
export interface McpUiHostContext {
/** Metadata of the tool call that instantiated this App */
toolInfo?: {
/** JSON-RPC id of the tools/call request */
id: RequestId;
/** Tool definition including name, inputSchema, etc. */
tool: Tool;
};
/**
* Current color theme preference.
* @example "dark"
*/
theme?: McpUiTheme;
/**
* How the UI is currently displayed.
* @example "inline"
*/
displayMode?: McpUiDisplayMode;
/**
* Display modes the host supports.
* Apps can use this to offer mode-switching UI if applicable.
*/
availableDisplayModes?: string[];
/** Current and maximum dimensions available to the UI */
viewport?: {
/** Current viewport width in pixels */
width: number;
/** Current viewport height in pixels */
height: number;
/** Maximum available height in pixels (if constrained) */
maxHeight?: number;
/** Maximum available width in pixels (if constrained) */
maxWidth?: number;
};
/**
* User's language and region preference in BCP 47 format.
* @example "en-US", "fr-CA", "ja-JP"
*/
locale?: string;
/**
* User's timezone in IANA format.
* @example "America/New_York", "Europe/London", "Asia/Tokyo"
*/
timeZone?: string;
/**
* Host application identifier.
* @example "claude-desktop/1.0.0"
*/
userAgent?: string;
/**
* Platform type for responsive design decisions.
* @example "desktop"
*/
platform?: "web" | "desktop" | "mobile";
/** Device input capabilities */
deviceCapabilities?: {
/** Whether the device supports touch input */
touch?: boolean;
/** Whether the device supports hover interactions */
hover?: boolean;
};
/**
* Safe area boundaries in pixels.
* Used to avoid notches, rounded corners, and system UI.
*/
safeAreaInsets?: {
/** Top safe area inset in pixels */
top: number;
/** Right safe area inset in pixels */
right: number;
/** Bottom safe area inset in pixels */
bottom: number;
/** Left safe area inset in pixels */
left: number;
};
}
/**
* Runtime validation schema for {@link McpUiHostContext}.
* @internal
*/
export const McpUiHostContextSchema: z.ZodType<McpUiHostContext> = z.object({
toolInfo: z
.object({
id: RequestIdSchema,
tool: ToolSchema,
})
.optional(),
theme: McpUiThemeSchema.optional(),
displayMode: McpUiDisplayModeSchema.optional(),
availableDisplayModes: z.array(z.string()).optional(),
viewport: z
.object({
width: z.number(),
height: z.number(),
maxHeight: z.number().optional(),
maxWidth: z.number().optional(),
})
.optional(),
locale: z.string().optional(),
timeZone: z.string().optional(),
userAgent: z.string().optional(),
platform: z.enum(["web", "desktop", "mobile"]).optional(),
deviceCapabilities: z
.object({
touch: z.boolean().optional(),
hover: z.boolean().optional(),
})
.optional(),
safeAreaInsets: z
.object({
top: z.number(),
right: z.number(),
bottom: z.number(),
left: z.number(),
})
.optional(),
});
/**
* Notification that host context has changed (Host → Guest UI).
*
* The host MAY send this notification when any context field changes, such as:
* - Theme toggled (light/dark)
* - Display mode changed (inline/fullscreen)
* - Device orientation changed
* - Window/panel resized
*
* This notification contains partial updates. Guest UIs SHOULD merge received
* fields with their current context state rather than replacing it entirely.
*
* @see {@link McpUiHostContext} for the full context structure
*/
export interface McpUiHostContextChangedNotification {
method: "ui/notifications/host-context-changed";
/** Partial context update containing only changed fields */
params: McpUiHostContext;
}
/**
* Runtime validation schema for {@link McpUiHostContextChangedNotification}.
* @internal
*/
export const McpUiHostContextChangedNotificationSchema = z.object({
method: z.literal("ui/notifications/host-context-changed"),
params: McpUiHostContextSchema,
});
/** @internal - Compile-time verification that schema matches interface */
type _VerifyHostContextChangedNotification = VerifySchemaMatches<
typeof McpUiHostContextChangedNotificationSchema,
McpUiHostContextChangedNotification
>;
/**
* Request for graceful shutdown of the Guest UI (Host → Guest UI).
*
* The host MUST send this request before tearing down the UI resource, for any
* reason including user action, resource reallocation, or app closure. This gives
* the Guest UI an opportunity to save state, cancel pending operations, or show
* confirmation dialogs.
*
* The host SHOULD wait for the response before unmounting the iframe to prevent
* data loss.
*
* @see {@link app-bridge.AppBridge.sendResourceTeardown} for the host method that sends this
*/
export interface McpUiResourceTeardownRequest {
method: "ui/resource-teardown";
params: {};
}
/**
* Runtime validation schema for {@link McpUiResourceTeardownRequest}.
* @internal
*/
export const McpUiResourceTeardownRequestSchema = RequestSchema.extend({
method: z.literal("ui/resource-teardown"),
params: z.object({}),
});
/** @internal - Compile-time verification that schema matches interface */
type _VerifyResourceTeardownRequest = VerifySchemaMatches<
typeof McpUiResourceTeardownRequestSchema,
McpUiResourceTeardownRequest
>;
/**
* Result from graceful shutdown request.
*
* Empty result indicates the Guest UI has completed cleanup and is ready to be
* torn down.
*
* @see {@link McpUiResourceTeardownRequest}
*/
export interface McpUiResourceTeardownResult {}
/**
* Runtime validation schema for {@link McpUiResourceTeardownResult}.
* @internal
*/
export const McpUiResourceTeardownResultSchema: z.ZodType<McpUiResourceTeardownResult> =
EmptyResultSchema;
/**
* Capabilities supported by the host application.
*
* Hosts declare these capabilities during the initialization handshake. Guest UIs
* can check capabilities before attempting to use specific features.
*
* @example Check if host supports opening links
* ```typescript
* const result = await app.connect(transport);
* if (result.hostCapabilities.openLinks) {
* await app.sendOpenLink({ url: "https://example.com" });
* }
* ```
*
* @see {@link McpUiInitializeResult} for the initialization result that includes these capabilities
*/
export interface McpUiHostCapabilities {
/** Experimental features (structure TBD) */
experimental?: {};
/** Host supports opening external URLs via {@link app.App.sendOpenLink} */
openLinks?: {};
/** Host can proxy tool calls to the MCP server */
serverTools?: {
/** Host supports tools/list_changed notifications */
listChanged?: boolean;
};
/** Host can proxy resource reads to the MCP server */
serverResources?: {
/** Host supports resources/list_changed notifications */
listChanged?: boolean;
};
/** Host accepts log messages via {@link app.App.sendLog} */
logging?: {};
}
/**
* Runtime validation schema for {@link McpUiHostCapabilities}.
* @internal
*/
export const McpUiHostCapabilitiesSchema: z.ZodType<McpUiHostCapabilities> =
z.object({
experimental: z.object({}).optional(),
openLinks: z.object({}).optional(),
serverTools: z
.object({
listChanged: z.boolean().optional(),
})
.optional(),
serverResources: z
.object({
listChanged: z.boolean().optional(),
})
.optional(),
logging: z.object({}).optional(),
});
/**
* Capabilities provided by the Guest UI (App).
*
* Apps declare these capabilities during the initialization handshake to indicate
* what features they provide to the host.
*
* @example Declare tool capabilities
* ```typescript
* const app = new App(
* { name: "MyApp", version: "1.0.0" },
* { tools: { listChanged: true } }
* );
* ```
*
* @see {@link McpUiInitializeRequest} for the initialization request that includes these capabilities
*/
export interface McpUiAppCapabilities {
/** Experimental features (structure TBD) */
experimental?: {};
/**
* App exposes MCP-style tools that the host can call.
* These are app-specific tools, not proxied from the server.
*/
tools?: {
/** App supports tools/list_changed notifications */
listChanged?: boolean;
};
}
/**
* Runtime validation schema for {@link McpUiAppCapabilities}.
* @internal
*/
export const McpUiAppCapabilitiesSchema: z.ZodType<McpUiAppCapabilities> =
z.object({
experimental: z.object({}).optional(),
tools: z
.object({
listChanged: z.boolean().optional(),
})
.optional(),
});
/**
* Initialization request sent from Guest UI to Host.
*
* This is the first message sent by the Guest UI after loading. The host responds
* with {@link McpUiInitializeResult} containing host capabilities and context.
* After receiving the response, the Guest UI MUST send
* {@link McpUiInitializedNotification}.
*
* This replaces the custom iframe-ready pattern used in pre-SEP MCP-UI.
*
* @see {@link app.App.connect} for the method that sends this request
*/
export interface McpUiInitializeRequest {
method: "ui/initialize";
params: {
/** App identification (name and version) */
appInfo: Implementation;
/** Features and capabilities this app provides */
appCapabilities: McpUiAppCapabilities;
/** Protocol version this app supports */
protocolVersion: string;
};
}
/**
* Runtime validation schema for {@link McpUiInitializeRequest}.
* @internal
*/
export const McpUiInitializeRequestSchema = RequestSchema.extend({
method: z.literal("ui/initialize"),
params: z.object({
appInfo: ImplementationSchema,
appCapabilities: McpUiAppCapabilitiesSchema,
protocolVersion: z.string(),
}),
});
/** @internal - Compile-time verification that schema matches interface */
type _VerifyInitializeRequest = VerifySchemaMatches<
typeof McpUiInitializeRequestSchema,
McpUiInitializeRequest
>;
/**
* Initialization result returned from Host to Guest UI.
*
* Contains the negotiated protocol version, host information, capabilities,
* and rich context about the host environment.
*
* @see {@link McpUiInitializeRequest}
*/
export interface McpUiInitializeResult {
/** Negotiated protocol version string (e.g., "2025-11-21") */
protocolVersion: string;
/** Host application identification and version */
hostInfo: Implementation;
/** Features and capabilities provided by the host */
hostCapabilities: McpUiHostCapabilities;
/** Rich context about the host environment */
hostContext: McpUiHostContext;
/**
* Index signature required for MCP SDK `Protocol` class compatibility.
* Note: The schema intentionally omits this to enforce strict validation.
*/
[key: string]: unknown;
}
/**
* Runtime validation schema for {@link McpUiInitializeResult}.
* @internal
*/
export const McpUiInitializeResultSchema: z.ZodType<McpUiInitializeResult> =
z.object({
protocolVersion: z.string(),
hostInfo: ImplementationSchema,
hostCapabilities: McpUiHostCapabilitiesSchema,
hostContext: McpUiHostContextSchema,
});
/**
* Notification that Guest UI has completed initialization (Guest UI → Host).
*
* The Guest UI MUST send this notification after receiving
* {@link McpUiInitializeResult} and completing any setup. The host waits for this
* notification before sending tool input and other data to the Guest UI.
*
* @see {@link app.App.connect} for the method that sends this notification
*/
export interface McpUiInitializedNotification {
method: "ui/notifications/initialized";
params?: {};
}
/**
* Runtime validation schema for {@link McpUiInitializedNotification}.
* @internal
*/
export const McpUiInitializedNotificationSchema = z.object({
method: z.literal("ui/notifications/initialized"),
params: z.object({}).optional(),
});
/** @internal - Compile-time verification that schema matches interface */
type _VerifyInitializedNotification = VerifySchemaMatches<
typeof McpUiInitializedNotificationSchema,
McpUiInitializedNotification
>;
// =============================================================================
// UI Resource Metadata Types
// =============================================================================
/**
* Content Security Policy configuration for UI resources.
*
* Servers declare which external origins their UI needs to access.
* Hosts use this to enforce appropriate CSP headers.
*/
export const McpUiResourceCspSchema = z.object({
/** Origins for network requests (fetch/XHR/WebSocket). Maps to CSP connect-src */
connectDomains: z.array(z.string()).optional(),
/** Origins for static resources (images, scripts, stylesheets, fonts, media). Maps to CSP img-src, script-src, style-src, font-src, media-src */
resourceDomains: z.array(z.string()).optional(),
});
export type McpUiResourceCsp = z.infer<typeof McpUiResourceCspSchema>;
/**
* UI Resource metadata for security and rendering configuration.
*
* Included in the `_meta.ui` field of UI resource content returned via `resources/read`.
*
* @see {@link McpUiResourceCspSchema} for CSP configuration
*/
export const McpUiResourceMetaSchema = z.object({
/** Content Security Policy configuration */
csp: McpUiResourceCspSchema.optional(),
/** Dedicated origin for widget sandbox */
domain: z.string().optional(),
/** Visual boundary preference - true if UI prefers a visible border */
prefersBorder: z.boolean().optional(),
});
export type McpUiResourceMeta = z.infer<typeof McpUiResourceMetaSchema>;
// Re-export all schemas from generated/schema.ts (already PascalCase)
export {
McpUiThemeSchema,
McpUiDisplayModeSchema,
McpUiOpenLinkRequestSchema,
McpUiOpenLinkResultSchema,
McpUiMessageRequestSchema,
McpUiMessageResultSchema,
McpUiSandboxProxyReadyNotificationSchema,
McpUiSandboxResourceReadyNotificationSchema,
McpUiSizeChangedNotificationSchema,
McpUiToolInputNotificationSchema,
McpUiToolInputPartialNotificationSchema,
McpUiToolResultNotificationSchema,
McpUiHostContextSchema,
McpUiHostContextChangedNotificationSchema,
McpUiResourceTeardownRequestSchema,
McpUiResourceTeardownResultSchema,
McpUiHostCapabilitiesSchema,
McpUiAppCapabilitiesSchema,
McpUiInitializeRequestSchema,
McpUiInitializeResultSchema,
McpUiInitializedNotificationSchema,
McpUiResourceCspSchema,
McpUiResourceMetaSchema,
} from "./generated/schema.js";