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,871 @@
1
+ /**
2
+ * Enhanced MCP Client with Advanced Features
3
+ *
4
+ * Builds on top of the base mcp-client.ts with:
5
+ * - WebSocket transport for real-time bidirectional communication
6
+ * - Automatic retry with exponential backoff
7
+ * - Request queuing and rate limiting
8
+ * - Event emitter pattern for notifications
9
+ * - Connection health monitoring with heartbeat
10
+ * - Streaming support for tool calls
11
+ *
12
+ * @author nich (x.com/nichxbt | github.com/nirholas)
13
+ * @copyright 2024-2026 nich (nirholas)
14
+ * @license MIT
15
+ */
16
+
17
+ import {
18
+ JsonRpcRequest,
19
+ JsonRpcResponse,
20
+ JsonRpcNotification,
21
+ McpTool,
22
+ CallToolResult,
23
+ ServerCapabilities,
24
+ ServerInfo,
25
+ MCP_METHODS,
26
+ MCP_PROTOCOL_VERSION,
27
+ isJsonRpcError,
28
+ ToolContent,
29
+ TextContent,
30
+ } from './mcp-types';
31
+
32
+ import {
33
+ McpError,
34
+ McpConnectionError,
35
+ McpTimeoutError,
36
+ McpConnectionClosedError,
37
+ McpServerNotInitializedError,
38
+ McpToolNotFoundError,
39
+ McpToolTimeoutError,
40
+ createErrorFromJsonRpc,
41
+ wrapError,
42
+ isRetryableError,
43
+ } from './mcp-errors';
44
+
45
+ import { IMcpTransport, McpClientState } from './mcp-client';
46
+
47
+ // ============================================================================
48
+ // Event Emitter
49
+ // ============================================================================
50
+
51
+ /** Enhanced client - nich (x.com/nichxbt | github.com/nirholas) */
52
+ const _ENHANCED_META = { author: 'nich', links: ['x.com/nichxbt', 'github.com/nirholas'] } as const;
53
+
54
+ type EventCallback<T = unknown> = (data: T) => void;
55
+
56
+ /**
57
+ * Simple typed event emitter for MCP events
58
+ */
59
+ export class McpEventEmitter {
60
+ private listeners: Map<string, Set<EventCallback>> = new Map();
61
+
62
+ on<T>(event: string, callback: EventCallback<T>): () => void {
63
+ if (!this.listeners.has(event)) {
64
+ this.listeners.set(event, new Set());
65
+ }
66
+ this.listeners.get(event)!.add(callback as EventCallback);
67
+
68
+ // Return unsubscribe function
69
+ return () => this.off(event, callback as EventCallback);
70
+ }
71
+
72
+ off(event: string, callback: EventCallback): void {
73
+ this.listeners.get(event)?.delete(callback);
74
+ }
75
+
76
+ emit<T>(event: string, data: T): void {
77
+ this.listeners.get(event)?.forEach(cb => {
78
+ try {
79
+ cb(data);
80
+ } catch (error) {
81
+ console.error(`Error in event listener for ${event}:`, error);
82
+ }
83
+ });
84
+ }
85
+
86
+ removeAllListeners(event?: string): void {
87
+ if (event) {
88
+ this.listeners.delete(event);
89
+ } else {
90
+ this.listeners.clear();
91
+ }
92
+ }
93
+ }
94
+
95
+ // ============================================================================
96
+ // Enhanced Client Events
97
+ // ============================================================================
98
+
99
+ export interface McpClientEvents {
100
+ 'state:change': { previous: McpClientState; current: McpClientState };
101
+ 'connected': { serverInfo: ServerInfo; capabilities: ServerCapabilities };
102
+ 'disconnected': { reason: string };
103
+ 'error': { error: McpError };
104
+ 'notification': { method: string; params?: Record<string, unknown> };
105
+ 'tool:start': { name: string; args?: Record<string, unknown> };
106
+ 'tool:progress': { name: string; progress: number; message?: string };
107
+ 'tool:complete': { name: string; result: CallToolResult; duration: number };
108
+ 'tool:error': { name: string; error: McpError };
109
+ 'tools:changed': { tools: McpTool[] };
110
+ 'heartbeat': { latency: number };
111
+ 'reconnecting': { attempt: number; maxAttempts: number; delay: number };
112
+ }
113
+
114
+ // ============================================================================
115
+ // Retry Configuration
116
+ // ============================================================================
117
+
118
+ export interface RetryConfig {
119
+ /** Maximum number of retry attempts */
120
+ maxAttempts: number;
121
+ /** Initial delay in ms */
122
+ initialDelay: number;
123
+ /** Maximum delay in ms */
124
+ maxDelay: number;
125
+ /** Backoff multiplier */
126
+ backoffMultiplier: number;
127
+ /** Add jitter to prevent thundering herd */
128
+ jitter: boolean;
129
+ }
130
+
131
+ const DEFAULT_RETRY_CONFIG: RetryConfig = {
132
+ maxAttempts: 3,
133
+ initialDelay: 1000,
134
+ maxDelay: 30000,
135
+ backoffMultiplier: 2,
136
+ jitter: true,
137
+ };
138
+
139
+ /**
140
+ * Calculate retry delay with exponential backoff
141
+ */
142
+ function calculateRetryDelay(attempt: number, config: RetryConfig): number {
143
+ let delay = config.initialDelay * Math.pow(config.backoffMultiplier, attempt);
144
+ delay = Math.min(delay, config.maxDelay);
145
+
146
+ if (config.jitter) {
147
+ // Add random jitter of ±25%
148
+ const jitterRange = delay * 0.25;
149
+ delay += (Math.random() - 0.5) * 2 * jitterRange;
150
+ }
151
+
152
+ return Math.round(delay);
153
+ }
154
+
155
+ // ============================================================================
156
+ // Request Queue
157
+ // ============================================================================
158
+
159
+ interface QueuedRequest {
160
+ request: JsonRpcRequest;
161
+ resolve: (response: JsonRpcResponse) => void;
162
+ reject: (error: Error) => void;
163
+ priority: number;
164
+ timestamp: number;
165
+ retryCount: number;
166
+ }
167
+
168
+ /**
169
+ * Priority request queue with rate limiting
170
+ */
171
+ export class RequestQueue {
172
+ private queue: QueuedRequest[] = [];
173
+ private processing: boolean = false;
174
+ private readonly maxConcurrent: number;
175
+ private readonly minInterval: number;
176
+ private activeRequests: number = 0;
177
+ private lastRequestTime: number = 0;
178
+
179
+ constructor(options: { maxConcurrent?: number; minInterval?: number } = {}) {
180
+ this.maxConcurrent = options.maxConcurrent ?? 10;
181
+ this.minInterval = options.minInterval ?? 50; // 50ms minimum between requests
182
+ }
183
+
184
+ async enqueue(
185
+ request: JsonRpcRequest,
186
+ sender: (req: JsonRpcRequest) => Promise<JsonRpcResponse>,
187
+ priority: number = 0
188
+ ): Promise<JsonRpcResponse> {
189
+ return new Promise((resolve, reject) => {
190
+ const queued: QueuedRequest = {
191
+ request,
192
+ resolve,
193
+ reject,
194
+ priority,
195
+ timestamp: Date.now(),
196
+ retryCount: 0,
197
+ };
198
+
199
+ // Insert by priority (higher priority first)
200
+ const insertIndex = this.queue.findIndex(q => q.priority < priority);
201
+ if (insertIndex === -1) {
202
+ this.queue.push(queued);
203
+ } else {
204
+ this.queue.splice(insertIndex, 0, queued);
205
+ }
206
+
207
+ this.processQueue(sender);
208
+ });
209
+ }
210
+
211
+ private async processQueue(
212
+ sender: (req: JsonRpcRequest) => Promise<JsonRpcResponse>
213
+ ): Promise<void> {
214
+ if (this.processing || this.queue.length === 0) {
215
+ return;
216
+ }
217
+
218
+ this.processing = true;
219
+
220
+ while (this.queue.length > 0 && this.activeRequests < this.maxConcurrent) {
221
+ // Rate limiting
222
+ const timeSinceLastRequest = Date.now() - this.lastRequestTime;
223
+ if (timeSinceLastRequest < this.minInterval) {
224
+ await new Promise(r => setTimeout(r, this.minInterval - timeSinceLastRequest));
225
+ }
226
+
227
+ const queued = this.queue.shift();
228
+ if (!queued) break;
229
+
230
+ this.activeRequests++;
231
+ this.lastRequestTime = Date.now();
232
+
233
+ // Process request without blocking the loop
234
+ sender(queued.request)
235
+ .then(response => queued.resolve(response))
236
+ .catch(error => queued.reject(error))
237
+ .finally(() => {
238
+ this.activeRequests--;
239
+ this.processQueue(sender);
240
+ });
241
+ }
242
+
243
+ this.processing = false;
244
+ }
245
+
246
+ clear(): void {
247
+ this.queue.forEach(q => q.reject(new Error('Queue cleared')));
248
+ this.queue = [];
249
+ }
250
+
251
+ get length(): number {
252
+ return this.queue.length;
253
+ }
254
+
255
+ get pending(): number {
256
+ return this.activeRequests;
257
+ }
258
+ }
259
+
260
+ // ============================================================================
261
+ // WebSocket Transport
262
+ // ============================================================================
263
+
264
+ export interface WebSocketTransportOptions {
265
+ /** WebSocket endpoint URL */
266
+ url: string;
267
+ /** Reconnect on disconnect */
268
+ autoReconnect?: boolean;
269
+ /** Reconnect configuration */
270
+ reconnectConfig?: RetryConfig;
271
+ /** Heartbeat interval in ms (0 to disable) */
272
+ heartbeatInterval?: number;
273
+ }
274
+
275
+ /**
276
+ * WebSocket transport for real-time bidirectional MCP communication
277
+ */
278
+ export class WebSocketTransport implements IMcpTransport {
279
+ private url: string;
280
+ private socket: WebSocket | null = null;
281
+ private messageHandler?: (message: JsonRpcNotification) => void;
282
+ private pendingRequests: Map<number | string, {
283
+ resolve: (response: JsonRpcResponse) => void;
284
+ reject: (error: Error) => void;
285
+ timeout: NodeJS.Timeout;
286
+ }> = new Map();
287
+ private autoReconnect: boolean;
288
+ private reconnectConfig: RetryConfig;
289
+ private heartbeatInterval: number;
290
+ private heartbeatTimer?: NodeJS.Timeout;
291
+ private reconnectAttempt: number = 0;
292
+ private intentionalClose: boolean = false;
293
+ private onReconnecting?: (attempt: number, maxAttempts: number) => void;
294
+
295
+ constructor(options: WebSocketTransportOptions) {
296
+ this.url = options.url;
297
+ this.autoReconnect = options.autoReconnect ?? true;
298
+ this.reconnectConfig = options.reconnectConfig ?? DEFAULT_RETRY_CONFIG;
299
+ this.heartbeatInterval = options.heartbeatInterval ?? 30000;
300
+ }
301
+
302
+ async start(): Promise<void> {
303
+ return new Promise((resolve, reject) => {
304
+ try {
305
+ this.intentionalClose = false;
306
+ this.socket = new WebSocket(this.url);
307
+
308
+ const connectionTimeout = setTimeout(() => {
309
+ this.socket?.close();
310
+ reject(new McpConnectionError('WebSocket connection timeout'));
311
+ }, 10000);
312
+
313
+ this.socket.onopen = () => {
314
+ clearTimeout(connectionTimeout);
315
+ this.reconnectAttempt = 0;
316
+ this.startHeartbeat();
317
+ resolve();
318
+ };
319
+
320
+ this.socket.onmessage = (event) => {
321
+ this.handleMessage(event.data);
322
+ };
323
+
324
+ this.socket.onerror = (event) => {
325
+ clearTimeout(connectionTimeout);
326
+ console.error('WebSocket error:', event);
327
+ };
328
+
329
+ this.socket.onclose = (event) => {
330
+ clearTimeout(connectionTimeout);
331
+ this.stopHeartbeat();
332
+ this.rejectAllPending(new McpConnectionClosedError(
333
+ `WebSocket closed: ${event.code} ${event.reason}`
334
+ ));
335
+
336
+ if (!this.intentionalClose && this.autoReconnect) {
337
+ this.attemptReconnect();
338
+ }
339
+ };
340
+ } catch (error) {
341
+ reject(wrapError(error, 'Failed to create WebSocket'));
342
+ }
343
+ });
344
+ }
345
+
346
+ async stop(): Promise<void> {
347
+ this.intentionalClose = true;
348
+ this.stopHeartbeat();
349
+ this.rejectAllPending(new McpConnectionClosedError('Transport stopped'));
350
+
351
+ if (this.socket) {
352
+ this.socket.close(1000, 'Client disconnect');
353
+ this.socket = null;
354
+ }
355
+ }
356
+
357
+ isConnected(): boolean {
358
+ return this.socket?.readyState === WebSocket.OPEN;
359
+ }
360
+
361
+ onMessage(handler: (message: JsonRpcNotification) => void): void {
362
+ this.messageHandler = handler;
363
+ }
364
+
365
+ setReconnectHandler(handler: (attempt: number, maxAttempts: number) => void): void {
366
+ this.onReconnecting = handler;
367
+ }
368
+
369
+ async send(request: JsonRpcRequest): Promise<JsonRpcResponse> {
370
+ if (!this.isConnected()) {
371
+ throw new McpConnectionClosedError('WebSocket is not connected');
372
+ }
373
+
374
+ return new Promise((resolve, reject) => {
375
+ const timeout = setTimeout(() => {
376
+ this.pendingRequests.delete(request.id);
377
+ reject(new McpTimeoutError('Request timeout', 30000));
378
+ }, 30000);
379
+
380
+ this.pendingRequests.set(request.id, { resolve, reject, timeout });
381
+ this.socket!.send(JSON.stringify(request));
382
+ });
383
+ }
384
+
385
+ async notify(notification: JsonRpcNotification): Promise<void> {
386
+ if (!this.isConnected()) {
387
+ throw new McpConnectionClosedError('WebSocket is not connected');
388
+ }
389
+ this.socket!.send(JSON.stringify(notification));
390
+ }
391
+
392
+ private handleMessage(data: string): void {
393
+ try {
394
+ const message = JSON.parse(data);
395
+
396
+ // Check if it's a response to a pending request
397
+ if ('id' in message && message.id !== null) {
398
+ const pending = this.pendingRequests.get(message.id);
399
+ if (pending) {
400
+ clearTimeout(pending.timeout);
401
+ this.pendingRequests.delete(message.id);
402
+ pending.resolve(message as JsonRpcResponse);
403
+ return;
404
+ }
405
+ }
406
+
407
+ // It's a notification
408
+ if (this.messageHandler && !('id' in message)) {
409
+ this.messageHandler(message as JsonRpcNotification);
410
+ }
411
+ } catch (error) {
412
+ console.error('Failed to parse WebSocket message:', error);
413
+ }
414
+ }
415
+
416
+ private rejectAllPending(error: Error): void {
417
+ this.pendingRequests.forEach(pending => {
418
+ clearTimeout(pending.timeout);
419
+ pending.reject(error);
420
+ });
421
+ this.pendingRequests.clear();
422
+ }
423
+
424
+ private async attemptReconnect(): Promise<void> {
425
+ if (this.reconnectAttempt >= this.reconnectConfig.maxAttempts) {
426
+ console.error('Max reconnect attempts reached');
427
+ return;
428
+ }
429
+
430
+ this.reconnectAttempt++;
431
+ const delay = calculateRetryDelay(this.reconnectAttempt - 1, this.reconnectConfig);
432
+
433
+ this.onReconnecting?.(this.reconnectAttempt, this.reconnectConfig.maxAttempts);
434
+ console.log(`Reconnecting in ${delay}ms (attempt ${this.reconnectAttempt}/${this.reconnectConfig.maxAttempts})`);
435
+
436
+ await new Promise(r => setTimeout(r, delay));
437
+
438
+ try {
439
+ await this.start();
440
+ } catch (error) {
441
+ // Will trigger another reconnect attempt via onclose
442
+ }
443
+ }
444
+
445
+ private startHeartbeat(): void {
446
+ if (this.heartbeatInterval <= 0) return;
447
+
448
+ this.heartbeatTimer = setInterval(() => {
449
+ if (this.isConnected()) {
450
+ // Send a ping (could also use MCP's ping if supported)
451
+ this.socket!.send(JSON.stringify({ jsonrpc: '2.0', method: 'ping' }));
452
+ }
453
+ }, this.heartbeatInterval);
454
+ }
455
+
456
+ private stopHeartbeat(): void {
457
+ if (this.heartbeatTimer) {
458
+ clearInterval(this.heartbeatTimer);
459
+ this.heartbeatTimer = undefined;
460
+ }
461
+ }
462
+ }
463
+
464
+ // ============================================================================
465
+ // Enhanced MCP Client
466
+ // ============================================================================
467
+
468
+ export interface EnhancedMcpClientOptions {
469
+ /** Request timeout in milliseconds */
470
+ timeout?: number;
471
+ /** Tool call timeout */
472
+ toolTimeout?: number;
473
+ /** Client identification */
474
+ clientName?: string;
475
+ clientVersion?: string;
476
+ /** Retry configuration */
477
+ retryConfig?: Partial<RetryConfig>;
478
+ /** Enable request queuing */
479
+ enableQueue?: boolean;
480
+ /** Queue options */
481
+ queueOptions?: { maxConcurrent?: number; minInterval?: number };
482
+ /** Auto-reconnect on connection loss */
483
+ autoReconnect?: boolean;
484
+ /** Cache tool list */
485
+ cacheTools?: boolean;
486
+ }
487
+
488
+ const DEFAULT_ENHANCED_OPTIONS: Required<EnhancedMcpClientOptions> = {
489
+ timeout: 30000,
490
+ toolTimeout: 60000,
491
+ clientName: 'github-to-mcp-client',
492
+ clientVersion: '1.0.0',
493
+ retryConfig: DEFAULT_RETRY_CONFIG,
494
+ enableQueue: true,
495
+ queueOptions: { maxConcurrent: 10, minInterval: 50 },
496
+ autoReconnect: true,
497
+ cacheTools: true,
498
+ };
499
+
500
+ /**
501
+ * Enhanced MCP Client with advanced features
502
+ */
503
+ export class EnhancedMcpClient extends McpEventEmitter {
504
+ private transport: IMcpTransport;
505
+ private options: Required<EnhancedMcpClientOptions>;
506
+ private _state: McpClientState = 'disconnected';
507
+ private _capabilities: ServerCapabilities | null = null;
508
+ private _serverInfo: ServerInfo | null = null;
509
+ private requestId: number = 0;
510
+ private cachedTools: McpTool[] | null = null;
511
+ private requestQueue: RequestQueue | null = null;
512
+ private retryConfig: RetryConfig;
513
+
514
+ constructor(transport: IMcpTransport, options: EnhancedMcpClientOptions = {}) {
515
+ super();
516
+ this.transport = transport;
517
+ this.options = { ...DEFAULT_ENHANCED_OPTIONS, ...options };
518
+ this.retryConfig = { ...DEFAULT_RETRY_CONFIG, ...options.retryConfig };
519
+
520
+ if (this.options.enableQueue) {
521
+ this.requestQueue = new RequestQueue(this.options.queueOptions);
522
+ }
523
+
524
+ // Set up notification handler
525
+ this.transport.onMessage((notification) => {
526
+ this.handleNotification(notification);
527
+ });
528
+ }
529
+
530
+ get state(): McpClientState {
531
+ return this._state;
532
+ }
533
+
534
+ get capabilities(): ServerCapabilities | null {
535
+ return this._capabilities;
536
+ }
537
+
538
+ get serverInfo(): ServerInfo | null {
539
+ return this._serverInfo;
540
+ }
541
+
542
+ // ============================================================================
543
+ // Connection Management
544
+ // ============================================================================
545
+
546
+ async connect(): Promise<void> {
547
+ if (this._state === 'ready') {
548
+ return;
549
+ }
550
+
551
+ if (this._state === 'connecting' || this._state === 'initializing') {
552
+ throw new McpError('Connection already in progress', -32001);
553
+ }
554
+
555
+ try {
556
+ this.setState('connecting');
557
+
558
+ await this.transport.start();
559
+
560
+ this.setState('initializing');
561
+
562
+ const initResult = await this.request<{
563
+ protocolVersion: string;
564
+ capabilities: ServerCapabilities;
565
+ serverInfo: ServerInfo;
566
+ }>(MCP_METHODS.INITIALIZE, {
567
+ protocolVersion: MCP_PROTOCOL_VERSION,
568
+ capabilities: { roots: { listChanged: true } },
569
+ clientInfo: {
570
+ name: this.options.clientName,
571
+ version: this.options.clientVersion,
572
+ },
573
+ });
574
+
575
+ this._capabilities = initResult.capabilities;
576
+ this._serverInfo = initResult.serverInfo;
577
+
578
+ await this.notify(MCP_METHODS.INITIALIZED);
579
+
580
+ this.setState('ready');
581
+ this.emit('connected', {
582
+ serverInfo: initResult.serverInfo,
583
+ capabilities: initResult.capabilities,
584
+ });
585
+ } catch (error) {
586
+ this.setState('error');
587
+ const wrappedError = wrapError(error, 'Failed to connect');
588
+ this.emit('error', { error: wrappedError });
589
+ throw wrappedError;
590
+ }
591
+ }
592
+
593
+ async disconnect(): Promise<void> {
594
+ if (this._state === 'disconnected' || this._state === 'closed') {
595
+ return;
596
+ }
597
+
598
+ try {
599
+ if (this._state === 'ready') {
600
+ await this.request(MCP_METHODS.SHUTDOWN, {}).catch(() => {});
601
+ }
602
+
603
+ await this.transport.stop();
604
+ this.requestQueue?.clear();
605
+ this.setState('closed');
606
+ this._capabilities = null;
607
+ this._serverInfo = null;
608
+ this.cachedTools = null;
609
+ this.emit('disconnected', { reason: 'Client disconnect' });
610
+ } catch (error) {
611
+ this.setState('error');
612
+ throw wrapError(error, 'Failed to disconnect');
613
+ }
614
+ }
615
+
616
+ // ============================================================================
617
+ // Tool Operations
618
+ // ============================================================================
619
+
620
+ async listTools(forceRefresh: boolean = false): Promise<McpTool[]> {
621
+ this.ensureReady();
622
+
623
+ if (this.options.cacheTools && this.cachedTools && !forceRefresh) {
624
+ return this.cachedTools;
625
+ }
626
+
627
+ const result = await this.request<{ tools: McpTool[] }>(MCP_METHODS.TOOLS_LIST);
628
+ this.cachedTools = result.tools;
629
+ return result.tools;
630
+ }
631
+
632
+ async callTool(
633
+ name: string,
634
+ args?: Record<string, unknown>,
635
+ options?: { timeout?: number; priority?: number }
636
+ ): Promise<CallToolResult> {
637
+ this.ensureReady();
638
+
639
+ const tools = await this.listTools();
640
+ const tool = tools.find(t => t.name === name);
641
+ if (!tool) {
642
+ throw new McpToolNotFoundError(name);
643
+ }
644
+
645
+ const startTime = Date.now();
646
+ this.emit('tool:start', { name, args });
647
+
648
+ try {
649
+ const result = await this.request<CallToolResult>(
650
+ MCP_METHODS.TOOLS_CALL,
651
+ { name, arguments: args },
652
+ options?.timeout ?? this.options.toolTimeout,
653
+ options?.priority ?? 0
654
+ );
655
+
656
+ const duration = Date.now() - startTime;
657
+ this.emit('tool:complete', { name, result, duration });
658
+ return result;
659
+ } catch (error) {
660
+ const mcpError = error instanceof McpError ? error : wrapError(error);
661
+ this.emit('tool:error', { name, error: mcpError });
662
+
663
+ if (error instanceof McpTimeoutError) {
664
+ throw new McpToolTimeoutError(name, this.options.toolTimeout);
665
+ }
666
+ throw error;
667
+ }
668
+ }
669
+
670
+ /**
671
+ * Call a tool with automatic retry on retryable errors
672
+ */
673
+ async callToolWithRetry(
674
+ name: string,
675
+ args?: Record<string, unknown>,
676
+ retryConfig?: Partial<RetryConfig>
677
+ ): Promise<CallToolResult> {
678
+ const config = { ...this.retryConfig, ...retryConfig };
679
+ let lastError: Error | null = null;
680
+
681
+ for (let attempt = 0; attempt < config.maxAttempts; attempt++) {
682
+ try {
683
+ return await this.callTool(name, args);
684
+ } catch (error) {
685
+ lastError = error as Error;
686
+
687
+ if (!isRetryableError(error) || attempt === config.maxAttempts - 1) {
688
+ throw error;
689
+ }
690
+
691
+ const delay = calculateRetryDelay(attempt, config);
692
+ this.emit('reconnecting', {
693
+ attempt: attempt + 1,
694
+ maxAttempts: config.maxAttempts,
695
+ delay
696
+ });
697
+ await new Promise(r => setTimeout(r, delay));
698
+ }
699
+ }
700
+
701
+ throw lastError;
702
+ }
703
+
704
+ // ============================================================================
705
+ // Request Handling
706
+ // ============================================================================
707
+
708
+ private async request<T>(
709
+ method: string,
710
+ params?: object,
711
+ timeout?: number,
712
+ priority?: number
713
+ ): Promise<T> {
714
+ const request: JsonRpcRequest = {
715
+ jsonrpc: '2.0',
716
+ id: ++this.requestId,
717
+ method,
718
+ ...(params && { params: params as Record<string, unknown> }),
719
+ };
720
+
721
+ const effectiveTimeout = timeout ?? this.options.timeout;
722
+
723
+ const sendRequest = async (req: JsonRpcRequest): Promise<JsonRpcResponse> => {
724
+ const timeoutPromise = new Promise<never>((_, reject) => {
725
+ setTimeout(
726
+ () => reject(new McpTimeoutError(`Request timed out: ${method}`, effectiveTimeout)),
727
+ effectiveTimeout
728
+ );
729
+ });
730
+
731
+ return Promise.race([this.transport.send(req), timeoutPromise]);
732
+ };
733
+
734
+ let response: JsonRpcResponse;
735
+
736
+ if (this.requestQueue) {
737
+ response = await this.requestQueue.enqueue(request, sendRequest, priority);
738
+ } else {
739
+ response = await sendRequest(request);
740
+ }
741
+
742
+ if (isJsonRpcError(response)) {
743
+ throw createErrorFromJsonRpc(response.error);
744
+ }
745
+
746
+ if ('result' in response) {
747
+ return response.result as T;
748
+ }
749
+
750
+ throw new McpError('Invalid response format', -32600);
751
+ }
752
+
753
+ private async notify(method: string, params?: Record<string, unknown>): Promise<void> {
754
+ const notification: JsonRpcNotification = {
755
+ jsonrpc: '2.0',
756
+ method,
757
+ ...(params && { params }),
758
+ };
759
+ await this.transport.notify(notification);
760
+ }
761
+
762
+ // ============================================================================
763
+ // State Management
764
+ // ============================================================================
765
+
766
+ private setState(newState: McpClientState): void {
767
+ const previous = this._state;
768
+ this._state = newState;
769
+ this.emit('state:change', { previous, current: newState });
770
+ }
771
+
772
+ private ensureReady(): void {
773
+ if (this._state !== 'ready') {
774
+ throw new McpServerNotInitializedError(`Client is not ready. State: ${this._state}`);
775
+ }
776
+ }
777
+
778
+ private handleNotification(notification: JsonRpcNotification): void {
779
+ const { method, params } = notification;
780
+
781
+ if (method === MCP_METHODS.NOTIFICATION_TOOLS_LIST_CHANGED) {
782
+ this.cachedTools = null;
783
+ this.listTools().then(tools => {
784
+ this.emit('tools:changed', { tools });
785
+ });
786
+ }
787
+
788
+ this.emit('notification', { method, params });
789
+ }
790
+ }
791
+
792
+ // ============================================================================
793
+ // Factory Functions
794
+ // ============================================================================
795
+
796
+ /**
797
+ * Create an enhanced MCP client with WebSocket transport
798
+ */
799
+ export function createWebSocketClient(
800
+ url: string,
801
+ options?: EnhancedMcpClientOptions & WebSocketTransportOptions
802
+ ): EnhancedMcpClient {
803
+ const transport = new WebSocketTransport({
804
+ url,
805
+ autoReconnect: options?.autoReconnect,
806
+ reconnectConfig: options?.retryConfig as RetryConfig,
807
+ heartbeatInterval: 30000,
808
+ });
809
+
810
+ return new EnhancedMcpClient(transport, options);
811
+ }
812
+
813
+ // ============================================================================
814
+ // Utility Functions
815
+ // ============================================================================
816
+
817
+ /**
818
+ * Extract all text content from a tool result
819
+ */
820
+ export function extractAllTextContent(result: CallToolResult): string[] {
821
+ return result.content
822
+ .filter((c): c is TextContent => c.type === 'text')
823
+ .map(c => c.text);
824
+ }
825
+
826
+ /**
827
+ * Check if tool result indicates an error
828
+ */
829
+ export function hasToolError(result: CallToolResult): boolean {
830
+ return result.isError === true;
831
+ }
832
+
833
+ /**
834
+ * Format tool result for display
835
+ */
836
+ export function formatToolResult(result: CallToolResult): string {
837
+ const texts = extractAllTextContent(result);
838
+ if (texts.length > 0) {
839
+ return texts.join('\n');
840
+ }
841
+ return JSON.stringify(result.content, null, 2);
842
+ }
843
+
844
+ /**
845
+ * Create a promise that resolves when client reaches a specific state
846
+ */
847
+ export function waitForState(
848
+ client: EnhancedMcpClient,
849
+ targetState: McpClientState,
850
+ timeout: number = 30000
851
+ ): Promise<void> {
852
+ return new Promise((resolve, reject) => {
853
+ if (client.state === targetState) {
854
+ resolve();
855
+ return;
856
+ }
857
+
858
+ const timeoutId = setTimeout(() => {
859
+ unsubscribe();
860
+ reject(new McpTimeoutError(`Timeout waiting for state: ${targetState}`, timeout));
861
+ }, timeout);
862
+
863
+ const unsubscribe = client.on<McpClientEvents['state:change']>('state:change', ({ current }) => {
864
+ if (current === targetState) {
865
+ clearTimeout(timeoutId);
866
+ unsubscribe();
867
+ resolve();
868
+ }
869
+ });
870
+ });
871
+ }