-
Notifications
You must be signed in to change notification settings - Fork 49
build: auto-generate Zod schemas from TypeScript types using ts-to-zod #116
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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>
…rated.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>
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>
- 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>
Resolved conflicts: - .github/workflows/ci.yml: Combined schema verification with new test step - package.json: Merged schema generation script with new example scripts - src/types.ts: Kept refactored structure with spec.types.ts/schemas.generated.ts - package-lock.json: Regenerated Renamed McpUiSizeChangeNotification -> McpUiSizeChangedNotification to match main. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
- 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>
- 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>
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>
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>
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>
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>
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>
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>
commit: |
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>
334b656 to
93dd4a6
Compare
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>
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>
idosal
previously approved these changes
Dec 9, 2025
Collaborator
idosal
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Awesome @ochafik !
Resolved conflict in src/types.ts - kept the clean re-export structure from this branch which separates concerns between spec.types.ts (interfaces) and generated/schema.ts (Zod schemas). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This was referenced Dec 11, 2025
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
TL;DR
Edit types in one place → Zod schemas + JSON Schema auto-generated.
Before: Hand-maintain both TypeScript interfaces AND Zod schemas (error-prone, tedious).
After: Edit
src/spec.types.tsonly. Runnpm run generate:schemas. Done.How It Works
1. Single Source of Truth
2. Generation Pipeline
We use ts-to-zod to convert TypeScript interfaces to Zod schemas, then post-process the output:
import { z } from "zod""zod/v4"z.any()for SDK typesz.record().and(z.object())z.looseObject()3. Adding Descriptions
Use
@descriptionJSDoc tags to generate.describe()calls:Generates:
These descriptions flow into the JSON Schema too, enabling documentation generation and IDE support.
What's Exported
All 47 exports from main branch are preserved with identical types.
Testing
npm run buildnpm testschema.test.tsgit diff --exit-code src/generated/For Reviewers
Key files to review:
src/spec.types.ts- The new source of truth for all protocol typesscripts/generate-schemas.ts- The generation + post-processing logicsrc/types.ts- Re-exports (just wiring, no logic)The
src/generated/files are auto-generated and don't need detailed review.🤖 Generated with Claude Code