github-to-mcp-monorepo 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 (388) hide show
  1. package/.env.example +8 -0
  2. package/.github/CODEOWNERS +6 -0
  3. package/.husky/pre-commit +1 -0
  4. package/.nvmrc +1 -0
  5. package/.prettierignore +5 -0
  6. package/.prettierrc +7 -0
  7. package/.vscode/settings.json +4 -0
  8. package/ARCHITECTURE.md +1429 -0
  9. package/CHANGELOG.md +167 -0
  10. package/CONTRIBUTING.md +327 -0
  11. package/LICENSE +201 -0
  12. package/README.md +1028 -0
  13. package/SECURITY.md +248 -0
  14. package/VISUAL_GUIDE.md +437 -0
  15. package/apps/vscode/IMPLEMENTATION.md +480 -0
  16. package/apps/vscode/README.md +248 -0
  17. package/apps/vscode/package.json +381 -0
  18. package/apps/vscode/resources/icon.png +0 -0
  19. package/apps/vscode/resources/icon.svg +5 -0
  20. package/apps/vscode/src/commands/browseRegistry.ts +211 -0
  21. package/apps/vscode/src/commands/configureClaudeDesktop.ts +332 -0
  22. package/apps/vscode/src/commands/convert.ts +82 -0
  23. package/apps/vscode/src/commands/convertCurrentRepo.ts +109 -0
  24. package/apps/vscode/src/commands/convertFromUrl.ts +138 -0
  25. package/apps/vscode/src/commands/index.ts +121 -0
  26. package/apps/vscode/src/commands/validate.ts +197 -0
  27. package/apps/vscode/src/extension.ts +464 -0
  28. package/apps/vscode/src/global.d.ts +36 -0
  29. package/apps/vscode/src/test/extension.test.ts +73 -0
  30. package/apps/vscode/src/utils/file-generator.ts +529 -0
  31. package/apps/vscode/src/utils/github-api.ts +335 -0
  32. package/apps/vscode/src/utils/index.ts +29 -0
  33. package/apps/vscode/src/utils/mcp-config.ts +334 -0
  34. package/apps/vscode/src/utils/storage.ts +87 -0
  35. package/apps/vscode/src/views/McpServersTreeView.ts +160 -0
  36. package/apps/vscode/src/views/OutputChannelView.ts +195 -0
  37. package/apps/vscode/src/views/StatusBarItem.ts +251 -0
  38. package/apps/vscode/src/views/ToolsExplorerView.ts +314 -0
  39. package/apps/vscode/src/views/historyProvider.ts +75 -0
  40. package/apps/vscode/src/views/index.ts +12 -0
  41. package/apps/vscode/src/views/resultsPanel.ts +330 -0
  42. package/apps/vscode/src/webviews/ConversionPanel.ts +350 -0
  43. package/apps/vscode/src/webviews/ToolDetailsPanel.ts +448 -0
  44. package/apps/vscode/src/webviews/index.ts +9 -0
  45. package/apps/vscode/src/webviews/webview-ui/styles.ts +492 -0
  46. package/apps/vscode/tsconfig.json +20 -0
  47. package/apps/web/PLAYGROUND_GUIDE.md +499 -0
  48. package/apps/web/README.md +505 -0
  49. package/apps/web/app/api/convert/route.ts +100 -0
  50. package/apps/web/app/api/convert/stream/route.ts +198 -0
  51. package/apps/web/app/api/deploy/route.ts +157 -0
  52. package/apps/web/app/api/edge/route.ts +308 -0
  53. package/apps/web/app/api/export-docker/route.ts +284 -0
  54. package/apps/web/app/api/generate-openapi/route.ts +119 -0
  55. package/apps/web/app/api/mcp/[serverId]/route.ts +263 -0
  56. package/apps/web/app/api/playground/connect/route.ts +143 -0
  57. package/apps/web/app/api/playground/disconnect/route.ts +78 -0
  58. package/apps/web/app/api/playground/execute/route.ts +135 -0
  59. package/apps/web/app/api/playground/sessions/route.ts +103 -0
  60. package/apps/web/app/api/playground/tools/route.ts +117 -0
  61. package/apps/web/app/api/playground/v2/connect/route.ts +96 -0
  62. package/apps/web/app/api/playground/v2/disconnect/route.ts +88 -0
  63. package/apps/web/app/api/playground/v2/health/route.ts +80 -0
  64. package/apps/web/app/api/playground/v2/prompts/route.ts +160 -0
  65. package/apps/web/app/api/playground/v2/resources/route.ts +159 -0
  66. package/apps/web/app/api/playground/v2/sessions/route.ts +184 -0
  67. package/apps/web/app/api/playground/v2/tools/route.ts +167 -0
  68. package/apps/web/app/api/stream/route.ts +232 -0
  69. package/apps/web/app/batch/BatchConvertClient.tsx +190 -0
  70. package/apps/web/app/batch/page.tsx +37 -0
  71. package/apps/web/app/convert/page.tsx +269 -0
  72. package/apps/web/app/dashboard/page.tsx +380 -0
  73. package/apps/web/app/globals.css +622 -0
  74. package/apps/web/app/layout.tsx +120 -0
  75. package/apps/web/app/manifest.ts +31 -0
  76. package/apps/web/app/opengraph-image.tsx +112 -0
  77. package/apps/web/app/page.old.tsx +924 -0
  78. package/apps/web/app/page.tsx +77 -0
  79. package/apps/web/app/playground/page.tsx +306 -0
  80. package/apps/web/app/playground/v2/error.tsx +163 -0
  81. package/apps/web/app/playground/v2/layout.tsx +58 -0
  82. package/apps/web/app/playground/v2/loading.tsx +152 -0
  83. package/apps/web/app/playground/v2/page.tsx +644 -0
  84. package/apps/web/app/playground/v2/providers.tsx +214 -0
  85. package/apps/web/app/playground/v2/use-shortcuts.ts +209 -0
  86. package/apps/web/app/playground/v2/use-url-state.ts +296 -0
  87. package/apps/web/app/providers.tsx +22 -0
  88. package/apps/web/app/sitemap.ts +32 -0
  89. package/apps/web/app/twitter-image.tsx +112 -0
  90. package/apps/web/components/BranchSelector.tsx +401 -0
  91. package/apps/web/components/ClaudeConfigExport.tsx +226 -0
  92. package/apps/web/components/Features.tsx +84 -0
  93. package/apps/web/components/Footer.tsx +119 -0
  94. package/apps/web/components/GenerationProgress.tsx +248 -0
  95. package/apps/web/components/GithubUrlInput.tsx +483 -0
  96. package/apps/web/components/Header.tsx +175 -0
  97. package/apps/web/components/Hero.tsx +117 -0
  98. package/apps/web/components/HowItWorks.tsx +119 -0
  99. package/apps/web/components/InstallBanner.tsx +158 -0
  100. package/apps/web/components/Logo.tsx +116 -0
  101. package/apps/web/components/ParticleBackground.tsx +105 -0
  102. package/apps/web/components/Playground.tsx +472 -0
  103. package/apps/web/components/PlaygroundToolTester.tsx +410 -0
  104. package/apps/web/components/ProductCards.tsx +179 -0
  105. package/apps/web/components/SplitView.tsx +194 -0
  106. package/apps/web/components/ToolFilter.tsx +260 -0
  107. package/apps/web/components/ToolList.tsx +325 -0
  108. package/apps/web/components/batch/BatchConvert.tsx +785 -0
  109. package/apps/web/components/batch/index.ts +7 -0
  110. package/apps/web/components/convert/ConfigTabs.tsx +230 -0
  111. package/apps/web/components/convert/ConversionResult.tsx +482 -0
  112. package/apps/web/components/convert/InlinePlayground.tsx +259 -0
  113. package/apps/web/components/convert/LoadingSteps.tsx +311 -0
  114. package/apps/web/components/convert/OneClickInstall.tsx +224 -0
  115. package/apps/web/components/convert/ToolCard.tsx +189 -0
  116. package/apps/web/components/convert/TryInPlayground.tsx +242 -0
  117. package/apps/web/components/convert/index.ts +12 -0
  118. package/apps/web/components/deploy/DeployButton.tsx +369 -0
  119. package/apps/web/components/deploy/index.ts +7 -0
  120. package/apps/web/components/docker/DockerExport.tsx +690 -0
  121. package/apps/web/components/docker/index.ts +7 -0
  122. package/apps/web/components/install/OneClickInstall.tsx +676 -0
  123. package/apps/web/components/install/index.ts +7 -0
  124. package/apps/web/components/playground/CapabilityTabs.tsx +150 -0
  125. package/apps/web/components/playground/ConnectionStatusV2.tsx +322 -0
  126. package/apps/web/components/playground/EmptyStates.tsx +305 -0
  127. package/apps/web/components/playground/ExecutionLog.tsx +260 -0
  128. package/apps/web/components/playground/ExecutionLogV2.tsx +378 -0
  129. package/apps/web/components/playground/JsonViewer.tsx +388 -0
  130. package/apps/web/components/playground/PlaygroundLayout.tsx +244 -0
  131. package/apps/web/components/playground/PromptsPanel.tsx +385 -0
  132. package/apps/web/components/playground/ResourcesPanel.tsx +378 -0
  133. package/apps/web/components/playground/SchemaForm.tsx +477 -0
  134. package/apps/web/components/playground/ServerStatus.tsx +151 -0
  135. package/apps/web/components/playground/ShareButton.tsx +239 -0
  136. package/apps/web/components/playground/ToolsPanel.tsx +309 -0
  137. package/apps/web/components/playground/TransportConfigurator.tsx +563 -0
  138. package/apps/web/components/playground/index.ts +74 -0
  139. package/apps/web/components/playground/types.ts +202 -0
  140. package/apps/web/components/streaming/StreamingProgress.tsx +441 -0
  141. package/apps/web/components/streaming/index.ts +7 -0
  142. package/apps/web/components/ui/badge.tsx +42 -0
  143. package/apps/web/components/ui/button.tsx +88 -0
  144. package/apps/web/components/ui/card.tsx +75 -0
  145. package/apps/web/components/ui/code-block.tsx +122 -0
  146. package/apps/web/components/ui/index.ts +12 -0
  147. package/apps/web/components/ui/input.tsx +55 -0
  148. package/apps/web/components/ui/tabs.tsx +61 -0
  149. package/apps/web/hooks/index.ts +85 -0
  150. package/apps/web/hooks/types.ts +1173 -0
  151. package/apps/web/hooks/use-conversion.ts +133 -0
  152. package/apps/web/hooks/use-execution-history.ts +376 -0
  153. package/apps/web/hooks/use-generation-progress.ts +147 -0
  154. package/apps/web/hooks/use-local-storage.ts +88 -0
  155. package/apps/web/hooks/use-mcp-client.ts +623 -0
  156. package/apps/web/hooks/use-mcp-connection.ts +500 -0
  157. package/apps/web/hooks/use-mcp-execution.ts +282 -0
  158. package/apps/web/hooks/use-mcp-prompts.ts +441 -0
  159. package/apps/web/hooks/use-mcp-resources.ts +430 -0
  160. package/apps/web/hooks/use-mcp-tools.ts +540 -0
  161. package/apps/web/hooks/use-playground-store.ts +299 -0
  162. package/apps/web/hooks/use-playground.ts +184 -0
  163. package/apps/web/hooks/use-streaming-conversion.ts +227 -0
  164. package/apps/web/hooks/useBatchConversion.ts +271 -0
  165. package/apps/web/hooks/useDockerConfig.ts +161 -0
  166. package/apps/web/hooks/usePlatformDetection.ts +80 -0
  167. package/apps/web/hooks/useStreaming.ts +199 -0
  168. package/apps/web/lib/api/errors.ts +386 -0
  169. package/apps/web/lib/api/index.ts +137 -0
  170. package/apps/web/lib/api/logger.ts +187 -0
  171. package/apps/web/lib/api/middleware.ts +364 -0
  172. package/apps/web/lib/api/openapi.ts +977 -0
  173. package/apps/web/lib/api/session-manager.ts +594 -0
  174. package/apps/web/lib/api/types.ts +433 -0
  175. package/apps/web/lib/api/validation.ts +523 -0
  176. package/apps/web/lib/constants.ts +114 -0
  177. package/apps/web/lib/mcp/client.ts +1137 -0
  178. package/apps/web/lib/mcp/events.ts +651 -0
  179. package/apps/web/lib/mcp/index.ts +347 -0
  180. package/apps/web/lib/mcp/logger.ts +428 -0
  181. package/apps/web/lib/mcp/metrics.ts +703 -0
  182. package/apps/web/lib/mcp/retry.ts +616 -0
  183. package/apps/web/lib/mcp/session-manager.ts +779 -0
  184. package/apps/web/lib/mcp/transports.ts +988 -0
  185. package/apps/web/lib/mcp/types.ts +594 -0
  186. package/apps/web/lib/mcp-client-enhanced.ts +871 -0
  187. package/apps/web/lib/mcp-client.ts +778 -0
  188. package/apps/web/lib/mcp-errors.ts +489 -0
  189. package/apps/web/lib/mcp-sandbox.ts +593 -0
  190. package/apps/web/lib/mcp-testing.ts +428 -0
  191. package/apps/web/lib/mcp-types.ts +448 -0
  192. package/apps/web/lib/playground-store.tsx +1147 -0
  193. package/apps/web/lib/utils.ts +439 -0
  194. package/apps/web/next-env.d.ts +5 -0
  195. package/apps/web/next.config.js +23 -0
  196. package/apps/web/package.json +55 -0
  197. package/apps/web/postcss.config.js +6 -0
  198. package/apps/web/public/.well-known/ai-plugin.json +17 -0
  199. package/apps/web/public/logo.svg +6 -0
  200. package/apps/web/public/robots.txt +22 -0
  201. package/apps/web/public/schema.json +27 -0
  202. package/apps/web/tailwind.config.js +26 -0
  203. package/apps/web/tailwind.config.ts +123 -0
  204. package/apps/web/tsconfig.json +20 -0
  205. package/apps/web/types/deploy.ts +139 -0
  206. package/apps/web/types/index.ts +247 -0
  207. package/apps/web/vercel.json +39 -0
  208. package/eslint.config.mjs +23 -0
  209. package/llms.txt +102 -0
  210. package/mkdocs/docs/api/core.md +318 -0
  211. package/mkdocs/docs/api/index.md +128 -0
  212. package/mkdocs/docs/api/mcp-server.md +301 -0
  213. package/mkdocs/docs/api/openapi-parser.md +254 -0
  214. package/mkdocs/docs/assets/logo.svg +7 -0
  215. package/mkdocs/docs/changelog.md +118 -0
  216. package/mkdocs/docs/cli/generate.md +148 -0
  217. package/mkdocs/docs/cli/index.md +52 -0
  218. package/mkdocs/docs/cli/inspect.md +164 -0
  219. package/mkdocs/docs/cli/serve.md +136 -0
  220. package/mkdocs/docs/concepts/classification.md +254 -0
  221. package/mkdocs/docs/concepts/how-it-works.md +299 -0
  222. package/mkdocs/docs/concepts/index.md +77 -0
  223. package/mkdocs/docs/concepts/mcp-protocol.md +362 -0
  224. package/mkdocs/docs/concepts/tool-types.md +382 -0
  225. package/mkdocs/docs/contributing/architecture.md +262 -0
  226. package/mkdocs/docs/contributing/development.md +245 -0
  227. package/mkdocs/docs/contributing/index.md +73 -0
  228. package/mkdocs/docs/contributing/testing.md +320 -0
  229. package/mkdocs/docs/getting-started/configuration.md +235 -0
  230. package/mkdocs/docs/getting-started/index.md +54 -0
  231. package/mkdocs/docs/getting-started/installation.md +145 -0
  232. package/mkdocs/docs/getting-started/quickstart.md +160 -0
  233. package/mkdocs/docs/guides/batch.md +375 -0
  234. package/mkdocs/docs/guides/claude-desktop.md +227 -0
  235. package/mkdocs/docs/guides/cursor.md +188 -0
  236. package/mkdocs/docs/guides/custom-tools.md +367 -0
  237. package/mkdocs/docs/guides/index.md +78 -0
  238. package/mkdocs/docs/guides/private-repos.md +221 -0
  239. package/mkdocs/docs/guides/vscode.md +247 -0
  240. package/mkdocs/docs/index.md +175 -0
  241. package/mkdocs/docs/reference/config.md +223 -0
  242. package/mkdocs/docs/reference/env.md +192 -0
  243. package/mkdocs/docs/reference/index.md +102 -0
  244. package/mkdocs/docs/reference/tools.md +309 -0
  245. package/mkdocs/docs/stylesheets/extra.css +231 -0
  246. package/mkdocs/mkdocs.yml +204 -0
  247. package/mkdocs/overrides/.gitkeep +1 -0
  248. package/mkdocs/overrides/main.html +7 -0
  249. package/mkdocs/python-deps.txt +7 -0
  250. package/mkdocs/vercel.json +11 -0
  251. package/package.json +63 -0
  252. package/packages/core/package.json +61 -0
  253. package/packages/core/src/__tests__/bitbucket-client.test.ts +366 -0
  254. package/packages/core/src/__tests__/cli.test.ts +235 -0
  255. package/packages/core/src/__tests__/code-extractor.test.ts +378 -0
  256. package/packages/core/src/__tests__/docker-generator.test.ts +255 -0
  257. package/packages/core/src/__tests__/github-client.test.ts +390 -0
  258. package/packages/core/src/__tests__/gitlab-client.test.ts +319 -0
  259. package/packages/core/src/__tests__/go-extractor.test.ts +351 -0
  260. package/packages/core/src/__tests__/graphql-extractor.test.ts +330 -0
  261. package/packages/core/src/__tests__/java-extractor.test.ts +497 -0
  262. package/packages/core/src/__tests__/plugins.test.ts +467 -0
  263. package/packages/core/src/__tests__/readme-extractor.test.ts +258 -0
  264. package/packages/core/src/__tests__/redis-cache.test.ts +307 -0
  265. package/packages/core/src/__tests__/rust-extractor.test.ts +252 -0
  266. package/packages/core/src/__tests__/streaming.test.ts +251 -0
  267. package/packages/core/src/additional-extractors.ts +333 -0
  268. package/packages/core/src/cache/cache-interface.ts +179 -0
  269. package/packages/core/src/cache/index.ts +210 -0
  270. package/packages/core/src/cache/redis-cache.ts +291 -0
  271. package/packages/core/src/cache/upstash-cache.ts +379 -0
  272. package/packages/core/src/cache.ts +251 -0
  273. package/packages/core/src/cli.ts +822 -0
  274. package/packages/core/src/code-extractor.ts +696 -0
  275. package/packages/core/src/docker-generator.ts +470 -0
  276. package/packages/core/src/edge-compatible.ts +491 -0
  277. package/packages/core/src/extractors/go-extractor.ts +791 -0
  278. package/packages/core/src/extractors/index.ts +9 -0
  279. package/packages/core/src/extractors/java-extractor.ts +937 -0
  280. package/packages/core/src/extractors/rust-extractor.ts +744 -0
  281. package/packages/core/src/github-client.ts +319 -0
  282. package/packages/core/src/go-generator.ts +356 -0
  283. package/packages/core/src/graphql-extractor.ts +358 -0
  284. package/packages/core/src/index.ts +797 -0
  285. package/packages/core/src/langchain-exporter.ts +617 -0
  286. package/packages/core/src/language-parsers.ts +1114 -0
  287. package/packages/core/src/mcp-introspector.ts +279 -0
  288. package/packages/core/src/monorepo-detector.ts +378 -0
  289. package/packages/core/src/plugins/index.ts +370 -0
  290. package/packages/core/src/plugins/registry.ts +404 -0
  291. package/packages/core/src/plugins/types.ts +215 -0
  292. package/packages/core/src/providers/base-provider.ts +246 -0
  293. package/packages/core/src/providers/bitbucket-client.ts +464 -0
  294. package/packages/core/src/providers/gitlab-client.ts +388 -0
  295. package/packages/core/src/providers/index.ts +176 -0
  296. package/packages/core/src/python-generator.ts +260 -0
  297. package/packages/core/src/queue/index.ts +100 -0
  298. package/packages/core/src/queue/memory-queue.ts +445 -0
  299. package/packages/core/src/queue/redis-queue.ts +578 -0
  300. package/packages/core/src/queue/types.ts +251 -0
  301. package/packages/core/src/readme-extractor.ts +409 -0
  302. package/packages/core/src/schema-generator.ts +638 -0
  303. package/packages/core/src/streaming.ts +999 -0
  304. package/packages/core/src/types.ts +289 -0
  305. package/packages/core/tsconfig.json +9 -0
  306. package/packages/core/tsup.config.ts +25 -0
  307. package/packages/mcp-server/README.md +297 -0
  308. package/packages/mcp-server/package.json +55 -0
  309. package/packages/mcp-server/src/__tests__/mcp-server.test.ts +177 -0
  310. package/packages/mcp-server/src/__tests__/tools.test.ts +217 -0
  311. package/packages/mcp-server/src/index.ts +1206 -0
  312. package/packages/mcp-server/src/prompts/index.ts +601 -0
  313. package/packages/mcp-server/src/tools/export-docker.ts +362 -0
  314. package/packages/mcp-server/src/tools/generate-openapi.ts +162 -0
  315. package/packages/mcp-server/src/tools/monitor-mcp-server.ts +448 -0
  316. package/packages/mcp-server/src/tools/stream-convert.ts +398 -0
  317. package/packages/mcp-server/src/tools/test-mcp-tool.ts +531 -0
  318. package/packages/mcp-server/tsconfig.json +12 -0
  319. package/packages/mcp-server/tsup.config.ts +14 -0
  320. package/packages/openapi-parser/package-lock.json +3028 -0
  321. package/packages/openapi-parser/package.json +41 -0
  322. package/packages/openapi-parser/src/analyzer.ts +700 -0
  323. package/packages/openapi-parser/src/asyncapi-parser.ts +475 -0
  324. package/packages/openapi-parser/src/cli.ts +302 -0
  325. package/packages/openapi-parser/src/generator.ts +570 -0
  326. package/packages/openapi-parser/src/generators/express-analyzer.ts +649 -0
  327. package/packages/openapi-parser/src/generators/fastapi-analyzer.ts +960 -0
  328. package/packages/openapi-parser/src/generators/index.ts +200 -0
  329. package/packages/openapi-parser/src/generators/nextjs-analyzer.ts +768 -0
  330. package/packages/openapi-parser/src/generators/openapi-builder.ts +527 -0
  331. package/packages/openapi-parser/src/generators/types.ts +298 -0
  332. package/packages/openapi-parser/src/graphql-parser.ts +462 -0
  333. package/packages/openapi-parser/src/grpc-parser.ts +649 -0
  334. package/packages/openapi-parser/src/har-parser.ts +723 -0
  335. package/packages/openapi-parser/src/index.ts +635 -0
  336. package/packages/openapi-parser/src/insomnia-parser.ts +614 -0
  337. package/packages/openapi-parser/src/parser.ts +231 -0
  338. package/packages/openapi-parser/src/postman-parser.ts +611 -0
  339. package/packages/openapi-parser/src/ref-resolver.ts +313 -0
  340. package/packages/openapi-parser/src/transformer.ts +459 -0
  341. package/packages/openapi-parser/tests/generators/express.test.ts +209 -0
  342. package/packages/openapi-parser/tests/generators/fastapi.test.ts +236 -0
  343. package/packages/openapi-parser/tests/generators/nextjs.test.ts +273 -0
  344. package/packages/openapi-parser/tests/parsers.test.ts +847 -0
  345. package/packages/openapi-parser/tsconfig.json +9 -0
  346. package/packages/openapi-parser/tsup.config.ts +11 -0
  347. package/packages/registry/package.json +59 -0
  348. package/packages/registry/src/cli.ts +456 -0
  349. package/packages/registry/src/index.ts +44 -0
  350. package/packages/registry/src/popular/github.json +47 -0
  351. package/packages/registry/src/popular/index.ts +55 -0
  352. package/packages/registry/src/popular/linear.json +42 -0
  353. package/packages/registry/src/popular/notion.json +42 -0
  354. package/packages/registry/src/popular/openai.json +40 -0
  355. package/packages/registry/src/popular/resend.json +38 -0
  356. package/packages/registry/src/popular/slack.json +42 -0
  357. package/packages/registry/src/popular/stripe.json +163 -0
  358. package/packages/registry/src/popular/supabase.json +42 -0
  359. package/packages/registry/src/popular/twilio.json +40 -0
  360. package/packages/registry/src/popular/vercel.json +40 -0
  361. package/packages/registry/src/registry.ts +492 -0
  362. package/packages/registry/src/storage.ts +334 -0
  363. package/packages/registry/src/types.ts +275 -0
  364. package/packages/registry/src/updater.ts +208 -0
  365. package/packages/registry/tsconfig.json +10 -0
  366. package/packages/registry/tsup.config.ts +11 -0
  367. package/pnpm-workspace.yaml +3 -0
  368. package/scripts/build-docs.sh +16 -0
  369. package/server.json +9 -0
  370. package/templates/Dockerfile.python.template +60 -0
  371. package/templates/Dockerfile.typescript.template +60 -0
  372. package/templates/docker-compose.template.yml +68 -0
  373. package/tests/fixtures/express-app/index.js +34 -0
  374. package/tests/fixtures/express-app/routes/posts.js +43 -0
  375. package/tests/fixtures/express-app/routes/users.js +58 -0
  376. package/tests/fixtures/fastapi-app/main.py +125 -0
  377. package/tests/fixtures/fastapi-app/routes/admin.py +42 -0
  378. package/tests/fixtures/graphql/simple-schema.graphql +65 -0
  379. package/tests/fixtures/mocks/github-api-responses.json +63 -0
  380. package/tests/fixtures/nextjs-app/app/api/posts/route.ts +55 -0
  381. package/tests/fixtures/nextjs-app/app/api/users/[id]/route.ts +63 -0
  382. package/tests/fixtures/nextjs-app/app/api/users/route.ts +44 -0
  383. package/tests/fixtures/nextjs-app/pages/api/health.ts +28 -0
  384. package/tests/fixtures/openapi/petstore.yaml +179 -0
  385. package/tests/integration/langchain-export.test.ts +405 -0
  386. package/tests/integration/openapi-conversion.test.ts +221 -0
  387. package/tsconfig.json +18 -0
  388. package/vitest.config.ts +32 -0
