mnehmos.trace.mcp 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (339) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +1662 -0
  3. package/dist/adapters/bootstrap.d.ts +29 -0
  4. package/dist/adapters/bootstrap.d.ts.map +1 -0
  5. package/dist/adapters/bootstrap.js +46 -0
  6. package/dist/adapters/bootstrap.js.map +1 -0
  7. package/dist/adapters/errors.d.ts +94 -0
  8. package/dist/adapters/errors.d.ts.map +1 -0
  9. package/dist/adapters/errors.js +107 -0
  10. package/dist/adapters/errors.js.map +1 -0
  11. package/dist/adapters/graphql/index.d.ts +9 -0
  12. package/dist/adapters/graphql/index.d.ts.map +1 -0
  13. package/dist/adapters/graphql/index.js +9 -0
  14. package/dist/adapters/graphql/index.js.map +1 -0
  15. package/dist/adapters/graphql/sdl-parser.d.ts +74 -0
  16. package/dist/adapters/graphql/sdl-parser.d.ts.map +1 -0
  17. package/dist/adapters/graphql/sdl-parser.js +559 -0
  18. package/dist/adapters/graphql/sdl-parser.js.map +1 -0
  19. package/dist/adapters/grpc/adapter.d.ts +76 -0
  20. package/dist/adapters/grpc/adapter.d.ts.map +1 -0
  21. package/dist/adapters/grpc/adapter.js +362 -0
  22. package/dist/adapters/grpc/adapter.js.map +1 -0
  23. package/dist/adapters/grpc/index.d.ts +10 -0
  24. package/dist/adapters/grpc/index.d.ts.map +1 -0
  25. package/dist/adapters/grpc/index.js +12 -0
  26. package/dist/adapters/grpc/index.js.map +1 -0
  27. package/dist/adapters/grpc/proto-parser.d.ts +76 -0
  28. package/dist/adapters/grpc/proto-parser.d.ts.map +1 -0
  29. package/dist/adapters/grpc/proto-parser.js +523 -0
  30. package/dist/adapters/grpc/proto-parser.js.map +1 -0
  31. package/dist/adapters/grpc/type-converter.d.ts +43 -0
  32. package/dist/adapters/grpc/type-converter.d.ts.map +1 -0
  33. package/dist/adapters/grpc/type-converter.js +270 -0
  34. package/dist/adapters/grpc/type-converter.js.map +1 -0
  35. package/dist/adapters/grpc/types.d.ts +85 -0
  36. package/dist/adapters/grpc/types.d.ts.map +1 -0
  37. package/dist/adapters/grpc/types.js +7 -0
  38. package/dist/adapters/grpc/types.js.map +1 -0
  39. package/dist/adapters/index.d.ts +39 -0
  40. package/dist/adapters/index.d.ts.map +1 -0
  41. package/dist/adapters/index.js +50 -0
  42. package/dist/adapters/index.js.map +1 -0
  43. package/dist/adapters/mcp.d.ts +23 -0
  44. package/dist/adapters/mcp.d.ts.map +1 -0
  45. package/dist/adapters/mcp.js +293 -0
  46. package/dist/adapters/mcp.js.map +1 -0
  47. package/dist/adapters/openapi/adapter.d.ts +213 -0
  48. package/dist/adapters/openapi/adapter.d.ts.map +1 -0
  49. package/dist/adapters/openapi/adapter.js +557 -0
  50. package/dist/adapters/openapi/adapter.js.map +1 -0
  51. package/dist/adapters/openapi/convert.d.ts +120 -0
  52. package/dist/adapters/openapi/convert.d.ts.map +1 -0
  53. package/dist/adapters/openapi/convert.js +363 -0
  54. package/dist/adapters/openapi/convert.js.map +1 -0
  55. package/dist/adapters/openapi/index.d.ts +39 -0
  56. package/dist/adapters/openapi/index.d.ts.map +1 -0
  57. package/dist/adapters/openapi/index.js +48 -0
  58. package/dist/adapters/openapi/index.js.map +1 -0
  59. package/dist/adapters/openapi/parser.d.ts +95 -0
  60. package/dist/adapters/openapi/parser.d.ts.map +1 -0
  61. package/dist/adapters/openapi/parser.js +171 -0
  62. package/dist/adapters/openapi/parser.js.map +1 -0
  63. package/dist/adapters/registry.d.ts +116 -0
  64. package/dist/adapters/registry.d.ts.map +1 -0
  65. package/dist/adapters/registry.js +246 -0
  66. package/dist/adapters/registry.js.map +1 -0
  67. package/dist/adapters/trpc/adapter.d.ts +159 -0
  68. package/dist/adapters/trpc/adapter.d.ts.map +1 -0
  69. package/dist/adapters/trpc/adapter.js +223 -0
  70. package/dist/adapters/trpc/adapter.js.map +1 -0
  71. package/dist/adapters/trpc/extractor.d.ts +218 -0
  72. package/dist/adapters/trpc/extractor.d.ts.map +1 -0
  73. package/dist/adapters/trpc/extractor.js +708 -0
  74. package/dist/adapters/trpc/extractor.js.map +1 -0
  75. package/dist/adapters/trpc/index.d.ts +31 -0
  76. package/dist/adapters/trpc/index.d.ts.map +1 -0
  77. package/dist/adapters/trpc/index.js +40 -0
  78. package/dist/adapters/trpc/index.js.map +1 -0
  79. package/dist/adapters/trpc/parser.d.ts +119 -0
  80. package/dist/adapters/trpc/parser.d.ts.map +1 -0
  81. package/dist/adapters/trpc/parser.js +128 -0
  82. package/dist/adapters/trpc/parser.js.map +1 -0
  83. package/dist/compare/index.d.ts +33 -0
  84. package/dist/compare/index.d.ts.map +1 -0
  85. package/dist/compare/index.js +261 -0
  86. package/dist/compare/index.js.map +1 -0
  87. package/dist/core/types.d.ts +188 -0
  88. package/dist/core/types.d.ts.map +1 -0
  89. package/dist/core/types.js +9 -0
  90. package/dist/core/types.js.map +1 -0
  91. package/dist/extract/index.d.ts +26 -0
  92. package/dist/extract/index.d.ts.map +1 -0
  93. package/dist/extract/index.js +44 -0
  94. package/dist/extract/index.js.map +1 -0
  95. package/dist/index.d.ts +9 -0
  96. package/dist/index.d.ts.map +1 -0
  97. package/dist/index.js +674 -0
  98. package/dist/index.js.map +1 -0
  99. package/dist/languages/base.d.ts +57 -0
  100. package/dist/languages/base.d.ts.map +1 -0
  101. package/dist/languages/base.js +6 -0
  102. package/dist/languages/base.js.map +1 -0
  103. package/dist/languages/bootstrap.d.ts +10 -0
  104. package/dist/languages/bootstrap.d.ts.map +1 -0
  105. package/dist/languages/bootstrap.js +25 -0
  106. package/dist/languages/bootstrap.js.map +1 -0
  107. package/dist/languages/go/handlers/chi.d.ts +24 -0
  108. package/dist/languages/go/handlers/chi.d.ts.map +1 -0
  109. package/dist/languages/go/handlers/chi.js +205 -0
  110. package/dist/languages/go/handlers/chi.js.map +1 -0
  111. package/dist/languages/go/handlers/gin.d.ts +24 -0
  112. package/dist/languages/go/handlers/gin.d.ts.map +1 -0
  113. package/dist/languages/go/handlers/gin.js +156 -0
  114. package/dist/languages/go/handlers/gin.js.map +1 -0
  115. package/dist/languages/go/handlers/stdlib.d.ts +19 -0
  116. package/dist/languages/go/handlers/stdlib.d.ts.map +1 -0
  117. package/dist/languages/go/handlers/stdlib.js +112 -0
  118. package/dist/languages/go/handlers/stdlib.js.map +1 -0
  119. package/dist/languages/go/index.d.ts +18 -0
  120. package/dist/languages/go/index.d.ts.map +1 -0
  121. package/dist/languages/go/index.js +20 -0
  122. package/dist/languages/go/index.js.map +1 -0
  123. package/dist/languages/go/parser.d.ts +33 -0
  124. package/dist/languages/go/parser.d.ts.map +1 -0
  125. package/dist/languages/go/parser.js +95 -0
  126. package/dist/languages/go/parser.js.map +1 -0
  127. package/dist/languages/go/struct-extractor.d.ts +59 -0
  128. package/dist/languages/go/struct-extractor.d.ts.map +1 -0
  129. package/dist/languages/go/struct-extractor.js +483 -0
  130. package/dist/languages/go/struct-extractor.js.map +1 -0
  131. package/dist/languages/go/tag-parser.d.ts +62 -0
  132. package/dist/languages/go/tag-parser.d.ts.map +1 -0
  133. package/dist/languages/go/tag-parser.js +108 -0
  134. package/dist/languages/go/tag-parser.js.map +1 -0
  135. package/dist/languages/go/type-converter.d.ts +32 -0
  136. package/dist/languages/go/type-converter.d.ts.map +1 -0
  137. package/dist/languages/go/type-converter.js +226 -0
  138. package/dist/languages/go/type-converter.js.map +1 -0
  139. package/dist/languages/go/types.d.ts +153 -0
  140. package/dist/languages/go/types.d.ts.map +1 -0
  141. package/dist/languages/go/types.js +6 -0
  142. package/dist/languages/go/types.js.map +1 -0
  143. package/dist/languages/import-resolver.d.ts +645 -0
  144. package/dist/languages/import-resolver.d.ts.map +1 -0
  145. package/dist/languages/import-resolver.js +1278 -0
  146. package/dist/languages/import-resolver.js.map +1 -0
  147. package/dist/languages/index.d.ts +34 -0
  148. package/dist/languages/index.d.ts.map +1 -0
  149. package/dist/languages/index.js +93 -0
  150. package/dist/languages/index.js.map +1 -0
  151. package/dist/languages/json-schema.d.ts +40 -0
  152. package/dist/languages/json-schema.d.ts.map +1 -0
  153. package/dist/languages/json-schema.js +188 -0
  154. package/dist/languages/json-schema.js.map +1 -0
  155. package/dist/languages/python-ast/index.d.ts +8 -0
  156. package/dist/languages/python-ast/index.d.ts.map +1 -0
  157. package/dist/languages/python-ast/index.js +7 -0
  158. package/dist/languages/python-ast/index.js.map +1 -0
  159. package/dist/languages/python-ast/parser.d.ts +174 -0
  160. package/dist/languages/python-ast/parser.d.ts.map +1 -0
  161. package/dist/languages/python-ast/parser.js +1205 -0
  162. package/dist/languages/python-ast/parser.js.map +1 -0
  163. package/dist/languages/python-ast/type-resolver.d.ts +75 -0
  164. package/dist/languages/python-ast/type-resolver.d.ts.map +1 -0
  165. package/dist/languages/python-ast/type-resolver.js +421 -0
  166. package/dist/languages/python-ast/type-resolver.js.map +1 -0
  167. package/dist/languages/python-ast/types.d.ts +216 -0
  168. package/dist/languages/python-ast/types.d.ts.map +1 -0
  169. package/dist/languages/python-ast/types.js +6 -0
  170. package/dist/languages/python-ast/types.js.map +1 -0
  171. package/dist/languages/python.d.ts +55 -0
  172. package/dist/languages/python.d.ts.map +1 -0
  173. package/dist/languages/python.js +311 -0
  174. package/dist/languages/python.js.map +1 -0
  175. package/dist/languages/typescript.d.ts +272 -0
  176. package/dist/languages/typescript.d.ts.map +1 -0
  177. package/dist/languages/typescript.js +1381 -0
  178. package/dist/languages/typescript.js.map +1 -0
  179. package/dist/patterns/base.d.ts +146 -0
  180. package/dist/patterns/base.d.ts.map +1 -0
  181. package/dist/patterns/base.js +89 -0
  182. package/dist/patterns/base.js.map +1 -0
  183. package/dist/patterns/errors.d.ts +172 -0
  184. package/dist/patterns/errors.d.ts.map +1 -0
  185. package/dist/patterns/errors.js +185 -0
  186. package/dist/patterns/errors.js.map +1 -0
  187. package/dist/patterns/extractors.d.ts +170 -0
  188. package/dist/patterns/extractors.d.ts.map +1 -0
  189. package/dist/patterns/extractors.js +305 -0
  190. package/dist/patterns/extractors.js.map +1 -0
  191. package/dist/patterns/graphql/apollo-client.d.ts +80 -0
  192. package/dist/patterns/graphql/apollo-client.d.ts.map +1 -0
  193. package/dist/patterns/graphql/apollo-client.js +800 -0
  194. package/dist/patterns/graphql/apollo-client.js.map +1 -0
  195. package/dist/patterns/graphql/apollo-server.d.ts +55 -0
  196. package/dist/patterns/graphql/apollo-server.d.ts.map +1 -0
  197. package/dist/patterns/graphql/apollo-server.js +523 -0
  198. package/dist/patterns/graphql/apollo-server.js.map +1 -0
  199. package/dist/patterns/graphql/index.d.ts +11 -0
  200. package/dist/patterns/graphql/index.d.ts.map +1 -0
  201. package/dist/patterns/graphql/index.js +12 -0
  202. package/dist/patterns/graphql/index.js.map +1 -0
  203. package/dist/patterns/graphql/types.d.ts +213 -0
  204. package/dist/patterns/graphql/types.d.ts.map +1 -0
  205. package/dist/patterns/graphql/types.js +16 -0
  206. package/dist/patterns/graphql/types.js.map +1 -0
  207. package/dist/patterns/http-clients/axios.d.ts +148 -0
  208. package/dist/patterns/http-clients/axios.d.ts.map +1 -0
  209. package/dist/patterns/http-clients/axios.js +652 -0
  210. package/dist/patterns/http-clients/axios.js.map +1 -0
  211. package/dist/patterns/http-clients/fetch.d.ts +88 -0
  212. package/dist/patterns/http-clients/fetch.d.ts.map +1 -0
  213. package/dist/patterns/http-clients/fetch.js +364 -0
  214. package/dist/patterns/http-clients/fetch.js.map +1 -0
  215. package/dist/patterns/http-clients/index.d.ts +36 -0
  216. package/dist/patterns/http-clients/index.d.ts.map +1 -0
  217. package/dist/patterns/http-clients/index.js +50 -0
  218. package/dist/patterns/http-clients/index.js.map +1 -0
  219. package/dist/patterns/http-clients/property-access.d.ts +46 -0
  220. package/dist/patterns/http-clients/property-access.d.ts.map +1 -0
  221. package/dist/patterns/http-clients/property-access.js +818 -0
  222. package/dist/patterns/http-clients/property-access.js.map +1 -0
  223. package/dist/patterns/http-clients/type-inference.d.ts +48 -0
  224. package/dist/patterns/http-clients/type-inference.d.ts.map +1 -0
  225. package/dist/patterns/http-clients/type-inference.js +293 -0
  226. package/dist/patterns/http-clients/type-inference.js.map +1 -0
  227. package/dist/patterns/http-clients/types.d.ts +168 -0
  228. package/dist/patterns/http-clients/types.d.ts.map +1 -0
  229. package/dist/patterns/http-clients/types.js +10 -0
  230. package/dist/patterns/http-clients/types.js.map +1 -0
  231. package/dist/patterns/http-clients/url-extractor.d.ts +53 -0
  232. package/dist/patterns/http-clients/url-extractor.d.ts.map +1 -0
  233. package/dist/patterns/http-clients/url-extractor.js +338 -0
  234. package/dist/patterns/http-clients/url-extractor.js.map +1 -0
  235. package/dist/patterns/index.d.ts +44 -0
  236. package/dist/patterns/index.d.ts.map +1 -0
  237. package/dist/patterns/index.js +49 -0
  238. package/dist/patterns/index.js.map +1 -0
  239. package/dist/patterns/python/aiohttp.d.ts +21 -0
  240. package/dist/patterns/python/aiohttp.d.ts.map +1 -0
  241. package/dist/patterns/python/aiohttp.js +188 -0
  242. package/dist/patterns/python/aiohttp.js.map +1 -0
  243. package/dist/patterns/python/httpx.d.ts +20 -0
  244. package/dist/patterns/python/httpx.d.ts.map +1 -0
  245. package/dist/patterns/python/httpx.js +183 -0
  246. package/dist/patterns/python/httpx.js.map +1 -0
  247. package/dist/patterns/python/index.d.ts +32 -0
  248. package/dist/patterns/python/index.d.ts.map +1 -0
  249. package/dist/patterns/python/index.js +63 -0
  250. package/dist/patterns/python/index.js.map +1 -0
  251. package/dist/patterns/python/property-access.d.ts +27 -0
  252. package/dist/patterns/python/property-access.d.ts.map +1 -0
  253. package/dist/patterns/python/property-access.js +132 -0
  254. package/dist/patterns/python/property-access.js.map +1 -0
  255. package/dist/patterns/python/requests.d.ts +19 -0
  256. package/dist/patterns/python/requests.d.ts.map +1 -0
  257. package/dist/patterns/python/requests.js +239 -0
  258. package/dist/patterns/python/requests.js.map +1 -0
  259. package/dist/patterns/python/types.d.ts +95 -0
  260. package/dist/patterns/python/types.d.ts.map +1 -0
  261. package/dist/patterns/python/types.js +43 -0
  262. package/dist/patterns/python/types.js.map +1 -0
  263. package/dist/patterns/registry.d.ts +181 -0
  264. package/dist/patterns/registry.d.ts.map +1 -0
  265. package/dist/patterns/registry.js +304 -0
  266. package/dist/patterns/registry.js.map +1 -0
  267. package/dist/patterns/rest/express.d.ts +78 -0
  268. package/dist/patterns/rest/express.d.ts.map +1 -0
  269. package/dist/patterns/rest/express.js +289 -0
  270. package/dist/patterns/rest/express.js.map +1 -0
  271. package/dist/patterns/rest/fastify.d.ts +93 -0
  272. package/dist/patterns/rest/fastify.d.ts.map +1 -0
  273. package/dist/patterns/rest/fastify.js +420 -0
  274. package/dist/patterns/rest/fastify.js.map +1 -0
  275. package/dist/patterns/rest/index.d.ts +31 -0
  276. package/dist/patterns/rest/index.d.ts.map +1 -0
  277. package/dist/patterns/rest/index.js +45 -0
  278. package/dist/patterns/rest/index.js.map +1 -0
  279. package/dist/patterns/rest/middleware.d.ts +25 -0
  280. package/dist/patterns/rest/middleware.d.ts.map +1 -0
  281. package/dist/patterns/rest/middleware.js +219 -0
  282. package/dist/patterns/rest/middleware.js.map +1 -0
  283. package/dist/patterns/rest/path-parser.d.ts +50 -0
  284. package/dist/patterns/rest/path-parser.d.ts.map +1 -0
  285. package/dist/patterns/rest/path-parser.js +137 -0
  286. package/dist/patterns/rest/path-parser.js.map +1 -0
  287. package/dist/patterns/rest/response-inference.d.ts +44 -0
  288. package/dist/patterns/rest/response-inference.d.ts.map +1 -0
  289. package/dist/patterns/rest/response-inference.js +218 -0
  290. package/dist/patterns/rest/response-inference.js.map +1 -0
  291. package/dist/patterns/rest/types.d.ts +102 -0
  292. package/dist/patterns/rest/types.d.ts.map +1 -0
  293. package/dist/patterns/rest/types.js +10 -0
  294. package/dist/patterns/rest/types.js.map +1 -0
  295. package/dist/patterns/types.d.ts +105 -0
  296. package/dist/patterns/types.d.ts.map +1 -0
  297. package/dist/patterns/types.js +11 -0
  298. package/dist/patterns/types.js.map +1 -0
  299. package/dist/report/index.d.ts +11 -0
  300. package/dist/report/index.d.ts.map +1 -0
  301. package/dist/report/index.js +55 -0
  302. package/dist/report/index.js.map +1 -0
  303. package/dist/tools/contract-comments.d.ts +48 -0
  304. package/dist/tools/contract-comments.d.ts.map +1 -0
  305. package/dist/tools/contract-comments.js +130 -0
  306. package/dist/tools/contract-comments.js.map +1 -0
  307. package/dist/tools/index.d.ts +6 -0
  308. package/dist/tools/index.d.ts.map +1 -0
  309. package/dist/tools/index.js +6 -0
  310. package/dist/tools/index.js.map +1 -0
  311. package/dist/tools/scaffold.d.ts +38 -0
  312. package/dist/tools/scaffold.d.ts.map +1 -0
  313. package/dist/tools/scaffold.js +373 -0
  314. package/dist/tools/scaffold.js.map +1 -0
  315. package/dist/trace/index.d.ts +28 -0
  316. package/dist/trace/index.d.ts.map +1 -0
  317. package/dist/trace/index.js +45 -0
  318. package/dist/trace/index.js.map +1 -0
  319. package/dist/types.d.ts +135 -0
  320. package/dist/types.d.ts.map +1 -0
  321. package/dist/types.js +22 -0
  322. package/dist/types.js.map +1 -0
  323. package/dist/watch/cache.d.ts +41 -0
  324. package/dist/watch/cache.d.ts.map +1 -0
  325. package/dist/watch/cache.js +230 -0
  326. package/dist/watch/cache.js.map +1 -0
  327. package/dist/watch/index.d.ts +9 -0
  328. package/dist/watch/index.d.ts.map +1 -0
  329. package/dist/watch/index.js +7 -0
  330. package/dist/watch/index.js.map +1 -0
  331. package/dist/watch/project.d.ts +128 -0
  332. package/dist/watch/project.d.ts.map +1 -0
  333. package/dist/watch/project.js +152 -0
  334. package/dist/watch/project.js.map +1 -0
  335. package/dist/watch/watcher.d.ts +76 -0
  336. package/dist/watch/watcher.d.ts.map +1 -0
  337. package/dist/watch/watcher.js +235 -0
  338. package/dist/watch/watcher.js.map +1 -0
  339. package/package.json +70 -0
