11import fs from "node:fs" ;
22import path from "node:path" ;
33import { fileURLToPath } from "node:url" ;
4- import { zodToJsonSchema } from "zod-to-json-schema" ;
54
65const __dirname = path . dirname ( fileURLToPath ( import . meta. url ) ) ;
76const ROOT = path . resolve ( __dirname , "../../.." ) ;
@@ -11,6 +10,143 @@ const TOOLS_DIR = path.join(ROOT, "src", "tools");
1110const START = "<!-- AUTO-GENERATED TOOLS START -->" ;
1211const END = "<!-- AUTO-GENERATED TOOLS END -->" ;
1312
13+ /**
14+ * Check if a schema is a Zod v4 schema
15+ */
16+ function isZodV4 ( schema ) {
17+ return schema && typeof schema === "object" && schema . _zod ?. def ?. type ;
18+ }
19+
20+ /**
21+ * Check if a schema is a Zod v3 schema
22+ */
23+ function isZodV3 ( schema ) {
24+ return (
25+ schema &&
26+ typeof schema === "object" &&
27+ typeof schema . safeParse === "function" &&
28+ ! isZodV4 ( schema )
29+ ) ;
30+ }
31+
32+ /**
33+ * Extract the base type from a Zod v4 schema property
34+ */
35+ function extractZodV4BaseType ( prop ) {
36+ if ( ! prop ) return { type : "unknown" } ;
37+
38+ const def = prop . _zod ?. def || prop . def ;
39+ if ( ! def ) {
40+ // Check if it's a direct type
41+ if ( prop . type ) return { type : prop . type } ;
42+ return { type : "unknown" } ;
43+ }
44+
45+ const wrapperType = def . type ;
46+
47+ // Unwrap default/optional wrappers
48+ if ( wrapperType === "default" || wrapperType === "optional" ) {
49+ const inner = def . innerType ;
50+ const baseInfo = extractZodV4BaseType ( inner ) ;
51+
52+ if ( wrapperType === "default" ) {
53+ baseInfo . default = def . defaultValue ;
54+ }
55+ if ( wrapperType === "optional" ) {
56+ baseInfo . optional = true ;
57+ }
58+ return baseInfo ;
59+ }
60+
61+ // Handle base types
62+ switch ( wrapperType ) {
63+ case "string" :
64+ return { type : "string" } ;
65+ case "number" :
66+ return { type : "number" } ;
67+ case "boolean" :
68+ return { type : "boolean" } ;
69+ case "enum" :
70+ return {
71+ type : "string" ,
72+ enum : def . entries ? Object . keys ( def . entries ) : [ ] ,
73+ } ;
74+ case "array" :
75+ return { type : "array" } ;
76+ case "object" :
77+ return { type : "object" } ;
78+ case "union" :
79+ return { type : "union" } ;
80+ default :
81+ return { type : wrapperType || "unknown" } ;
82+ }
83+ }
84+
85+ /**
86+ * Convert a Zod v4 schema to JSON Schema format
87+ */
88+ function zodV4ToJsonSchema ( schema ) {
89+ const def = schema . _zod ?. def ;
90+ if ( ! def || def . type !== "object" ) {
91+ return { properties : { } , required : [ ] } ;
92+ }
93+
94+ const shape = def . shape || { } ;
95+ const properties = { } ;
96+ const required = [ ] ;
97+
98+ for ( const [ key , prop ] of Object . entries ( shape ) ) {
99+ const typeInfo = extractZodV4BaseType ( prop ) ;
100+ const description = prop . description || "" ;
101+
102+ properties [ key ] = {
103+ type : typeInfo . type ,
104+ description,
105+ } ;
106+
107+ if ( typeInfo . enum ) {
108+ properties [ key ] . enum = typeInfo . enum ;
109+ }
110+
111+ if ( typeInfo . default !== undefined ) {
112+ properties [ key ] . default = typeInfo . default ;
113+ }
114+
115+ // If not optional and no default, it's required
116+ if ( ! typeInfo . optional && typeInfo . default === undefined ) {
117+ required . push ( key ) ;
118+ }
119+ }
120+
121+ return { properties, required } ;
122+ }
123+
124+ /**
125+ * Convert any Zod schema to JSON Schema format
126+ */
127+ async function zodToJsonSchema ( schema ) {
128+ // Handle Zod v4
129+ if ( isZodV4 ( schema ) ) {
130+ return zodV4ToJsonSchema ( schema ) ;
131+ }
132+
133+ // Handle Zod v3 - dynamically import zod-to-json-schema
134+ if ( isZodV3 ( schema ) ) {
135+ try {
136+ const { zodToJsonSchema : zodV3ToJsonSchema } = await import (
137+ "zod-to-json-schema"
138+ ) ;
139+ return zodV3ToJsonSchema ( schema ) ;
140+ } catch ( err ) {
141+ console . warn ( "Warning: Could not use zod-to-json-schema:" , err . message ) ;
142+ return { properties : { } , required : [ ] } ;
143+ }
144+ }
145+
146+ // Already JSON Schema or unknown format
147+ return schema ;
148+ }
149+
14150/**
15151 * Check if value is an MCP tool object
16152 */
@@ -94,14 +230,12 @@ async function loadTools() {
94230 return tools . sort ( ( a , b ) => a . name . localeCompare ( b . name ) ) ;
95231}
96232
97- function renderSchema ( schema ) {
233+ async function renderSchema ( schema ) {
98234 if ( ! schema ) {
99235 return "_No parameters_" ;
100236 }
101237
102- // If this is a Zod schema, convert it to JSON Schema
103- const jsonSchema =
104- typeof schema . safeParse === "function" ? zodToJsonSchema ( schema ) : schema ;
238+ const jsonSchema = await zodToJsonSchema ( schema ) ;
105239
106240 const properties = jsonSchema . properties ?? { } ;
107241 const required = new Set ( jsonSchema . required ?? [ ] ) ;
@@ -150,27 +284,25 @@ function renderSchema(schema) {
150284 return table . trim ( ) ;
151285}
152286
153- function renderMarkdown ( tools ) {
287+ async function renderMarkdown ( tools ) {
154288 let md = "" ;
155289
156290 for ( const tool of tools ) {
157291 const schema = tool . parameters || tool . schema ;
158292
159293 md += `### \`${ tool . name } \`\n` ;
160294 md += `${ tool . description } \n\n` ;
161- md += `${ renderSchema ( schema ) } \n\n` ;
295+ md += `${ await renderSchema ( schema ) } \n\n` ;
162296 }
163297
164298 return md . trim ( ) ;
165299}
166300
167- function updateReadme ( { readme, tools } ) {
301+ function updateReadme ( { readme, toolsMd } ) {
168302 if ( ! readme . includes ( START ) || ! readme . includes ( END ) ) {
169303 throw new Error ( "README missing AUTO-GENERATED TOOLS markers" ) ;
170304 }
171305
172- const toolsMd = renderMarkdown ( tools ) ;
173-
174306 return readme . replace (
175307 new RegExp ( `${ START } [\\s\\S]*?${ END } ` , "m" ) ,
176308 `${ START } \n\n${ toolsMd } \n\n${ END } ` ,
@@ -186,7 +318,8 @@ async function main() {
186318 console . warn ( "Warning: No tools found!" ) ;
187319 }
188320
189- const updated = updateReadme ( { readme, tools } ) ;
321+ const toolsMd = await renderMarkdown ( tools ) ;
322+ const updated = updateReadme ( { readme, toolsMd } ) ;
190323
191324 fs . writeFileSync ( README_PATH , updated ) ;
192325 console . log ( `Synced ${ tools . length } MCP tools to README.md` ) ;
0 commit comments