@@ -0,0 +1,1147 @@
1
+ /**
2
+ * Playground Store - Shared state management for playground integration
3
+ * @copyright 2024-2026 nirholas
4
+ * @license MIT
5
+ */
6
+
7
+ 'use client';
8
+
9
+ import { createContext, useContext, useCallback, useReducer, useEffect, ReactNode } from 'react';
10
+ import type { Tool, ConversionResult } from '@/types';
11
+ import { safeJsonParse } from '@/lib/utils';
12
+
13
+ // ===== Types =====
14
+
15
+ export interface PlaygroundState {
16
+ /** Generated TypeScript MCP server code */
17
+ generatedCode: string | null;
18
+ /** Generated Python MCP server code */
19
+ generatedPythonCode: string | null;
20
+ /** Extracted tools from conversion */
21
+ tools: Tool[];
22
+ /** Repository name */
23
+ repoName: string | null;
24
+ /** Repository URL */
25
+ repoUrl: string | null;
26
+ /** Session ID for server connection */
27
+ sessionId: string | null;
28
+ /** Last conversion timestamp */
29
+ lastConversion: Date | null;
30
+ /** Full conversion result for reference */
31
+ conversionResult: ConversionResult | null;
32
+ /** Error state */
33
+ error: PlaygroundError | null;
34
+ /** Loading state */
35
+ isLoading: boolean;
36
+ }
37
+
38
+ export interface PlaygroundError {
39
+ type: 'syntax' | 'server' | 'execution' | 'network' | 'unknown';
40
+ message: string;
41
+ details?: string;
42
+ recoverable: boolean;
43
+ retryCount?: number;
44
+ }
45
+
46
+ // API interfaces for tool execution
47
+ export interface ExecuteToolRequest {
48
+ generatedCode: string;
49
+ toolName: string;
50
+ toolParams: Record<string, unknown>;
51
+ sessionId?: string;
52
+ }
53
+
54
+ export interface ExecuteToolResponse {
55
+ success: boolean;
56
+ result?: unknown;
57
+ error?: string;
58
+ sessionId: string;
59
+ executionTime: number;
60
+ logs?: string[];
61
+ }
62
+
63
+ type PlaygroundAction =
64
+ | { type: 'SET_CONVERSION_RESULT'; payload: ConversionResult }
65
+ | { type: 'SET_CODE'; payload: { typescript: string; python?: string } }
66
+ | { type: 'SET_TOOLS'; payload: Tool[] }
67
+ | { type: 'SET_SESSION_ID'; payload: string }
68
+ | { type: 'SET_ERROR'; payload: PlaygroundError | null }
69
+ | { type: 'SET_LOADING'; payload: boolean }
70
+ | { type: 'CLEAR_STATE' }
71
+ | { type: 'LOAD_FROM_STORAGE'; payload: Partial<PlaygroundState> }
72
+ | { type: 'INCREMENT_RETRY' };
73
+
74
+ interface PlaygroundContextValue {
75
+ state: PlaygroundState;
76
+ dispatch: React.Dispatch<PlaygroundAction>;
77
+
78
+ // Convenience methods
79
+ setConversionResult: (result: ConversionResult) => void;
80
+ setCode: (typescript: string, python?: string) => void;
81
+ setError: (error: PlaygroundError | null) => void;
82
+ clearState: () => void;
83
+
84
+ // URL helpers
85
+ generateShareableLink: () => string;
86
+ loadFromUrl: (params: URLSearchParams) => Promise<void>;
87
+
88
+ // Gist helpers
89
+ loadFromGist: (gistId: string) => Promise<void>;
90
+ }
91
+
92
+ // ===== Initial State =====
93
+
94
+ const initialState: PlaygroundState = {
95
+ generatedCode: null,
96
+ generatedPythonCode: null,
97
+ tools: [],
98
+ repoName: null,
99
+ repoUrl: null,
100
+ sessionId: null,
101
+ lastConversion: null,
102
+ conversionResult: null,
103
+ error: null,
104
+ isLoading: false,
105
+ };
106
+
107
+ // ===== Storage Keys =====
108
+
109
+ const STORAGE_KEY = 'playground-state';
110
+ const SESSION_KEY = 'playground-session';
111
+
112
+ // ===== Reducer =====
113
+
114
+ function playgroundReducer(state: PlaygroundState, action: PlaygroundAction): PlaygroundState {
115
+ switch (action.type) {
116
+ case 'SET_CONVERSION_RESULT': {
117
+ const result = action.payload;
118
+ return {
119
+ ...state,
120
+ generatedCode: result.code,
121
+ generatedPythonCode: result.pythonCode || null,
122
+ tools: result.tools,
123
+ repoName: result.name,
124
+ repoUrl: result.repository?.url || null,
125
+ lastConversion: new Date(),
126
+ conversionResult: result,
127
+ error: null,
128
+ };
129
+ }
130
+
131
+ case 'SET_CODE':
132
+ return {
133
+ ...state,
134
+ generatedCode: action.payload.typescript,
135
+ generatedPythonCode: action.payload.python || null,
136
+ };
137
+
138
+ case 'SET_TOOLS':
139
+ return {
140
+ ...state,
141
+ tools: action.payload,
142
+ };
143
+
144
+ case 'SET_SESSION_ID':
145
+ return {
146
+ ...state,
147
+ sessionId: action.payload,
148
+ };
149
+
150
+ case 'SET_ERROR':
151
+ return {
152
+ ...state,
153
+ error: action.payload,
154
+ isLoading: false,
155
+ };
156
+
157
+ case 'SET_LOADING':
158
+ return {
159
+ ...state,
160
+ isLoading: action.payload,
161
+ };
162
+
163
+ case 'CLEAR_STATE':
164
+ return {
165
+ ...initialState,
166
+ };
167
+
168
+ case 'LOAD_FROM_STORAGE':
169
+ return {
170
+ ...state,
171
+ ...action.payload,
172
+ };
173
+
174
+ case 'INCREMENT_RETRY':
175
+ return {
176
+ ...state,
177
+ error: state.error
178
+ ? { ...state.error, retryCount: (state.error.retryCount || 0) + 1 }
179
+ : null,
180
+ };
181
+
182
+ default:
183
+ return state;
184
+ }
185
+ }
186
+
187
+ // ===== Context =====
188
+
189
+ const PlaygroundContext = createContext<PlaygroundContextValue | null>(null);
190
+
191
+ // ===== Provider =====
192
+
193
+ interface PlaygroundProviderProps {
194
+ children: ReactNode;
195
+ }
196
+
197
+ export function PlaygroundProvider({ children }: PlaygroundProviderProps) {
198
+ const [state, dispatch] = useReducer(playgroundReducer, initialState);
199
+
200
+ // Load from localStorage on mount
201
+ useEffect(() => {
202
+ if (typeof window === 'undefined') return;
203
+
204
+ try {
205
+ const stored = window.localStorage.getItem(STORAGE_KEY);
206
+ if (stored) {
207
+ const parsed = safeJsonParse<Partial<PlaygroundState>>(stored, {});
208
+ if (parsed.lastConversion) {
209
+ parsed.lastConversion = new Date(parsed.lastConversion);
210
+ }
211
+ dispatch({ type: 'LOAD_FROM_STORAGE', payload: parsed });
212
+ }
213
+ } catch (error) {
214
+ console.warn('Failed to load playground state from storage:', error);
215
+ }
216
+ }, []);
217
+
218
+ // Persist to localStorage on state change
219
+ useEffect(() => {
220
+ if (typeof window === 'undefined') return;
221
+ if (!state.generatedCode && !state.tools.length) return;
222
+
223
+ try {
224
+ const toStore = {
225
+ generatedCode: state.generatedCode,
226
+ generatedPythonCode: state.generatedPythonCode,
227
+ tools: state.tools,
228
+ repoName: state.repoName,
229
+ repoUrl: state.repoUrl,
230
+ lastConversion: state.lastConversion?.toISOString(),
231
+ conversionResult: state.conversionResult,
232
+ };
233
+ window.localStorage.setItem(STORAGE_KEY, JSON.stringify(toStore));
234
+ } catch (error) {
235
+ console.warn('Failed to persist playground state:', error);
236
+ }
237
+ }, [state.generatedCode, state.generatedPythonCode, state.tools, state.repoName, state.repoUrl, state.lastConversion, state.conversionResult]);
238
+
239
+ // Convenience methods
240
+ const setConversionResult = useCallback((result: ConversionResult) => {
241
+ dispatch({ type: 'SET_CONVERSION_RESULT', payload: result });
242
+ }, []);
243
+
244
+ const setCode = useCallback((typescript: string, python?: string) => {
245
+ dispatch({ type: 'SET_CODE', payload: { typescript, python } });
246
+ }, []);
247
+
248
+ const setError = useCallback((error: PlaygroundError | null) => {
249
+ dispatch({ type: 'SET_ERROR', payload: error });
250
+ }, []);
251
+
252
+ const clearState = useCallback(() => {
253
+ dispatch({ type: 'CLEAR_STATE' });
254
+ if (typeof window !== 'undefined') {
255
+ window.localStorage.removeItem(STORAGE_KEY);
256
+ window.localStorage.removeItem(SESSION_KEY);
257
+ }
258
+ }, []);
259
+
260
+ // Generate a shareable link with base64-encoded code
261
+ const generateShareableLink = useCallback(() => {
262
+ if (typeof window === 'undefined' || !state.generatedCode) {
263
+ return '';
264
+ }
265
+
266
+ const baseUrl = window.location.origin;
267
+
268
+ // For large code, we truncate and suggest using gist
269
+ if (state.generatedCode.length > 10000) {
270
+ console.warn('Code too large for URL encoding. Consider using a Gist.');
271
+ }
272
+
273
+ try {
274
+ const encoded = btoa(encodeURIComponent(state.generatedCode));
275
+ const params = new URLSearchParams();
276
+ params.set('code', encoded);
277
+
278
+ if (state.repoName) {
279
+ params.set('name', state.repoName);
280
+ }
281
+
282
+ return `${baseUrl}/playground?${params.toString()}`;
283
+ } catch (error) {
284
+ console.error('Failed to generate shareable link:', error);
285
+ return '';
286
+ }
287
+ }, [state.generatedCode, state.repoName]);
288
+
289
+ // Load code from URL parameters
290
+ const loadFromUrl = useCallback(async (params: URLSearchParams) => {
291
+ dispatch({ type: 'SET_LOADING', payload: true });
292
+
293
+ try {
294
+ // Check for base64-encoded code
295
+ const encodedCode = params.get('code');
296
+ if (encodedCode) {
297
+ // Validate base64 encoding
298
+ if (!isValidBase64(encodedCode)) {
299
+ dispatch({
300
+ type: 'SET_ERROR',
301
+ payload: {
302
+ type: 'syntax',
303
+ message: 'Invalid code parameter',
304
+ details: 'The code parameter is not valid base64 encoding',
305
+ recoverable: false,
306
+ },
307
+ });
308
+ return;
309
+ }
310
+
311
+ try {
312
+ const decoded = decodeURIComponent(atob(encodedCode));
313
+
314
+ // Validate the decoded content looks like code
315
+ if (!decoded.trim() || decoded.length < 10) {
316
+ dispatch({
317
+ type: 'SET_ERROR',
318
+ payload: {
319
+ type: 'syntax',
320
+ message: 'Invalid code content',
321
+ details: 'The shared code appears to be empty or too short',
322
+ recoverable: false,
323
+ },
324
+ });
325
+ return;
326
+ }
327
+
328
+ const name = params.get('name') || 'Shared Code';
329
+
330
+ dispatch({
331
+ type: 'SET_CODE',
332
+ payload: { typescript: decoded },
333
+ });
334
+
335
+ // Try to extract tools from the code
336
+ const extractedTools = extractToolsFromCode(decoded);
337
+ if (extractedTools.length > 0) {
338
+ dispatch({ type: 'SET_TOOLS', payload: extractedTools });
339
+ }
340
+
341
+ dispatch({ type: 'SET_LOADING', payload: false });
342
+ return;
343
+ } catch (decodeError) {
344
+ dispatch({
345
+ type: 'SET_ERROR',
346
+ payload: {
347
+ type: 'syntax',
348
+ message: 'Failed to decode shared code',
349
+ details: decodeError instanceof Error ? decodeError.message : 'Invalid encoding',
350
+ recoverable: false,
351
+ },
352
+ });
353
+ return;
354
+ }
355
+ }
356
+
357
+ // Check for Gist ID
358
+ const gistId = params.get('gist');
359
+ if (gistId) {
360
+ // Validate Gist ID format (alphanumeric, 20-32 chars typically)
361
+ if (!isValidGistId(gistId)) {
362
+ dispatch({
363
+ type: 'SET_ERROR',
364
+ payload: {
365
+ type: 'syntax',
366
+ message: 'Invalid Gist ID',
367
+ details: 'The Gist ID format is invalid. It should be alphanumeric.',
368
+ recoverable: false,
369
+ },
370
+ });
371
+ return;
372
+ }
373
+
374
+ await loadFromGistInternal(gistId, dispatch);
375
+ return;
376
+ }
377
+
378
+ dispatch({ type: 'SET_LOADING', payload: false });
379
+ } catch (error) {
380
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
381
+ dispatch({
382
+ type: 'SET_ERROR',
383
+ payload: {
384
+ type: 'network',
385
+ message: 'Failed to load code from URL',
386
+ details: errorMessage,
387
+ recoverable: true,
388
+ },
389
+ });
390
+ }
391
+ }, []);
392
+
393
+ // Load code from GitHub Gist
394
+ const loadFromGist = useCallback(async (gistId: string) => {
395
+ // Validate Gist ID before making API call
396
+ if (!isValidGistId(gistId)) {
397
+ dispatch({
398
+ type: 'SET_ERROR',
399
+ payload: {
400
+ type: 'syntax',
401
+ message: 'Invalid Gist ID',
402
+ details: 'The Gist ID format is invalid. It should be alphanumeric.',
403
+ recoverable: false,
404
+ },
405
+ });
406
+ return;
407
+ }
408
+ await loadFromGistInternal(gistId, dispatch);
409
+ }, []);
410
+
411
+ const value: PlaygroundContextValue = {
412
+ state,
413
+ dispatch,
414
+ setConversionResult,
415
+ setCode,
416
+ setError,
417
+ clearState,
418
+ generateShareableLink,
419
+ loadFromUrl,
420
+ loadFromGist,
421
+ };
422
+
423
+ return (
424
+ <PlaygroundContext.Provider value={value}>
425
+ {children}
426
+ </PlaygroundContext.Provider>
427
+ );
428
+ }
429
+
430
+ // ===== Hook =====
431
+
432
+ export function usePlaygroundStore(): PlaygroundContextValue {
433
+ const context = useContext(PlaygroundContext);
434
+ if (!context) {
435
+ throw new Error('usePlaygroundStore must be used within a PlaygroundProvider');
436
+ }
437
+ return context;
438
+ }
439
+
440
+ // ===== Validation Helpers =====
441
+
442
+ /**
443
+ * Validate base64 encoding
444
+ */
445
+ function isValidBase64(str: string): boolean {
446
+ if (!str || str.length === 0) return false;
447
+
448
+ // Check for valid base64 characters
449
+ const base64Regex = /^[A-Za-z0-9+/]*={0,2}$/;
450
+ if (!base64Regex.test(str)) return false;
451
+
452
+ // Try to decode to verify
453
+ try {
454
+ atob(str);
455
+ return true;
456
+ } catch {
457
+ return false;
458
+ }
459
+ }
460
+
461
+ /**
462
+ * Validate GitHub Gist ID format
463
+ * Gist IDs are alphanumeric strings, typically 20-32 characters
464
+ */
465
+ function isValidGistId(gistId: string): boolean {
466
+ if (!gistId || typeof gistId !== 'string') return false;
467
+
468
+ // Gist IDs are alphanumeric, typically 20-32 chars (but can vary)
469
+ // They don't contain special characters
470
+ const gistIdRegex = /^[a-f0-9]{20,40}$/i;
471
+ return gistIdRegex.test(gistId);
472
+ }
473
+
474
+ /**
475
+ * Validate GitHub URL format
476
+ */
477
+ export function isValidGitHubUrl(url: string): boolean {
478
+ if (!url || typeof url !== 'string') return false;
479
+
480
+ try {
481
+ const parsed = new URL(url.startsWith('http') ? url : `https://${url}`);
482
+ const isGithub = parsed.hostname === 'github.com' || parsed.hostname === 'www.github.com';
483
+ if (!isGithub) return false;
484
+
485
+ // Check for owner/repo pattern
486
+ const pathParts = parsed.pathname.split('/').filter(Boolean);
487
+ return pathParts.length >= 2;
488
+ } catch {
489
+ return false;
490
+ }
491
+ }
492
+
493
+ /**
494
+ * Parse GitHub URL to extract owner and repo
495
+ */
496
+ export function parseGitHubUrl(url: string): { owner: string; repo: string } | null {
497
+ if (!isValidGitHubUrl(url)) return null;
498
+
499
+ try {
500
+ const parsed = new URL(url.startsWith('http') ? url : `https://${url}`);
501
+ const pathParts = parsed.pathname.split('/').filter(Boolean);
502
+ if (pathParts.length >= 2) {
503
+ return {
504
+ owner: pathParts[0],
505
+ repo: pathParts[1].replace(/\.git$/, ''),
506
+ };
507
+ }
508
+ } catch {
509
+ // ignore
510
+ }
511
+ return null;
512
+ }
513
+
514
+ // ===== Helper Functions =====
515
+
516
+ async function loadFromGistInternal(
517
+ gistId: string,
518
+ dispatch: React.Dispatch<PlaygroundAction>
519
+ ): Promise<void> {
520
+ dispatch({ type: 'SET_LOADING', payload: true });
521
+
522
+ try {
523
+ const response = await fetch(`https://api.github.com/gists/${gistId}`);
524
+
525
+ if (!response.ok) {
526
+ if (response.status === 404) {
527
+ throw new Error('Gist not found. It may have been deleted or is private.');
528
+ }
529
+ if (response.status === 403) {
530
+ throw new Error('Rate limited by GitHub API. Please try again in a few minutes.');
531
+ }
532
+ throw new Error(`Failed to fetch Gist: ${response.status} ${response.statusText}`);
533
+ }
534
+
535
+ const gist = await response.json();
536
+ const files = Object.values(gist.files) as Array<{
537
+ filename: string;
538
+ content: string;
539
+ language?: string;
540
+ }>;
541
+
542
+ // Look for TypeScript or Python files
543
+ const tsFile = files.find(f => f.filename.endsWith('.ts') || f.language === 'TypeScript');
544
+ const pyFile = files.find(f => f.filename.endsWith('.py') || f.language === 'Python');
545
+
546
+ if (!tsFile && !pyFile) {
547
+ throw new Error('No TypeScript or Python file found in Gist. Please share a Gist containing a .ts or .py file.');
548
+ }
549
+
550
+ dispatch({
551
+ type: 'SET_CODE',
552
+ payload: {
553
+ typescript: tsFile?.content || '',
554
+ python: pyFile?.content,
555
+ },
556
+ });
557
+
558
+ // Try to extract tools from the code
559
+ const code = tsFile?.content || pyFile?.content || '';
560
+ const extractedTools = extractToolsFromCode(code);
561
+ if (extractedTools.length > 0) {
562
+ dispatch({ type: 'SET_TOOLS', payload: extractedTools });
563
+ }
564
+
565
+ dispatch({ type: 'SET_LOADING', payload: false });
566
+ } catch (error) {
567
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
568
+ dispatch({
569
+ type: 'SET_ERROR',
570
+ payload: {
571
+ type: 'network',
572
+ message: 'Failed to load Gist',
573
+ details: errorMessage,
574
+ recoverable: true,
575
+ },
576
+ });
577
+ }
578
+ }
579
+
580
+ /**
581
+ * Extract tool definitions from MCP server code.
582
+ * Supports multiple patterns for both TypeScript and Python MCP servers.
583
+ */
584
+ function extractToolsFromCode(code: string): Tool[] {
585
+ const tools: Tool[] = [];
586
+ const seenNames = new Set<string>();
587
+
588
+ // Extract TypeScript tools
589
+ extractTypeScriptTools(code, tools, seenNames);
590
+
591
+ // Extract Python tools
592
+ extractPythonTools(code, tools, seenNames);
593
+
594
+ return tools;
595
+ }
596
+
597
+ /**
598
+ * Extract TypeScript MCP tool definitions
599
+ */
600
+ function extractTypeScriptTools(code: string, tools: Tool[], seenNames: Set<string>): void {
601
+ // Pattern 1: server.tool("name", "description", schema, handler)
602
+ const pattern1 = /server\.tool\(\s*(['"`])(\w+)\1\s*,\s*(['"`])([\s\S]*?)\3\s*,\s*(\{[\s\S]*?\})\s*,/g;
603
+
604
+ // Pattern 2: server.tool({ name: "...", description: "...", inputSchema: {...} })
605
+ const pattern2 = /server\.tool\(\s*\{[\s\S]*?name\s*:\s*(['"`])(\w+)\1[\s\S]*?description\s*:\s*(['"`])([\s\S]*?)\3[\s\S]*?inputSchema\s*:\s*(\{[\s\S]*?\})\s*[\s\S]*?\}\s*\)/g;
606
+
607
+ // Pattern 3: .tool("name", { description, inputSchema })
608
+ const pattern3 = /\.tool\(\s*(['"`])(\w+)\1\s*,\s*\{[\s\S]*?description\s*:\s*(['"`])([\s\S]*?)\3/g;
609
+
610
+ let match;
611
+
612
+ // Process Pattern 1
613
+ while ((match = pattern1.exec(code)) !== null) {
614
+ const [, , name, , description, schemaStr] = match;
615
+ if (seenNames.has(name)) continue;
616
+ seenNames.add(name);
617
+
618
+ tools.push({
619
+ name,
620
+ description: cleanDescription(description),
621
+ inputSchema: parseSchemaString(schemaStr),
622
+ source: { type: 'code', file: 'loaded' },
623
+ });
624
+ }
625
+
626
+ // Process Pattern 2
627
+ while ((match = pattern2.exec(code)) !== null) {
628
+ const [, , name, , description, schemaStr] = match;
629
+ if (seenNames.has(name)) continue;
630
+ seenNames.add(name);
631
+
632
+ tools.push({
633
+ name,
634
+ description: cleanDescription(description),
635
+ inputSchema: parseSchemaString(schemaStr),
636
+ source: { type: 'code', file: 'loaded' },
637
+ });
638
+ }
639
+
640
+ // Process Pattern 3 (partial - no schema)
641
+ while ((match = pattern3.exec(code)) !== null) {
642
+ const [, , name, , description] = match;
643
+ if (seenNames.has(name)) continue;
644
+ seenNames.add(name);
645
+
646
+ tools.push({
647
+ name,
648
+ description: cleanDescription(description),
649
+ inputSchema: { type: 'object', properties: {}, required: [] },
650
+ source: { type: 'code', file: 'loaded' },
651
+ });
652
+ }
653
+
654
+ // Pattern 4: Look for z.object schemas with tool names nearby
655
+ const zodPattern = /const\s+(\w+)Schema\s*=\s*z\.object\(\s*(\{[\s\S]*?\})\s*\)/g;
656
+ const zodSchemas = new Map<string, string>();
657
+
658
+ while ((match = zodPattern.exec(code)) !== null) {
659
+ const [, schemaName, schemaBody] = match;
660
+ zodSchemas.set(schemaName.toLowerCase(), schemaBody);
661
+ }
662
+
663
+ // Try to match Zod schemas to tools that don't have schemas yet
664
+ for (const tool of tools) {
665
+ if (Object.keys(tool.inputSchema.properties || {}).length === 0) {
666
+ const schemaBody = zodSchemas.get(tool.name.toLowerCase()) ||
667
+ zodSchemas.get(tool.name.replace(/_/g, '').toLowerCase());
668
+ if (schemaBody) {
669
+ tool.inputSchema = parseZodSchema(schemaBody);
670
+ }
671
+ }
672
+ }
673
+ }
674
+
675
+ /**
676
+ * Extract Python MCP tool definitions
677
+ */
678
+ function extractPythonTools(code: string, tools: Tool[], seenNames: Set<string>): void {
679
+ // Pattern 1: @server.tool() or @mcp.tool() decorator
680
+ const decoratorPattern = /@(?:server|mcp)\.tool\s*\(\s*\)\s*\n\s*(?:async\s+)?def\s+(\w+)\s*\(([\s\S]*?)\)\s*(?:->[\s\S]*?)?:\s*\n\s*(?:['"`]{3}([\s\S]*?)['"`]{3}|['"`]([\s\S]*?)['"`])?/g;
681
+
682
+ // Pattern 2: @tool decorator (from mcp.server.tool)
683
+ const toolDecoratorPattern = /@tool\s*\(\s*(?:name\s*=\s*)?(['"`])?(\w+)\1?\s*(?:,\s*description\s*=\s*(['"`])([\s\S]*?)\3)?\s*\)\s*\n\s*(?:async\s+)?def\s+(\w+)/g;
684
+
685
+ // Pattern 3: server.add_tool or mcp.add_tool
686
+ const addToolPattern = /(?:server|mcp)\.add_tool\(\s*['"`](\w+)['"`]\s*,\s*(?:description\s*=\s*)?['"`]([\s\S]*?)['"`]/g;
687
+
688
+ let match;
689
+
690
+ // Process Pattern 1 (decorator with docstring)
691
+ while ((match = decoratorPattern.exec(code)) !== null) {
692
+ const [, funcName, params, tripleQuoteDoc, singleQuoteDoc] = match;
693
+ if (seenNames.has(funcName)) continue;
694
+ seenNames.add(funcName);
695
+
696
+ const description = tripleQuoteDoc || singleQuoteDoc || `Tool: ${funcName}`;
697
+
698
+ tools.push({
699
+ name: funcName,
700
+ description: cleanDescription(description),
701
+ inputSchema: parsePythonParams(params),
702
+ source: { type: 'python-mcp', file: 'loaded' },
703
+ });
704
+ }
705
+
706
+ // Process Pattern 2 (tool decorator with name)
707
+ while ((match = toolDecoratorPattern.exec(code)) !== null) {
708
+ const [, , decoratorName, , description, funcName] = match;
709
+ const name = decoratorName || funcName;
710
+ if (seenNames.has(name)) continue;
711
+ seenNames.add(name);
712
+
713
+ tools.push({
714
+ name,
715
+ description: cleanDescription(description || `Tool: ${name}`),
716
+ inputSchema: { type: 'object', properties: {}, required: [] },
717
+ source: { type: 'python-mcp', file: 'loaded' },
718
+ });
719
+ }
720
+
721
+ // Process Pattern 3 (add_tool)
722
+ while ((match = addToolPattern.exec(code)) !== null) {
723
+ const [, name, description] = match;
724
+ if (seenNames.has(name)) continue;
725
+ seenNames.add(name);
726
+
727
+ tools.push({
728
+ name,
729
+ description: cleanDescription(description),
730
+ inputSchema: { type: 'object', properties: {}, required: [] },
731
+ source: { type: 'python-mcp', file: 'loaded' },
732
+ });
733
+ }
734
+
735
+ // Pattern 4: Look for Pydantic models that might be tool inputs
736
+ const pydanticPattern = /class\s+(\w+)(?:Input|Params|Args|Request)\s*\((?:BaseModel|TypedDict)\):\s*([\s\S]*?)(?=\n(?:class|def|@|\Z))/g;
737
+ const pydanticModels = new Map<string, string>();
738
+
739
+ while ((match = pydanticPattern.exec(code)) !== null) {
740
+ const [, modelName, modelBody] = match;
741
+ pydanticModels.set(modelName.toLowerCase().replace(/input|params|args|request/i, ''), modelBody);
742
+ }
743
+
744
+ // Try to match Pydantic models to tools
745
+ for (const tool of tools) {
746
+ if (Object.keys(tool.inputSchema.properties || {}).length === 0) {
747
+ const modelBody = pydanticModels.get(tool.name.toLowerCase()) ||
748
+ pydanticModels.get(tool.name.replace(/_/g, '').toLowerCase());
749
+ if (modelBody) {
750
+ tool.inputSchema = parsePydanticModel(modelBody);
751
+ }
752
+ }
753
+ }
754
+ }
755
+
756
+ /**
757
+ * Clean and normalize description text
758
+ */
759
+ function cleanDescription(desc: string): string {
760
+ if (!desc) return '';
761
+ return desc
762
+ .trim()
763
+ .replace(/\\n/g, ' ')
764
+ .replace(/\s+/g, ' ')
765
+ .replace(/^['"`]+|['"`]+$/g, '')
766
+ .trim();
767
+ }
768
+
769
+ /**
770
+ * Parse a JSON-like schema string into an inputSchema object
771
+ */
772
+ function parseSchemaString(schemaStr: string): Tool['inputSchema'] {
773
+ const schema: Tool['inputSchema'] = {
774
+ type: 'object',
775
+ properties: {},
776
+ required: [],
777
+ };
778
+
779
+ try {
780
+ // Try direct JSON parse first (for well-formed schemas)
781
+ const parsed = JSON.parse(schemaStr);
782
+ if (parsed.properties) {
783
+ schema.properties = parsed.properties;
784
+ }
785
+ if (parsed.required) {
786
+ schema.required = parsed.required;
787
+ }
788
+ return schema;
789
+ } catch {
790
+ // Fall back to regex-based extraction
791
+ }
792
+
793
+ // Extract properties using regex
794
+ const propsMatch = schemaStr.match(/properties\s*:\s*\{([\s\S]*?)\}(?=\s*,?\s*(?:required|additionalProperties|\}))/);
795
+ if (propsMatch) {
796
+ const propsStr = propsMatch[1];
797
+
798
+ // Match individual property definitions
799
+ const propPattern = /(\w+)\s*:\s*\{([^{}]*(?:\{[^{}]*\}[^{}]*)*)\}/g;
800
+ let propMatch;
801
+
802
+ while ((propMatch = propPattern.exec(propsStr)) !== null) {
803
+ const [, propName, propDef] = propMatch;
804
+ const prop: { type: string; description?: string; enum?: string[]; default?: unknown } = {
805
+ type: 'string' // default type
806
+ };
807
+
808
+ // Extract type
809
+ const typeMatch = propDef.match(/type\s*:\s*['"`]?(\w+)['"`]?/);
810
+ if (typeMatch) {
811
+ prop.type = typeMatch[1];
812
+ }
813
+
814
+ // Extract description
815
+ const descMatch = propDef.match(/description\s*:\s*(['"`])([\s\S]*?)\1/);
816
+ if (descMatch) {
817
+ prop.description = descMatch[2];
818
+ }
819
+
820
+ // Extract enum values
821
+ const enumMatch = propDef.match(/enum\s*:\s*\[([\s\S]*?)\]/);
822
+ if (enumMatch) {
823
+ const enumValues = enumMatch[1].match(/['"`]([^'"`]+)['"`]/g);
824
+ if (enumValues) {
825
+ prop.enum = enumValues.map(v => v.replace(/['"`]/g, ''));
826
+ }
827
+ }
828
+
829
+ // Extract default value
830
+ const defaultMatch = propDef.match(/default\s*:\s*(['"`]?)([^,}\s]+)\1/);
831
+ if (defaultMatch) {
832
+ const val = defaultMatch[2];
833
+ prop.default = val === 'true' ? true : val === 'false' ? false : isNaN(Number(val)) ? val : Number(val);
834
+ }
835
+
836
+ schema.properties![propName] = prop;
837
+ }
838
+ }
839
+
840
+ // Extract required array
841
+ const requiredMatch = schemaStr.match(/required\s*:\s*\[([\s\S]*?)\]/);
842
+ if (requiredMatch) {
843
+ const requiredItems = requiredMatch[1].match(/['"`](\w+)['"`]/g);
844
+ if (requiredItems) {
845
+ schema.required = requiredItems.map(r => r.replace(/['"`]/g, ''));
846
+ }
847
+ }
848
+
849
+ return schema;
850
+ }
851
+
852
+ /**
853
+ * Parse Zod schema definition to extract properties
854
+ */
855
+ function parseZodSchema(schemaBody: string): Tool['inputSchema'] {
856
+ const schema: Tool['inputSchema'] = {
857
+ type: 'object',
858
+ properties: {},
859
+ required: [],
860
+ };
861
+
862
+ // Match Zod property definitions
863
+ const propPattern = /(\w+)\s*:\s*z\.(string|number|boolean|array|object|enum)\s*\(\s*(?:\[([\s\S]*?)\])?\s*\)(?:\.describe\(\s*(['"`])([\s\S]*?)\4\s*\))?(?:\.optional\(\))?(?:\.default\(([^)]+)\))?/g;
864
+ let match;
865
+
866
+ while ((match = propPattern.exec(schemaBody)) !== null) {
867
+ const [fullMatch, propName, zodType, enumValues, , description, defaultValue] = match;
868
+
869
+ const prop: { type: string; description?: string; enum?: string[]; default?: unknown } = {
870
+ type: zodType === 'array' ? 'array' : zodType === 'enum' ? 'string' : zodType,
871
+ };
872
+
873
+ if (description) {
874
+ prop.description = description;
875
+ }
876
+
877
+ if (enumValues) {
878
+ const values = enumValues.match(/['"`]([^'"`]+)['"`]/g);
879
+ if (values) {
880
+ prop.enum = values.map(v => v.replace(/['"`]/g, ''));
881
+ }
882
+ }
883
+
884
+ if (defaultValue !== undefined) {
885
+ const val = defaultValue.trim();
886
+ prop.default = val === 'true' ? true : val === 'false' ? false : isNaN(Number(val)) ? val.replace(/['"`]/g, '') : Number(val);
887
+ }
888
+
889
+ schema.properties![propName] = prop;
890
+
891
+ // If not optional, add to required
892
+ if (!fullMatch.includes('.optional()')) {
893
+ schema.required!.push(propName);
894
+ }
895
+ }
896
+
897
+ return schema;
898
+ }
899
+
900
+ /**
901
+ * Parse Python function parameters to extract schema
902
+ */
903
+ function parsePythonParams(params: string): Tool['inputSchema'] {
904
+ const schema: Tool['inputSchema'] = {
905
+ type: 'object',
906
+ properties: {},
907
+ required: [],
908
+ };
909
+
910
+ if (!params || params.trim() === '' || params.trim() === 'self') {
911
+ return schema;
912
+ }
913
+
914
+ // Split parameters, handling nested types
915
+ const paramList = splitPythonParams(params);
916
+
917
+ for (const param of paramList) {
918
+ const trimmed = param.trim();
919
+ if (!trimmed || trimmed === 'self' || trimmed === 'cls' || trimmed.startsWith('*')) {
920
+ continue;
921
+ }
922
+
923
+ // Match: name: Type = default or name: Type or name = default or just name
924
+ const paramMatch = trimmed.match(/^(\w+)\s*(?::\s*([\w\[\],\s|]+))?\s*(?:=\s*(.+))?$/);
925
+ if (!paramMatch) continue;
926
+
927
+ const [, paramName, typeHint, defaultValue] = paramMatch;
928
+
929
+ const prop: { type: string; description?: string; enum?: string[]; default?: unknown } = {
930
+ type: 'string' // Default type
931
+ };
932
+
933
+ // Convert Python type hints to JSON Schema types
934
+ if (typeHint) {
935
+ prop.type = pythonTypeToJsonType(typeHint.trim());
936
+ }
937
+
938
+ if (defaultValue !== undefined && defaultValue.trim() !== 'None') {
939
+ const val = defaultValue.trim();
940
+ if (val === 'True') {
941
+ prop.default = true;
942
+ } else if (val === 'False') {
943
+ prop.default = false;
944
+ } else if (!isNaN(Number(val))) {
945
+ prop.default = Number(val);
946
+ } else {
947
+ prop.default = val.replace(/^['"`]|['"`]$/g, '');
948
+ }
949
+ }
950
+
951
+ schema.properties![paramName] = prop;
952
+
953
+ // If no default value (and not None), it's required
954
+ if (defaultValue === undefined) {
955
+ schema.required!.push(paramName);
956
+ }
957
+ }
958
+
959
+ return schema;
960
+ }
961
+
962
+ /**
963
+ * Split Python parameters handling nested brackets
964
+ */
965
+ function splitPythonParams(params: string): string[] {
966
+ const result: string[] = [];
967
+ let current = '';
968
+ let depth = 0;
969
+
970
+ for (const char of params) {
971
+ if (char === '[' || char === '(') {
972
+ depth++;
973
+ current += char;
974
+ } else if (char === ']' || char === ')') {
975
+ depth--;
976
+ current += char;
977
+ } else if (char === ',' && depth === 0) {
978
+ result.push(current);
979
+ current = '';
980
+ } else {
981
+ current += char;
982
+ }
983
+ }
984
+
985
+ if (current.trim()) {
986
+ result.push(current);
987
+ }
988
+
989
+ return result;
990
+ }
991
+
992
+ /**
993
+ * Convert Python type hint to JSON Schema type
994
+ */
995
+ function pythonTypeToJsonType(typeHint: string): string {
996
+ const normalized = typeHint.toLowerCase().replace(/\s/g, '');
997
+
998
+ if (normalized.includes('str')) return 'string';
999
+ if (normalized.includes('int')) return 'integer';
1000
+ if (normalized.includes('float')) return 'number';
1001
+ if (normalized.includes('bool')) return 'boolean';
1002
+ if (normalized.includes('list') || normalized.includes('array')) return 'array';
1003
+ if (normalized.includes('dict') || normalized.includes('object')) return 'object';
1004
+ if (normalized.includes('none') || normalized.includes('null')) return 'null';
1005
+
1006
+ return 'string'; // Default fallback
1007
+ }
1008
+
1009
+ /**
1010
+ * Parse Pydantic model body to extract properties
1011
+ */
1012
+ function parsePydanticModel(modelBody: string): Tool['inputSchema'] {
1013
+ const schema: Tool['inputSchema'] = {
1014
+ type: 'object',
1015
+ properties: {},
1016
+ required: [],
1017
+ };
1018
+
1019
+ // Match Pydantic field definitions
1020
+ // Pattern: field_name: Type = Field(...) or field_name: Type = default
1021
+ const fieldPattern = /(\w+)\s*:\s*([\w\[\],\s|]+)\s*(?:=\s*(?:Field\(([\s\S]*?)\)|([^#\n]+)))?/g;
1022
+ let match;
1023
+
1024
+ while ((match = fieldPattern.exec(modelBody)) !== null) {
1025
+ const [, fieldName, typeHint, fieldArgs, defaultValue] = match;
1026
+
1027
+ // Skip private fields
1028
+ if (fieldName.startsWith('_')) continue;
1029
+
1030
+ const prop: { type: string; description?: string; enum?: string[]; default?: unknown } = {
1031
+ type: pythonTypeToJsonType(typeHint.trim()),
1032
+ };
1033
+
1034
+ // Parse Field() arguments
1035
+ if (fieldArgs) {
1036
+ const descMatch = fieldArgs.match(/description\s*=\s*(['"`])([\s\S]*?)\1/);
1037
+ if (descMatch) {
1038
+ prop.description = descMatch[2];
1039
+ }
1040
+
1041
+ const defaultMatch = fieldArgs.match(/default\s*=\s*([^,)]+)/);
1042
+ if (defaultMatch) {
1043
+ const val = defaultMatch[1].trim();
1044
+ if (val !== '...' && val !== 'None') {
1045
+ prop.default = val === 'True' ? true : val === 'False' ? false :
1046
+ isNaN(Number(val)) ? val.replace(/['"`]/g, '') : Number(val);
1047
+ }
1048
+ }
1049
+
1050
+ // If default is ... or not present in Field(), it's required
1051
+ if (!fieldArgs.includes('default=') || fieldArgs.includes('default=...')) {
1052
+ schema.required!.push(fieldName);
1053
+ }
1054
+ } else if (defaultValue !== undefined) {
1055
+ // Handle inline default values
1056
+ const val = defaultValue.trim();
1057
+ if (val && val !== 'None') {
1058
+ prop.default = val === 'True' ? true : val === 'False' ? false :
1059
+ isNaN(Number(val)) ? val.replace(/['"`]/g, '') : Number(val);
1060
+ }
1061
+ } else {
1062
+ // No default = required
1063
+ schema.required!.push(fieldName);
1064
+ }
1065
+
1066
+ schema.properties![fieldName] = prop;
1067
+ }
1068
+
1069
+ return schema;
1070
+ }
1071
+
1072
+ // ===== Error Helpers =====
1073
+
1074
+ export function createPlaygroundError(
1075
+ type: PlaygroundError['type'],
1076
+ message: string,
1077
+ details?: string,
1078
+ recoverable = true
1079
+ ): PlaygroundError {
1080
+ return {
1081
+ type,
1082
+ message,
1083
+ details,
1084
+ recoverable,
1085
+ retryCount: 0,
1086
+ };
1087
+ }
1088
+
1089
+ export function isSyntaxError(error: PlaygroundError | null): boolean {
1090
+ return error?.type === 'syntax';
1091
+ }
1092
+
1093
+ export function isServerError(error: PlaygroundError | null): boolean {
1094
+ return error?.type === 'server';
1095
+ }
1096
+
1097
+ export function isRecoverable(error: PlaygroundError | null): boolean {
1098
+ return error?.recoverable ?? false;
1099
+ }
1100
+
1101
+ // ===== Analytics Helpers (Optional) =====
1102
+
1103
+ export interface PlaygroundAnalytics {
1104
+ trackToolExecution: (toolName: string, success: boolean, executionTime: number) => void;
1105
+ trackError: (error: PlaygroundError) => void;
1106
+ trackShare: (method: 'url' | 'gist') => void;
1107
+ }
1108
+
1109
+ export function createAnalyticsTracker(): PlaygroundAnalytics {
1110
+ return {
1111
+ trackToolExecution: (toolName, success, executionTime) => {
1112
+ if (typeof window !== 'undefined' && 'gtag' in window) {
1113
+ (window as unknown as { gtag: (...args: unknown[]) => void }).gtag('event', 'playground_tool_execution', {
1114
+ tool_name: toolName,
1115
+ success,
1116
+ execution_time: executionTime,
1117
+ });
1118
+ }
1119
+ // Also log to console in development
1120
+ if (process.env.NODE_ENV === 'development') {
1121
+ console.log('[Analytics] Tool execution:', { toolName, success, executionTime });
1122
+ }
1123
+ },
1124
+ trackError: (error) => {
1125
+ if (typeof window !== 'undefined' && 'gtag' in window) {
1126
+ (window as unknown as { gtag: (...args: unknown[]) => void }).gtag('event', 'playground_error', {
1127
+ error_type: error.type,
1128
+ error_message: error.message,
1129
+ recoverable: error.recoverable,
1130
+ });
1131
+ }
1132
+ if (process.env.NODE_ENV === 'development') {
1133
+ console.log('[Analytics] Error:', error);
1134
+ }
1135
+ },
1136
+ trackShare: (method) => {
1137
+ if (typeof window !== 'undefined' && 'gtag' in window) {
1138
+ (window as unknown as { gtag: (...args: unknown[]) => void }).gtag('event', 'playground_share', {
1139
+ method,
1140
+ });
1141
+ }
1142
+ if (process.env.NODE_ENV === 'development') {
1143
+ console.log('[Analytics] Share:', method);
1144
+ }
1145
+ },
1146
+ };
1147
+ }