@@ -0,0 +1,1381 @@
1
+ /**
2
+ * 🔷 TypeScript Language Parser
3
+ *
4
+ * Extracts MCP schemas from TypeScript source files using ts-morph.
5
+ * This is an enhanced parser that supports multiple extraction patterns
6
+ * for both producer schemas (tool definitions) and type definitions.
7
+ *
8
+ * Supported extraction patterns:
9
+ * 1. **server.tool()** - FastMCP / MCP SDK pattern
10
+ * 2. **Registry Pattern** - Object literal with ToolDefinition entries (ChatRPG)
11
+ * 3. **Exported Zod Schemas** - Named *Schema exports for tracing
12
+ * 4. **TypeScript Interfaces** - Exported interface declarations
13
+ * 5. **Type Aliases** - Exported type definitions
14
+ * 6. **Enums** - Exported enum declarations
15
+ *
16
+ * @module languages/typescript
17
+ *
18
+ * @example
19
+ * ```typescript
20
+ * import { TypeScriptParser } from './languages/typescript.js';
21
+ *
22
+ * const parser = new TypeScriptParser();
23
+ *
24
+ * // Extract tool definitions (Zod schemas)
25
+ * const producers = await parser.extractSchemas({ rootDir: './src' });
26
+ *
27
+ * // Extract interfaces and types
28
+ * const interfaces = await parser.extractInterfaces({ rootDir: './src' });
29
+ *
30
+ * // Extract all schemas
31
+ * const all = await parser.extractAll({ rootDir: './src' });
32
+ * ```
33
+ */
34
+ import { Project, SyntaxKind, Node, ts } from 'ts-morph';
35
+ // Common registry patterns found in MCP servers
36
+ const REGISTRY_PATTERNS = [
37
+ {
38
+ name: 'toolRegistry',
39
+ type: 'record',
40
+ entryShape: {
41
+ nameField: 'name',
42
+ descriptionField: 'description',
43
+ schemaField: 'inputSchema',
44
+ handlerField: 'handler',
45
+ },
46
+ },
47
+ {
48
+ name: 'tools',
49
+ type: 'array',
50
+ entryShape: {
51
+ nameField: 'name',
52
+ descriptionField: 'description',
53
+ schemaField: 'schema',
54
+ handlerField: 'handler',
55
+ },
56
+ },
57
+ {
58
+ name: 'TOOLS',
59
+ type: 'record',
60
+ entryShape: {
61
+ nameField: 'name',
62
+ descriptionField: 'description',
63
+ schemaField: 'inputSchema',
64
+ handlerField: 'handler',
65
+ },
66
+ },
67
+ ];
68
+ // ============================================================================
69
+ // Parser Implementation
70
+ // ============================================================================
71
+ export class TypeScriptParser {
72
+ name = 'typescript';
73
+ filePatterns = ['**/*.ts', '**/*.tsx'];
74
+ // Track found Zod schemas for cross-file resolution
75
+ zodSchemaCache = new Map();
76
+ // ==========================================================================
77
+ // Producer Schema Extraction
78
+ // ==========================================================================
79
+ async extractSchemas(options) {
80
+ if (process.env.DEBUG_TRACE_MCP) {
81
+ console.error(`[TypeScript] Scanning: ${options.rootDir}`);
82
+ }
83
+ const project = new Project({
84
+ tsConfigFilePath: undefined,
85
+ skipAddingFilesFromTsConfig: true,
86
+ });
87
+ const patterns = options.include || this.filePatterns;
88
+ const excludePatterns = options.exclude || ['**/node_modules/**', '**/dist/**'];
89
+ project.addSourceFilesAtPaths(patterns.map(p => `${options.rootDir}/${p}`));
90
+ const schemas = [];
91
+ // First pass: collect all exported Zod schemas for cross-reference
92
+ for (const sourceFile of project.getSourceFiles()) {
93
+ const filePath = sourceFile.getFilePath();
94
+ if (excludePatterns.some(pattern => filePath.includes(pattern.replace('**/', '')))) {
95
+ continue;
96
+ }
97
+ this.collectZodSchemas(sourceFile, filePath);
98
+ }
99
+ // Second pass: extract tool definitions
100
+ for (const sourceFile of project.getSourceFiles()) {
101
+ const filePath = sourceFile.getFilePath();
102
+ if (excludePatterns.some(pattern => filePath.includes(pattern.replace('**/', '')))) {
103
+ continue;
104
+ }
105
+ // Method 1: server.tool() calls (FastMCP pattern)
106
+ const toolCalls = this.findToolCalls(sourceFile);
107
+ for (const toolCall of toolCalls) {
108
+ const schema = this.parseToolCall(toolCall, filePath);
109
+ if (schema) {
110
+ schemas.push(schema);
111
+ }
112
+ }
113
+ // Method 2: Registry pattern (ChatRPG pattern)
114
+ const registrySchemas = this.extractFromRegistry(sourceFile, filePath);
115
+ schemas.push(...registrySchemas);
116
+ // Method 3: Direct exports with tool-like shape
117
+ const exportedSchemas = this.extractFromExports(sourceFile, filePath);
118
+ schemas.push(...exportedSchemas);
119
+ }
120
+ if (process.env.DEBUG_TRACE_MCP) {
121
+ console.error(`[TypeScript] Found ${schemas.length} tool definitions`);
122
+ }
123
+ return schemas;
124
+ }
125
+ // ==========================================================================
126
+ // Method 1: server.tool() Pattern (Original)
127
+ // ==========================================================================
128
+ findToolCalls(sourceFile) {
129
+ const toolCalls = [];
130
+ sourceFile.forEachDescendant((node) => {
131
+ if (Node.isCallExpression(node)) {
132
+ const expression = node.getExpression();
133
+ if (Node.isPropertyAccessExpression(expression)) {
134
+ const methodName = expression.getName();
135
+ if (methodName === 'tool') {
136
+ toolCalls.push(node);
137
+ }
138
+ }
139
+ }
140
+ });
141
+ return toolCalls;
142
+ }
143
+ parseToolCall(callExpr, filePath) {
144
+ const args = callExpr.getArguments();
145
+ if (args.length < 3) {
146
+ if (process.env.DEBUG_TRACE_MCP) {
147
+ console.error(`[TypeScript] Skipping tool call with insufficient args at ${filePath}:${callExpr.getStartLineNumber()}`);
148
+ }
149
+ return null;
150
+ }
151
+ const nameArg = args[0];
152
+ let toolName = 'unknown';
153
+ if (Node.isStringLiteral(nameArg)) {
154
+ toolName = nameArg.getLiteralValue();
155
+ }
156
+ let description;
157
+ let schemaArg;
158
+ const secondArg = args[1];
159
+ if (Node.isStringLiteral(secondArg)) {
160
+ description = secondArg.getLiteralValue();
161
+ schemaArg = args[2];
162
+ }
163
+ else {
164
+ schemaArg = secondArg;
165
+ }
166
+ const inputSchema = this.parseZodSchema(schemaArg);
167
+ const location = {
168
+ file: filePath,
169
+ line: callExpr.getStartLineNumber(),
170
+ column: callExpr.getStartLinePos(),
171
+ };
172
+ return {
173
+ toolName,
174
+ description,
175
+ inputSchema,
176
+ outputSchema: this.getMcpOutputSchema(),
177
+ location,
178
+ };
179
+ }
180
+ // ==========================================================================
181
+ // Method 2: Registry Pattern (ChatRPG)
182
+ // ==========================================================================
183
+ extractFromRegistry(sourceFile, filePath) {
184
+ const schemas = [];
185
+ // Look for exported object literals that match registry patterns
186
+ sourceFile.forEachDescendant((node) => {
187
+ if (Node.isVariableDeclaration(node)) {
188
+ const varName = node.getName();
189
+ const initializer = node.getInitializer();
190
+ // Check if this matches a known registry pattern
191
+ const pattern = REGISTRY_PATTERNS.find(p => p.name === varName);
192
+ if (pattern && initializer && Node.isObjectLiteralExpression(initializer)) {
193
+ if (process.env.DEBUG_TRACE_MCP) {
194
+ console.error(`[TypeScript] Found registry pattern: ${varName}`);
195
+ }
196
+ const registrySchemas = this.parseRegistryObject(initializer, pattern, filePath);
197
+ schemas.push(...registrySchemas);
198
+ return; // Found, skip to next
199
+ }
200
+ // Also check for objects with tool-like entries even if not matching named patterns
201
+ if (initializer && Node.isObjectLiteralExpression(initializer)) {
202
+ const inferredSchemas = this.inferRegistryFromShape(initializer, varName, filePath);
203
+ if (inferredSchemas.length > 0) {
204
+ if (process.env.DEBUG_TRACE_MCP) {
205
+ console.error(`[TypeScript] Inferred registry from shape: ${varName} (${inferredSchemas.length} tools)`);
206
+ }
207
+ schemas.push(...inferredSchemas);
208
+ }
209
+ }
210
+ }
211
+ });
212
+ return schemas;
213
+ }
214
+ parseRegistryObject(obj, pattern, filePath) {
215
+ const schemas = [];
216
+ for (const prop of obj.getProperties()) {
217
+ if (!Node.isPropertyAssignment(prop))
218
+ continue;
219
+ const entryName = prop.getName();
220
+ const entryValue = prop.getInitializer();
221
+ if (!entryValue || !Node.isObjectLiteralExpression(entryValue))
222
+ continue;
223
+ const toolDef = this.parseToolDefinition(entryValue, pattern.entryShape, filePath, prop.getStartLineNumber());
224
+ if (toolDef) {
225
+ // Use the property key as fallback name
226
+ if (toolDef.toolName === 'unknown') {
227
+ toolDef.toolName = entryName;
228
+ }
229
+ schemas.push(toolDef);
230
+ }
231
+ }
232
+ return schemas;
233
+ }
234
+ parseToolDefinition(obj, shape, filePath, lineNumber) {
235
+ let toolName = 'unknown';
236
+ let description;
237
+ let inputSchema = { type: 'object' };
238
+ for (const prop of obj.getProperties()) {
239
+ if (!Node.isPropertyAssignment(prop))
240
+ continue;
241
+ const propName = prop.getName();
242
+ const init = prop.getInitializer();
243
+ if (!init)
244
+ continue;
245
+ // Extract name
246
+ if (propName === shape.nameField && Node.isStringLiteral(init)) {
247
+ toolName = init.getLiteralValue();
248
+ }
249
+ // Extract description
250
+ if (propName === shape.descriptionField && Node.isStringLiteral(init)) {
251
+ description = init.getLiteralValue();
252
+ }
253
+ // Extract schema
254
+ if (propName === shape.schemaField) {
255
+ inputSchema = this.extractInputSchema(init);
256
+ }
257
+ }
258
+ // Skip if we couldn't find a name or schema
259
+ if (toolName === 'unknown' && Object.keys(inputSchema.properties || {}).length === 0) {
260
+ return null;
261
+ }
262
+ return {
263
+ toolName,
264
+ description,
265
+ inputSchema,
266
+ outputSchema: this.getMcpOutputSchema(),
267
+ location: {
268
+ file: filePath,
269
+ line: lineNumber,
270
+ },
271
+ };
272
+ }
273
+ /**
274
+ * Infer registry pattern from object shape
275
+ * Checks if object entries look like tool definitions
276
+ */
277
+ inferRegistryFromShape(obj, varName, filePath) {
278
+ const schemas = [];
279
+ const props = obj.getProperties();
280
+ // Must have at least one property
281
+ if (props.length === 0)
282
+ return schemas;
283
+ // Check first entry to see if it looks like a tool definition
284
+ const firstProp = props[0];
285
+ if (!Node.isPropertyAssignment(firstProp))
286
+ return schemas;
287
+ const firstInit = firstProp.getInitializer();
288
+ if (!firstInit || !Node.isObjectLiteralExpression(firstInit))
289
+ return schemas;
290
+ // Check if this looks like a ToolDefinition
291
+ const firstShape = this.detectToolDefinitionShape(firstInit);
292
+ if (!firstShape)
293
+ return schemas;
294
+ if (process.env.DEBUG_TRACE_MCP) {
295
+ console.error(`[TypeScript] Detected tool definition shape in ${varName}: ${JSON.stringify(firstShape)}`);
296
+ }
297
+ // Parse all entries using detected shape
298
+ for (const prop of props) {
299
+ if (!Node.isPropertyAssignment(prop))
300
+ continue;
301
+ const entryName = prop.getName();
302
+ const entryValue = prop.getInitializer();
303
+ if (!entryValue || !Node.isObjectLiteralExpression(entryValue))
304
+ continue;
305
+ const toolDef = this.parseToolDefinition(entryValue, firstShape, filePath, prop.getStartLineNumber());
306
+ if (toolDef) {
307
+ if (toolDef.toolName === 'unknown') {
308
+ toolDef.toolName = entryName;
309
+ }
310
+ schemas.push(toolDef);
311
+ }
312
+ }
313
+ return schemas;
314
+ }
315
+ /**
316
+ * Detect the shape of a tool definition from an object literal
317
+ */
318
+ detectToolDefinitionShape(obj) {
319
+ const propNames = new Set();
320
+ for (const prop of obj.getProperties()) {
321
+ if (Node.isPropertyAssignment(prop)) {
322
+ propNames.add(prop.getName());
323
+ }
324
+ }
325
+ // Must have name, description, and some kind of schema/handler
326
+ const hasName = propNames.has('name') || propNames.has('toolName');
327
+ const hasDescription = propNames.has('description');
328
+ const hasSchema = propNames.has('inputSchema') || propNames.has('schema') || propNames.has('parameters');
329
+ const hasHandler = propNames.has('handler') || propNames.has('execute') || propNames.has('run');
330
+ // Need at least name and (schema or handler)
331
+ if (!hasName && !(hasSchema || hasHandler)) {
332
+ return null;
333
+ }
334
+ return {
335
+ nameField: propNames.has('name') ? 'name' : 'toolName',
336
+ descriptionField: 'description',
337
+ schemaField: propNames.has('inputSchema') ? 'inputSchema' : (propNames.has('schema') ? 'schema' : 'parameters'),
338
+ handlerField: propNames.has('handler') ? 'handler' : (propNames.has('execute') ? 'execute' : 'run'),
339
+ };
340
+ }
341
+ /**
342
+ * Extract input schema from various formats
343
+ */
344
+ extractInputSchema(node) {
345
+ const text = node.getText();
346
+ // Case 1: Direct object literal (already JSON Schema)
347
+ if (Node.isObjectLiteralExpression(node)) {
348
+ return this.parseJsonSchemaLiteral(node);
349
+ }
350
+ // Case 2: toJsonSchema(zodSchema) call
351
+ if (Node.isCallExpression(node)) {
352
+ const expr = node.getExpression();
353
+ const exprText = expr.getText();
354
+ // toJsonSchema(createCharacterSchema) pattern
355
+ if (exprText === 'toJsonSchema' || exprText.endsWith('.toJsonSchema')) {
356
+ const args = node.getArguments();
357
+ if (args.length > 0) {
358
+ const schemaRef = args[0].getText();
359
+ // Look up in cache
360
+ const cached = this.zodSchemaCache.get(schemaRef);
361
+ if (cached) {
362
+ return cached.schema;
363
+ }
364
+ // Return reference for later resolution
365
+ return { $ref: `#/definitions/${schemaRef}` };
366
+ }
367
+ }
368
+ }
369
+ // Case 3: Identifier referencing a Zod schema
370
+ if (Node.isIdentifier(node)) {
371
+ const schemaName = node.getText();
372
+ const cached = this.zodSchemaCache.get(schemaName);
373
+ if (cached) {
374
+ return cached.schema;
375
+ }
376
+ // Check if it's a *Schema naming convention
377
+ if (schemaName.endsWith('Schema')) {
378
+ return { $ref: `#/definitions/${schemaName}` };
379
+ }
380
+ }
381
+ // Case 4: Inline Zod schema (z.object(...))
382
+ if (text.startsWith('z.object(') || text.includes('z.object(')) {
383
+ return this.parseZodSchema(node);
384
+ }
385
+ return { type: 'object' };
386
+ }
387
+ /**
388
+ * Parse a JSON Schema object literal
389
+ */
390
+ parseJsonSchemaLiteral(obj) {
391
+ const schema = {};
392
+ for (const prop of obj.getProperties()) {
393
+ if (!Node.isPropertyAssignment(prop))
394
+ continue;
395
+ const propName = prop.getName();
396
+ const init = prop.getInitializer();
397
+ if (!init)
398
+ continue;
399
+ switch (propName) {
400
+ case 'type':
401
+ if (Node.isStringLiteral(init)) {
402
+ schema.type = init.getLiteralValue();
403
+ }
404
+ break;
405
+ case 'properties':
406
+ if (Node.isObjectLiteralExpression(init)) {
407
+ schema.properties = {};
408
+ for (const subProp of init.getProperties()) {
409
+ if (Node.isPropertyAssignment(subProp)) {
410
+ const subName = subProp.getName();
411
+ const subInit = subProp.getInitializer();
412
+ if (subInit && Node.isObjectLiteralExpression(subInit)) {
413
+ schema.properties[subName] = this.parseJsonSchemaLiteral(subInit);
414
+ }
415
+ }
416
+ }
417
+ }
418
+ break;
419
+ case 'required':
420
+ if (Node.isArrayLiteralExpression(init)) {
421
+ schema.required = init.getElements()
422
+ .filter(e => Node.isStringLiteral(e))
423
+ .map(e => e.getLiteralValue());
424
+ }
425
+ break;
426
+ case 'description':
427
+ if (Node.isStringLiteral(init)) {
428
+ schema.description = init.getLiteralValue();
429
+ }
430
+ break;
431
+ }
432
+ }
433
+ return schema;
434
+ }
435
+ // ==========================================================================
436
+ // Method 3: Exported Zod Schemas
437
+ // ==========================================================================
438
+ /**
439
+ * Collect all exported Zod schemas for cross-reference
440
+ */
441
+ collectZodSchemas(sourceFile, filePath) {
442
+ sourceFile.forEachDescendant((node) => {
443
+ if (Node.isVariableDeclaration(node)) {
444
+ const varName = node.getName();
445
+ // Look for *Schema naming convention
446
+ if (varName.endsWith('Schema')) {
447
+ const init = node.getInitializer();
448
+ if (init) {
449
+ const text = init.getText();
450
+ // Check if it's a Zod schema definition
451
+ if (text.includes('z.object(') || text.includes('z.union(') ||
452
+ text.includes('z.string()') || text.includes('z.number()')) {
453
+ const parsedSchema = this.parseZodSchema(init);
454
+ this.zodSchemaCache.set(varName, {
455
+ schema: parsedSchema,
456
+ location: {
457
+ file: filePath,
458
+ line: node.getStartLineNumber(),
459
+ },
460
+ });
461
+ }
462
+ }
463
+ }
464
+ }
465
+ });
466
+ }
467
+ /**
468
+ * Extract tool definitions from exports
469
+ */
470
+ extractFromExports(sourceFile, filePath) {
471
+ const schemas = [];
472
+ // Look for exported functions that match tool handler signatures
473
+ for (const exportDecl of sourceFile.getExportedDeclarations()) {
474
+ const [name, declarations] = exportDecl;
475
+ for (const decl of declarations) {
476
+ // Check for exported async functions with matching schema
477
+ if (Node.isFunctionDeclaration(decl) && decl.isAsync()) {
478
+ const schemaName = `${name}Schema`;
479
+ const cached = this.zodSchemaCache.get(schemaName);
480
+ if (cached) {
481
+ // Get JSDoc description if available
482
+ const jsDocs = decl.getJsDocs();
483
+ const description = jsDocs.length > 0
484
+ ? jsDocs[0].getDescription().trim()
485
+ : undefined;
486
+ schemas.push({
487
+ toolName: name,
488
+ description,
489
+ inputSchema: cached.schema,
490
+ outputSchema: this.getMcpOutputSchema(),
491
+ location: {
492
+ file: filePath,
493
+ line: decl.getStartLineNumber(),
494
+ },
495
+ });
496
+ }
497
+ }
498
+ }
499
+ }
500
+ return schemas;
501
+ }
502
+ // ==========================================================================
503
+ // Zod Schema Parsing (Enhanced)
504
+ // ==========================================================================
505
+ parseZodSchema(node) {
506
+ const schema = {
507
+ type: 'object',
508
+ properties: {},
509
+ required: [],
510
+ };
511
+ // Handle z.object({...})
512
+ if (Node.isCallExpression(node)) {
513
+ const expr = node.getExpression();
514
+ const exprText = expr.getText();
515
+ if (exprText === 'z.object' || exprText.endsWith('.object')) {
516
+ const args = node.getArguments();
517
+ if (args.length > 0 && Node.isObjectLiteralExpression(args[0])) {
518
+ return this.parseZodObjectContent(args[0]);
519
+ }
520
+ }
521
+ // Handle z.union([...])
522
+ if (exprText === 'z.union' || exprText.endsWith('.union')) {
523
+ return this.parseZodUnion(node);
524
+ }
525
+ }
526
+ // Handle direct object literal (for older Zod style)
527
+ if (Node.isObjectLiteralExpression(node)) {
528
+ return this.parseZodObjectContent(node);
529
+ }
530
+ return schema;
531
+ }
532
+ parseZodObjectContent(obj) {
533
+ const schema = {
534
+ type: 'object',
535
+ properties: {},
536
+ required: [],
537
+ };
538
+ for (const prop of obj.getProperties()) {
539
+ if (!Node.isPropertyAssignment(prop))
540
+ continue;
541
+ const propName = prop.getName();
542
+ const initializer = prop.getInitializer();
543
+ if (!initializer)
544
+ continue;
545
+ const propSchema = this.parseZodType(initializer);
546
+ schema.properties[propName] = propSchema;
547
+ const initText = initializer.getText();
548
+ if (!initText.includes('.optional()') && !initText.includes('.nullish()')) {
549
+ schema.required.push(propName);
550
+ }
551
+ }
552
+ return schema;
553
+ }
554
+ parseZodUnion(node) {
555
+ const args = node.getArguments();
556
+ if (args.length === 0)
557
+ return { type: 'object' };
558
+ const firstArg = args[0];
559
+ if (!Node.isArrayLiteralExpression(firstArg))
560
+ return { type: 'object' };
561
+ const variants = [];
562
+ for (const element of firstArg.getElements()) {
563
+ variants.push(this.parseZodType(element));
564
+ }
565
+ if (variants.length === 1) {
566
+ return variants[0];
567
+ }
568
+ return { anyOf: variants };
569
+ }
570
+ parseZodType(node) {
571
+ const text = node.getText();
572
+ // z.string()
573
+ if (text.startsWith('z.string()') || text.match(/^z\.string\(\)/)) {
574
+ const schema = { type: 'string' };
575
+ // Extract description
576
+ const descMatch = text.match(/\.describe\(['"](.+?)['"]\)/);
577
+ if (descMatch) {
578
+ schema.description = descMatch[1];
579
+ }
580
+ // Extract min/max
581
+ const minMatch = text.match(/\.min\((\d+)\)/);
582
+ const maxMatch = text.match(/\.max\((\d+)\)/);
583
+ if (minMatch)
584
+ schema.minLength = parseInt(minMatch[1]);
585
+ if (maxMatch)
586
+ schema.maxLength = parseInt(maxMatch[1]);
587
+ return schema;
588
+ }
589
+ // z.number()
590
+ if (text.startsWith('z.number()') || text.match(/^z\.number\(\)/)) {
591
+ const schema = { type: 'number' };
592
+ const minMatch = text.match(/\.min\((-?\d+)\)/);
593
+ const maxMatch = text.match(/\.max\((-?\d+)\)/);
594
+ if (minMatch)
595
+ schema.minimum = parseInt(minMatch[1]);
596
+ if (maxMatch)
597
+ schema.maximum = parseInt(maxMatch[1]);
598
+ return schema;
599
+ }
600
+ // z.boolean()
601
+ if (text.startsWith('z.boolean()')) {
602
+ return { type: 'boolean' };
603
+ }
604
+ // z.enum([...])
605
+ if (text.includes('z.enum(')) {
606
+ const enumMatch = text.match(/z\.enum\(\[(.*?)\]\)/s);
607
+ if (enumMatch) {
608
+ const values = enumMatch[1]
609
+ .split(',')
610
+ .map(v => v.trim().replace(/['"]/g, ''))
611
+ .filter(v => v.length > 0);
612
+ return { type: 'string', enum: values };
613
+ }
614
+ }
615
+ // z.array(...)
616
+ if (text.startsWith('z.array(')) {
617
+ return { type: 'array' };
618
+ }
619
+ // z.object(...)
620
+ if (text.startsWith('z.object(')) {
621
+ if (Node.isCallExpression(node)) {
622
+ return this.parseZodSchema(node);
623
+ }
624
+ return { type: 'object' };
625
+ }
626
+ // z.literal(...)
627
+ if (text.includes('z.literal(')) {
628
+ const match = text.match(/z\.literal\(['"](.+?)['"]\)/);
629
+ if (match) {
630
+ return { type: 'string', const: match[1] };
631
+ }
632
+ }
633
+ // Reference to another schema
634
+ if (text.endsWith('Schema') || this.zodSchemaCache.has(text)) {
635
+ const cached = this.zodSchemaCache.get(text);
636
+ if (cached) {
637
+ return cached.schema;
638
+ }
639
+ return { $ref: `#/definitions/${text}` };
640
+ }
641
+ return { type: 'unknown' };
642
+ }
643
+ /**
644
+ * Standard MCP tool output schema
645
+ */
646
+ getMcpOutputSchema() {
647
+ return {
648
+ type: 'object',
649
+ properties: {
650
+ content: {
651
+ type: 'array',
652
+ items: {
653
+ type: 'object',
654
+ properties: {
655
+ type: { type: 'string' },
656
+ text: { type: 'string' },
657
+ },
658
+ },
659
+ },
660
+ },
661
+ };
662
+ }
663
+ // ==========================================================================
664
+ // Consumer Usage Tracing
665
+ // ==========================================================================
666
+ async traceUsage(options) {
667
+ if (process.env.DEBUG_TRACE_MCP) {
668
+ console.error(`[TypeScript] Tracing: ${options.rootDir}`);
669
+ }
670
+ const project = new Project({
671
+ skipAddingFilesFromTsConfig: true,
672
+ });
673
+ const patterns = options.include || this.filePatterns;
674
+ const excludePatterns = options.exclude || ['**/node_modules/**', '**/dist/**'];
675
+ project.addSourceFilesAtPaths(patterns.map(p => `${options.rootDir}/${p}`));
676
+ const schemas = [];
677
+ for (const sourceFile of project.getSourceFiles()) {
678
+ const filePath = sourceFile.getFilePath();
679
+ if (excludePatterns.some(pattern => filePath.includes(pattern.replace('**/', '')))) {
680
+ continue;
681
+ }
682
+ const fileSchemas = this.traceFile(sourceFile, filePath);
683
+ schemas.push(...fileSchemas);
684
+ }
685
+ if (process.env.DEBUG_TRACE_MCP) {
686
+ console.error(`[TypeScript] Found ${schemas.length} tool calls`);
687
+ }
688
+ return schemas;
689
+ }
690
+ traceFile(sourceFile, filePath) {
691
+ const schemas = [];
692
+ sourceFile.forEachDescendant((node) => {
693
+ if (Node.isCallExpression(node)) {
694
+ const callInfo = this.parseCallToolExpression(node);
695
+ if (callInfo) {
696
+ const expectedProps = this.traceResultUsage(node);
697
+ schemas.push({
698
+ toolName: callInfo.toolName,
699
+ callSite: {
700
+ file: filePath,
701
+ line: node.getStartLineNumber(),
702
+ column: node.getStartLinePos(),
703
+ },
704
+ argumentsProvided: callInfo.arguments,
705
+ expectedProperties: expectedProps,
706
+ });
707
+ }
708
+ }
709
+ });
710
+ return schemas;
711
+ }
712
+ parseCallToolExpression(node) {
713
+ const expression = node.getExpression();
714
+ if (!Node.isPropertyAccessExpression(expression)) {
715
+ return null;
716
+ }
717
+ const methodName = expression.getName();
718
+ // Support multiple call patterns
719
+ const callPatterns = ['callTool', 'call', 'invoke', 'execute'];
720
+ if (!callPatterns.includes(methodName)) {
721
+ return null;
722
+ }
723
+ const args = node.getArguments();
724
+ if (args.length < 1) {
725
+ return null;
726
+ }
727
+ // Tool name could be first arg or in an options object
728
+ let toolName = 'unknown';
729
+ let providedArgs = {};
730
+ const firstArg = args[0];
731
+ // Pattern 1: callTool('toolName', { args })
732
+ if (Node.isStringLiteral(firstArg)) {
733
+ toolName = firstArg.getLiteralValue();
734
+ if (args.length > 1) {
735
+ providedArgs = this.parseObjectLiteral(args[1]);
736
+ }
737
+ }
738
+ // Pattern 2: callTool({ name: 'toolName', arguments: {...} })
739
+ else if (Node.isObjectLiteralExpression(firstArg)) {
740
+ for (const prop of firstArg.getProperties()) {
741
+ if (!Node.isPropertyAssignment(prop))
742
+ continue;
743
+ const propName = prop.getName();
744
+ const init = prop.getInitializer();
745
+ if (propName === 'name' && init && Node.isStringLiteral(init)) {
746
+ toolName = init.getLiteralValue();
747
+ }
748
+ if ((propName === 'arguments' || propName === 'args') && init) {
749
+ providedArgs = this.parseObjectLiteral(init);
750
+ }
751
+ }
752
+ }
753
+ return { toolName, arguments: providedArgs };
754
+ }
755
+ parseObjectLiteral(node) {
756
+ const result = {};
757
+ if (!Node.isObjectLiteralExpression(node)) {
758
+ return result;
759
+ }
760
+ for (const prop of node.getProperties()) {
761
+ if (Node.isPropertyAssignment(prop)) {
762
+ const name = prop.getName();
763
+ result[name] = '<value>';
764
+ }
765
+ else if (Node.isShorthandPropertyAssignment(prop)) {
766
+ const name = prop.getName();
767
+ result[name] = '<value>';
768
+ }
769
+ }
770
+ return result;
771
+ }
772
+ traceResultUsage(callNode) {
773
+ const properties = new Set();
774
+ const containingFunction = callNode.getFirstAncestorByKind(SyntaxKind.FunctionDeclaration)
775
+ || callNode.getFirstAncestorByKind(SyntaxKind.ArrowFunction)
776
+ || callNode.getFirstAncestorByKind(SyntaxKind.MethodDeclaration);
777
+ if (!containingFunction) {
778
+ return [];
779
+ }
780
+ containingFunction.forEachDescendant((node) => {
781
+ if (Node.isPropertyAccessExpression(node)) {
782
+ const propChain = this.getPropertyChain(node);
783
+ const mcpBoilerplate = ['content', 'text', 'type', 'length'];
784
+ const meaningfulProps = propChain.filter(p => !mcpBoilerplate.includes(p));
785
+ for (const prop of meaningfulProps) {
786
+ properties.add(prop);
787
+ }
788
+ }
789
+ });
790
+ return Array.from(properties);
791
+ }
792
+ getPropertyChain(node) {
793
+ const chain = [];
794
+ let current = node;
795
+ while (current && Node.isPropertyAccessExpression(current)) {
796
+ chain.unshift(current.getName());
797
+ current = current.getExpression();
798
+ }
799
+ return chain;
800
+ }
801
+ // ==========================================================================
802
+ // 📋 Interface, Type Alias, and Enum Extraction
803
+ // ==========================================================================
804
+ /**
805
+ * Extract interfaces, type aliases, and enums from TypeScript files.
806
+ *
807
+ * Scans TypeScript source files and extracts all exported type definitions,
808
+ * converting them to NormalizedSchema format for schema comparison.
809
+ *
810
+ * Supports extraction of:
811
+ * - **Interfaces** - Including inherited properties from extends
812
+ * - **Type Aliases** - Object types, unions, intersections, Records
813
+ * - **Enums** - Both const and regular enums
814
+ *
815
+ * @param options - Extraction options specifying root directory and file patterns
816
+ * @param options.rootDir - Root directory to search for TypeScript files
817
+ * @param options.include - Optional glob patterns to include (default: `['*.ts', '*.tsx']`)
818
+ * @param options.exclude - Optional glob patterns to exclude (default: excludes node_modules and dist)
819
+ * @returns Promise resolving to array of NormalizedSchema for all exported declarations
820
+ *
821
+ * @example
822
+ * ```typescript
823
+ * const parser = new TypeScriptParser();
824
+ * const schemas = await parser.extractInterfaces({
825
+ * rootDir: './src/types',
826
+ * include: ['*.ts'],
827
+ * exclude: ['*.test.ts']
828
+ * });
829
+ *
830
+ * // schemas[0] = {
831
+ * // name: 'UserProfile',
832
+ * // properties: { id: {...}, name: {...} },
833
+ * // required: ['id', 'name'],
834
+ * // source: { source: 'typescript', id: 'interface:UserProfile@...' },
835
+ * // location: { file: '...', line: 10 }
836
+ * // }
837
+ * ```
838
+ */
839
+ async extractInterfaces(options) {
840
+ const project = new Project({
841
+ tsConfigFilePath: undefined,
842
+ skipAddingFilesFromTsConfig: true,
843
+ compilerOptions: {
844
+ strict: true,
845
+ target: ts.ScriptTarget.ESNext,
846
+ module: ts.ModuleKind.ESNext,
847
+ },
848
+ });
849
+ const patterns = options.include || this.filePatterns;
850
+ const excludePatterns = options.exclude || ['**/node_modules/**', '**/dist/**'];
851
+ project.addSourceFilesAtPaths(patterns.map(p => `${options.rootDir}/${p}`));
852
+ const schemas = [];
853
+ for (const sourceFile of project.getSourceFiles()) {
854
+ const filePath = sourceFile.getFilePath();
855
+ if (excludePatterns.some(pattern => filePath.includes(pattern.replace('**/', '')))) {
856
+ continue;
857
+ }
858
+ // Extract interfaces
859
+ for (const iface of sourceFile.getInterfaces()) {
860
+ if (!iface.isExported())
861
+ continue;
862
+ const schema = this.convertInterfaceToSchema(iface, filePath);
863
+ schemas.push(schema);
864
+ }
865
+ // Extract type aliases
866
+ for (const typeAlias of sourceFile.getTypeAliases()) {
867
+ if (!typeAlias.isExported())
868
+ continue;
869
+ const schema = this.convertTypeAliasToSchema(typeAlias, filePath);
870
+ schemas.push(schema);
871
+ }
872
+ // Extract enums
873
+ for (const enumDecl of sourceFile.getEnums()) {
874
+ if (!enumDecl.isExported())
875
+ continue;
876
+ const schema = this.convertEnumToSchema(enumDecl, filePath);
877
+ schemas.push(schema);
878
+ }
879
+ }
880
+ return schemas;
881
+ }
882
+ /**
883
+ * Extract all schemas from TypeScript files.
884
+ *
885
+ * Comprehensive extraction that combines interface/type extraction.
886
+ * This is the recommended entry point for extracting TypeScript type
887
+ * definitions for schema comparison workflows.
888
+ *
889
+ * @param options - Extraction options specifying root directory and file patterns
890
+ * @param options.rootDir - Root directory to search for TypeScript files
891
+ * @param options.include - Optional glob patterns to include
892
+ * @param options.exclude - Optional glob patterns to exclude
893
+ * @returns Promise resolving to array of NormalizedSchema
894
+ *
895
+ * @remarks
896
+ * Currently returns interface/type/enum schemas only.
897
+ * Future enhancement: Convert ProducerSchema to NormalizedSchema
898
+ * for Zod schema inclusion.
899
+ *
900
+ * @example
901
+ * ```typescript
902
+ * const parser = new TypeScriptParser();
903
+ * const schemas = await parser.extractAll({ rootDir: './src' });
904
+ *
905
+ * // Returns all exported interfaces, type aliases, and enums
906
+ * console.log(`Found ${schemas.length} type definitions`);
907
+ * ```
908
+ */
909
+ async extractAll(options) {
910
+ // Extract interfaces, type aliases, and enums
911
+ const interfaceSchemas = await this.extractInterfaces(options);
912
+ // Note: extractSchemas returns ProducerSchema[], not NormalizedSchema[]
913
+ // For now, we return only interface schemas.
914
+ // A future enhancement could convert ProducerSchema to NormalizedSchema.
915
+ return interfaceSchemas;
916
+ }
917
+ // --------------------------------------------------------------------------
918
+ // Private: Interface Conversion
919
+ // --------------------------------------------------------------------------
920
+ /**
921
+ * Convert a TypeScript interface declaration to NormalizedSchema.
922
+ *
923
+ * Handles all interface features:
924
+ * - Direct properties with type annotations
925
+ * - Inherited properties from extends clauses
926
+ * - Optional properties (marked with ?)
927
+ * - Readonly properties
928
+ * - Nullable types (T | null)
929
+ * - JSDoc descriptions and @deprecated tags
930
+ *
931
+ * @param iface - The ts-morph InterfaceDeclaration to convert
932
+ * @param filePath - Path to the source file for location tracking
933
+ * @returns NormalizedSchema representation of the interface
934
+ */
935
+ convertInterfaceToSchema(iface, filePath) {
936
+ const name = iface.getName();
937
+ const properties = {};
938
+ const required = [];
939
+ // Get all properties including inherited ones
940
+ const allProperties = this.getInterfaceProperties(iface);
941
+ for (const prop of allProperties) {
942
+ const propName = prop.getName();
943
+ const propDecl = prop.getDeclarations()[0];
944
+ // Get the type from the value declaration or the declared type
945
+ const propType = prop.getValueDeclaration()
946
+ ? prop.getValueDeclarationOrThrow().getType()
947
+ : prop.getDeclaredType();
948
+ const isOptional = prop.isOptional();
949
+ const isReadonly = propDecl ? this.isPropertyReadonly(propDecl) : false;
950
+ const jsDocInfo = propDecl ? this.getJSDocInfo(propDecl) : { description: undefined, deprecated: false };
951
+ // Check for nullable types
952
+ const { baseType, isNullable, hasUndefined } = this.analyzeNullability(propType);
953
+ // Start with interface name in visitingTypes to prevent infinite recursion
954
+ const visitingTypes = new Set([name]);
955
+ const normalizedType = this.convertTypeToNormalized(baseType, visitingTypes);
956
+ properties[propName] = {
957
+ type: normalizedType,
958
+ optional: isOptional || hasUndefined,
959
+ nullable: isNullable,
960
+ readonly: isReadonly,
961
+ deprecated: jsDocInfo.deprecated,
962
+ description: jsDocInfo.description,
963
+ };
964
+ if (!isOptional && !hasUndefined) {
965
+ required.push(propName);
966
+ }
967
+ }
968
+ const schemaRef = {
969
+ source: 'typescript',
970
+ id: `interface:${name}@${filePath}`,
971
+ };
972
+ return {
973
+ name,
974
+ properties,
975
+ required,
976
+ source: schemaRef,
977
+ location: {
978
+ file: filePath,
979
+ line: iface.getStartLineNumber(),
980
+ },
981
+ };
982
+ }
983
+ /**
984
+ * Get all properties from an interface, including inherited ones.
985
+ *
986
+ * Uses ts-morph's type resolution to flatten the property list,
987
+ * ensuring all properties from extended interfaces are included.
988
+ *
989
+ * @param iface - The interface declaration
990
+ * @returns Array of ts-morph Symbol objects representing all properties
991
+ */
992
+ getInterfaceProperties(iface) {
993
+ const type = iface.getType();
994
+ return type.getProperties();
995
+ }
996
+ // --------------------------------------------------------------------------
997
+ // Private: Type Alias Conversion
998
+ // --------------------------------------------------------------------------
999
+ /**
1000
+ * Convert a TypeScript type alias to NormalizedSchema.
1001
+ *
1002
+ * Handles various type alias patterns:
1003
+ * - Object types: `type Foo = { bar: string }`
1004
+ * - Intersection types: `type Foo = A & B`
1005
+ * - Record types: `type Dict = Record<string, T>`
1006
+ * - Union types: `type Status = 'active' | 'inactive'`
1007
+ *
1008
+ * @param typeAlias - The ts-morph TypeAliasDeclaration to convert
1009
+ * @param filePath - Path to the source file for location tracking
1010
+ * @returns NormalizedSchema representation of the type alias
1011
+ */
1012
+ convertTypeAliasToSchema(typeAlias, filePath) {
1013
+ const name = typeAlias.getName();
1014
+ const aliasType = typeAlias.getType();
1015
+ // Handle object-like type aliases (type Foo = { ... })
1016
+ const properties = {};
1017
+ const required = [];
1018
+ let additionalProperties;
1019
+ // Check if it's a Record type (has index signature)
1020
+ const stringIndexType = aliasType.getStringIndexType();
1021
+ if (stringIndexType) {
1022
+ // This is a Record<string, T> or similar
1023
+ additionalProperties = this.convertTypeToNormalized(stringIndexType);
1024
+ }
1025
+ // Check if it's an intersection type and flatten it
1026
+ if (aliasType.isIntersection()) {
1027
+ const intersectionTypes = aliasType.getIntersectionTypes();
1028
+ for (const t of intersectionTypes) {
1029
+ this.extractPropertiesFromType(t, properties, required);
1030
+ }
1031
+ }
1032
+ else if (aliasType.isObject()) {
1033
+ this.extractPropertiesFromType(aliasType, properties, required);
1034
+ }
1035
+ // For union types (like string literals), still return a schema
1036
+ const schemaRef = {
1037
+ source: 'typescript',
1038
+ id: `type:${name}@${filePath}`,
1039
+ };
1040
+ const result = {
1041
+ name,
1042
+ properties,
1043
+ required,
1044
+ source: schemaRef,
1045
+ location: {
1046
+ file: filePath,
1047
+ line: typeAlias.getStartLineNumber(),
1048
+ },
1049
+ };
1050
+ if (additionalProperties !== undefined) {
1051
+ result.additionalProperties = additionalProperties;
1052
+ }
1053
+ return result;
1054
+ }
1055
+ /**
1056
+ * Extract properties from a ts-morph Type object.
1057
+ *
1058
+ * Helper method for extracting property definitions from resolved types.
1059
+ * Used by both interface and type alias conversion to handle object types.
1060
+ *
1061
+ * @param type - The ts-morph Type to extract properties from
1062
+ * @param properties - Output record to populate with PropertyDef entries
1063
+ * @param required - Output array to populate with required property names
1064
+ */
1065
+ extractPropertiesFromType(type, properties, required, visitingTypes = new Set()) {
1066
+ for (const prop of type.getProperties()) {
1067
+ const propName = prop.getName();
1068
+ const propDecl = prop.getDeclarations()[0];
1069
+ const propType = prop.getValueDeclaration()
1070
+ ? prop.getValueDeclarationOrThrow().getType()
1071
+ : prop.getDeclaredType();
1072
+ const isOptional = prop.isOptional();
1073
+ const isReadonly = propDecl ? this.isPropertyReadonly(propDecl) : false;
1074
+ const jsDocInfo = propDecl ? this.getJSDocInfo(propDecl) : { description: undefined, deprecated: false };
1075
+ const { baseType, isNullable, hasUndefined } = this.analyzeNullability(propType);
1076
+ const normalizedType = this.convertTypeToNormalized(baseType, visitingTypes);
1077
+ properties[propName] = {
1078
+ type: normalizedType,
1079
+ optional: isOptional || hasUndefined,
1080
+ nullable: isNullable,
1081
+ readonly: isReadonly,
1082
+ deprecated: jsDocInfo.deprecated,
1083
+ description: jsDocInfo.description,
1084
+ };
1085
+ if (!isOptional && !hasUndefined && !properties[propName]) {
1086
+ required.push(propName);
1087
+ }
1088
+ else if (!isOptional && !hasUndefined && !required.includes(propName)) {
1089
+ required.push(propName);
1090
+ }
1091
+ }
1092
+ }
1093
+ // --------------------------------------------------------------------------
1094
+ // Private: Enum Conversion
1095
+ // --------------------------------------------------------------------------
1096
+ /**
1097
+ * Convert a TypeScript enum to NormalizedSchema.
1098
+ *
1099
+ * Enums are represented as schemas with empty properties,
1100
+ * since they don't have traditional object properties.
1101
+ * The enum values are not currently extracted to the schema.
1102
+ *
1103
+ * @param enumDecl - The ts-morph EnumDeclaration to convert
1104
+ * @param filePath - Path to the source file for location tracking
1105
+ * @returns NormalizedSchema representation of the enum
1106
+ *
1107
+ * @remarks
1108
+ * Future enhancement: Include enum members as values or properties.
1109
+ */
1110
+ convertEnumToSchema(enumDecl, filePath) {
1111
+ const name = enumDecl.getName();
1112
+ const schemaRef = {
1113
+ source: 'typescript',
1114
+ id: `enum:${name}@${filePath}`,
1115
+ };
1116
+ // Enums don't have traditional properties, but we can represent them
1117
+ return {
1118
+ name,
1119
+ properties: {},
1120
+ required: [],
1121
+ source: schemaRef,
1122
+ location: {
1123
+ file: filePath,
1124
+ line: enumDecl.getStartLineNumber(),
1125
+ },
1126
+ };
1127
+ }
1128
+ // --------------------------------------------------------------------------
1129
+ // Private: Type Analysis Utilities
1130
+ // --------------------------------------------------------------------------
1131
+ /**
1132
+ * Analyze nullability of a TypeScript type.
1133
+ *
1134
+ * Examines union types to determine if they include null or undefined,
1135
+ * and extracts the "base" type after removing null/undefined.
1136
+ *
1137
+ * @param type - The ts-morph Type to analyze
1138
+ * @returns Object containing:
1139
+ * - `baseType`: The type with null/undefined removed
1140
+ * - `isNullable`: True if type includes `| null`
1141
+ * - `hasUndefined`: True if type includes `| undefined`
1142
+ *
1143
+ * @example
1144
+ * ```typescript
1145
+ * // For type: string | null | undefined
1146
+ * // Returns: { baseType: string, isNullable: true, hasUndefined: true }
1147
+ * ```
1148
+ */
1149
+ analyzeNullability(type) {
1150
+ let isNullable = false;
1151
+ let hasUndefined = false;
1152
+ let baseType = type;
1153
+ if (type.isUnion()) {
1154
+ const unionTypes = type.getUnionTypes();
1155
+ const nonNullTypes = [];
1156
+ for (const t of unionTypes) {
1157
+ if (t.isNull()) {
1158
+ isNullable = true;
1159
+ }
1160
+ else if (t.isUndefined()) {
1161
+ hasUndefined = true;
1162
+ }
1163
+ else {
1164
+ nonNullTypes.push(t);
1165
+ }
1166
+ }
1167
+ // If there's only one non-null type, use it as the base
1168
+ if (nonNullTypes.length === 1) {
1169
+ baseType = nonNullTypes[0];
1170
+ }
1171
+ }
1172
+ return { baseType, isNullable, hasUndefined };
1173
+ }
1174
+ /**
1175
+ * Convert a ts-morph Type to NormalizedType.
1176
+ *
1177
+ * Recursively converts TypeScript types to the normalized type system:
1178
+ * - Primitives: string, number, boolean, null
1179
+ * - Literals: 'active', 42, true
1180
+ * - Arrays: `string[]`, `Array<T>`
1181
+ * - Tuples: `[string, number]`
1182
+ * - Unions: `string | number`
1183
+ * - Intersections: `A & B`
1184
+ * - Objects: `{ foo: string }`
1185
+ * - References: named interfaces
1186
+ *
1187
+ * @param type - The ts-morph Type to convert
1188
+ * @param visitingTypes - Set of type names currently being visited (for cycle detection)
1189
+ * @returns NormalizedType representation
1190
+ */
1191
+ convertTypeToNormalized(type, visitingTypes = new Set()) {
1192
+ const typeToUse = type;
1193
+ // Check for primitives
1194
+ if (typeToUse.isString()) {
1195
+ return { kind: 'primitive', value: 'string' };
1196
+ }
1197
+ if (typeToUse.isNumber()) {
1198
+ return { kind: 'primitive', value: 'number' };
1199
+ }
1200
+ if (typeToUse.isBoolean()) {
1201
+ return { kind: 'primitive', value: 'boolean' };
1202
+ }
1203
+ if (typeToUse.isNull()) {
1204
+ return { kind: 'primitive', value: 'null' };
1205
+ }
1206
+ // Check for literal types
1207
+ if (typeToUse.isStringLiteral()) {
1208
+ return { kind: 'literal', value: typeToUse.getLiteralValue() };
1209
+ }
1210
+ if (typeToUse.isNumberLiteral()) {
1211
+ return { kind: 'literal', value: typeToUse.getLiteralValue() };
1212
+ }
1213
+ if (typeToUse.isBooleanLiteral()) {
1214
+ const text = typeToUse.getText();
1215
+ return { kind: 'literal', value: text === 'true' };
1216
+ }
1217
+ // Check for array types
1218
+ if (typeToUse.isArray()) {
1219
+ const elementType = typeToUse.getArrayElementType();
1220
+ if (elementType) {
1221
+ return {
1222
+ kind: 'array',
1223
+ element: this.convertTypeToNormalized(elementType, visitingTypes),
1224
+ };
1225
+ }
1226
+ return { kind: 'array', element: { kind: 'unknown' } };
1227
+ }
1228
+ // Check for tuple types (convert to array for simplicity)
1229
+ if (typeToUse.isTuple()) {
1230
+ const tupleTypes = typeToUse.getTupleElements();
1231
+ if (tupleTypes.length > 0) {
1232
+ // For tuples, we lose type information - just use array
1233
+ return { kind: 'array', element: { kind: 'unknown' } };
1234
+ }
1235
+ return { kind: 'array', element: { kind: 'unknown' } };
1236
+ }
1237
+ // Check for union types
1238
+ if (typeToUse.isUnion()) {
1239
+ const variants = typeToUse.getUnionTypes()
1240
+ .filter(t => !t.isNull() && !t.isUndefined())
1241
+ .map(t => this.convertTypeToNormalized(t, visitingTypes));
1242
+ if (variants.length === 0) {
1243
+ return { kind: 'unknown' };
1244
+ }
1245
+ if (variants.length === 1) {
1246
+ return variants[0];
1247
+ }
1248
+ return { kind: 'union', variants };
1249
+ }
1250
+ // Check for intersection types
1251
+ if (typeToUse.isIntersection()) {
1252
+ const members = typeToUse.getIntersectionTypes()
1253
+ .map(t => this.convertTypeToNormalized(t, visitingTypes));
1254
+ return { kind: 'intersection', members };
1255
+ }
1256
+ // Check for object types (inline or interface references)
1257
+ if (typeToUse.isObject()) {
1258
+ // Check if it's a named interface reference
1259
+ const symbol = typeToUse.getSymbol();
1260
+ if (symbol) {
1261
+ const name = symbol.getName();
1262
+ // Skip built-in types but check for circular references
1263
+ if (name !== '__type' && name !== 'Array' && name !== 'Date') {
1264
+ // Check for circular reference - already visiting this type
1265
+ if (visitingTypes.has(name)) {
1266
+ return { kind: 'ref', name };
1267
+ }
1268
+ // Add to visiting set
1269
+ visitingTypes.add(name);
1270
+ }
1271
+ }
1272
+ // Extract properties for inline object type
1273
+ const properties = {};
1274
+ const required = [];
1275
+ for (const prop of typeToUse.getProperties()) {
1276
+ const propName = prop.getName();
1277
+ const propDecl = prop.getDeclarations()[0];
1278
+ const propType = prop.getValueDeclaration()
1279
+ ? prop.getValueDeclarationOrThrow().getType()
1280
+ : prop.getDeclaredType();
1281
+ const isOptional = prop.isOptional();
1282
+ const isReadonly = propDecl ? this.isPropertyReadonly(propDecl) : false;
1283
+ const jsDocInfo = propDecl ? this.getJSDocInfo(propDecl) : { description: undefined, deprecated: false };
1284
+ // Check for circular reference in property type
1285
+ const propSymbol = propType.getSymbol();
1286
+ if (propSymbol && visitingTypes.has(propSymbol.getName())) {
1287
+ properties[propName] = {
1288
+ type: { kind: 'ref', name: propSymbol.getName() },
1289
+ optional: isOptional,
1290
+ nullable: false,
1291
+ readonly: isReadonly,
1292
+ deprecated: jsDocInfo.deprecated,
1293
+ description: jsDocInfo.description,
1294
+ };
1295
+ if (!isOptional) {
1296
+ required.push(propName);
1297
+ }
1298
+ continue;
1299
+ }
1300
+ const { baseType, isNullable, hasUndefined } = this.analyzeNullability(propType);
1301
+ const normalizedPropType = this.convertTypeToNormalized(baseType, visitingTypes);
1302
+ properties[propName] = {
1303
+ type: normalizedPropType,
1304
+ optional: isOptional || hasUndefined,
1305
+ nullable: isNullable,
1306
+ readonly: isReadonly,
1307
+ deprecated: jsDocInfo.deprecated,
1308
+ description: jsDocInfo.description,
1309
+ };
1310
+ if (!isOptional && !hasUndefined) {
1311
+ required.push(propName);
1312
+ }
1313
+ }
1314
+ return {
1315
+ kind: 'object',
1316
+ schema: {
1317
+ properties,
1318
+ required,
1319
+ source: { source: 'typescript', id: 'inline' },
1320
+ },
1321
+ };
1322
+ }
1323
+ // Check for any/unknown
1324
+ if (typeToUse.getText() === 'any') {
1325
+ return { kind: 'any' };
1326
+ }
1327
+ return { kind: 'unknown' };
1328
+ }
1329
+ // --------------------------------------------------------------------------
1330
+ // Private: Property Metadata Extraction
1331
+ // --------------------------------------------------------------------------
1332
+ /**
1333
+ * Check if a property declaration is readonly.
1334
+ *
1335
+ * @param decl - The property declaration node
1336
+ * @returns True if the property has the readonly modifier
1337
+ */
1338
+ isPropertyReadonly(decl) {
1339
+ if (Node.isPropertySignature(decl)) {
1340
+ return decl.isReadonly();
1341
+ }
1342
+ if (Node.isPropertyDeclaration(decl)) {
1343
+ return decl.isReadonly();
1344
+ }
1345
+ return false;
1346
+ }
1347
+ /**
1348
+ * Extract JSDoc information from a declaration.
1349
+ *
1350
+ * Parses JSDoc comments to extract:
1351
+ * - Description text
1352
+ * - @deprecated tag presence
1353
+ *
1354
+ * @param decl - The declaration node to extract JSDoc from
1355
+ * @returns Object with description and deprecated flag
1356
+ */
1357
+ getJSDocInfo(decl) {
1358
+ let description;
1359
+ let deprecated = false;
1360
+ // Get JSDoc from property signature or declaration
1361
+ if (Node.isPropertySignature(decl) || Node.isPropertyDeclaration(decl)) {
1362
+ const jsDocs = decl.getJsDocs();
1363
+ if (jsDocs.length > 0) {
1364
+ const jsDoc = jsDocs[0];
1365
+ const descText = jsDoc.getDescription().trim();
1366
+ if (descText) {
1367
+ description = descText;
1368
+ }
1369
+ // Check for @deprecated tag
1370
+ for (const tag of jsDoc.getTags()) {
1371
+ if (tag.getTagName() === 'deprecated') {
1372
+ deprecated = true;
1373
+ break;
1374
+ }
1375
+ }
1376
+ }
1377
+ }
1378
+ return { description, deprecated };
1379
+ }
1380
+ }
1381
+ //# sourceMappingURL=typescript.js.map