patchwork-os 0.2.0-alpha.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 (721) hide show
  1. package/LICENSE +21 -0
  2. package/README.bridge.md +352 -0
  3. package/README.md +72 -0
  4. package/deploy/README.md +172 -0
  5. package/deploy/bootstrap-new-vps.sh +364 -0
  6. package/deploy/claude-ide-bridge.service.template +67 -0
  7. package/deploy/claude-ide-bridge@.service +31 -0
  8. package/deploy/ecosystem.config.js.example +36 -0
  9. package/deploy/install-vps-service.sh +240 -0
  10. package/deploy/nginx-claude-bridge.conf.template +129 -0
  11. package/dist/activityLog.d.ts +112 -0
  12. package/dist/activityLog.js +399 -0
  13. package/dist/activityLog.js.map +1 -0
  14. package/dist/activityTypes.d.ts +28 -0
  15. package/dist/activityTypes.js +9 -0
  16. package/dist/activityTypes.js.map +1 -0
  17. package/dist/adapters/base.d.ts +78 -0
  18. package/dist/adapters/base.js +14 -0
  19. package/dist/adapters/base.js.map +1 -0
  20. package/dist/adapters/claude.d.ts +18 -0
  21. package/dist/adapters/claude.js +276 -0
  22. package/dist/adapters/claude.js.map +1 -0
  23. package/dist/adapters/gemini.d.ts +17 -0
  24. package/dist/adapters/gemini.js +218 -0
  25. package/dist/adapters/gemini.js.map +1 -0
  26. package/dist/adapters/grok.d.ts +7 -0
  27. package/dist/adapters/grok.js +21 -0
  28. package/dist/adapters/grok.js.map +1 -0
  29. package/dist/adapters/index.d.ts +5 -0
  30. package/dist/adapters/index.js +37 -0
  31. package/dist/adapters/index.js.map +1 -0
  32. package/dist/adapters/local.d.ts +7 -0
  33. package/dist/adapters/local.js +22 -0
  34. package/dist/adapters/local.js.map +1 -0
  35. package/dist/adapters/openai.d.ts +22 -0
  36. package/dist/adapters/openai.js +284 -0
  37. package/dist/adapters/openai.js.map +1 -0
  38. package/dist/adapters/sse.d.ts +13 -0
  39. package/dist/adapters/sse.js +58 -0
  40. package/dist/adapters/sse.js.map +1 -0
  41. package/dist/analyticsAggregator.d.ts +28 -0
  42. package/dist/analyticsAggregator.js +133 -0
  43. package/dist/analyticsAggregator.js.map +1 -0
  44. package/dist/analyticsPrefs.d.ts +9 -0
  45. package/dist/analyticsPrefs.js +50 -0
  46. package/dist/analyticsPrefs.js.map +1 -0
  47. package/dist/analyticsSend.d.ts +12 -0
  48. package/dist/analyticsSend.js +34 -0
  49. package/dist/analyticsSend.js.map +1 -0
  50. package/dist/approvalHttp.d.ts +46 -0
  51. package/dist/approvalHttp.js +370 -0
  52. package/dist/approvalHttp.js.map +1 -0
  53. package/dist/approvalQueue.d.ts +49 -0
  54. package/dist/approvalQueue.js +84 -0
  55. package/dist/approvalQueue.js.map +1 -0
  56. package/dist/automation.d.ts +675 -0
  57. package/dist/automation.js +1038 -0
  58. package/dist/automation.js.map +1 -0
  59. package/dist/bridge.d.ts +85 -0
  60. package/dist/bridge.js +1535 -0
  61. package/dist/bridge.js.map +1 -0
  62. package/dist/bridgeLockDiscovery.d.ts +11 -0
  63. package/dist/bridgeLockDiscovery.js +49 -0
  64. package/dist/bridgeLockDiscovery.js.map +1 -0
  65. package/dist/bridgeToken.d.ts +22 -0
  66. package/dist/bridgeToken.js +114 -0
  67. package/dist/bridgeToken.js.map +1 -0
  68. package/dist/bridgeToolsRules.d.ts +20 -0
  69. package/dist/bridgeToolsRules.js +79 -0
  70. package/dist/bridgeToolsRules.js.map +1 -0
  71. package/dist/ccPermissions.d.ts +59 -0
  72. package/dist/ccPermissions.js +163 -0
  73. package/dist/ccPermissions.js.map +1 -0
  74. package/dist/claudeDriver.d.ts +129 -0
  75. package/dist/claudeDriver.js +459 -0
  76. package/dist/claudeDriver.js.map +1 -0
  77. package/dist/claudeMdPatch.d.ts +29 -0
  78. package/dist/claudeMdPatch.js +164 -0
  79. package/dist/claudeMdPatch.js.map +1 -0
  80. package/dist/claudeOrchestrator.d.ts +171 -0
  81. package/dist/claudeOrchestrator.js +591 -0
  82. package/dist/claudeOrchestrator.js.map +1 -0
  83. package/dist/commands/install.d.ts +1 -0
  84. package/dist/commands/install.js +158 -0
  85. package/dist/commands/install.js.map +1 -0
  86. package/dist/commands/marketplace.d.ts +11 -0
  87. package/dist/commands/marketplace.js +120 -0
  88. package/dist/commands/marketplace.js.map +1 -0
  89. package/dist/commands/patchworkInit.d.ts +14 -0
  90. package/dist/commands/patchworkInit.js +155 -0
  91. package/dist/commands/patchworkInit.js.map +1 -0
  92. package/dist/commands/task.d.ts +14 -0
  93. package/dist/commands/task.js +289 -0
  94. package/dist/commands/task.js.map +1 -0
  95. package/dist/commands/tokenEfficiency.d.ts +9 -0
  96. package/dist/commands/tokenEfficiency.js +211 -0
  97. package/dist/commands/tokenEfficiency.js.map +1 -0
  98. package/dist/commands/tools.d.ts +28 -0
  99. package/dist/commands/tools.js +326 -0
  100. package/dist/commands/tools.js.map +1 -0
  101. package/dist/commitIssueLinkLog.d.ts +77 -0
  102. package/dist/commitIssueLinkLog.js +142 -0
  103. package/dist/commitIssueLinkLog.js.map +1 -0
  104. package/dist/companions/registry.d.ts +12 -0
  105. package/dist/companions/registry.js +71 -0
  106. package/dist/companions/registry.js.map +1 -0
  107. package/dist/config.d.ts +105 -0
  108. package/dist/config.js +720 -0
  109. package/dist/config.js.map +1 -0
  110. package/dist/crypto.d.ts +16 -0
  111. package/dist/crypto.js +34 -0
  112. package/dist/crypto.js.map +1 -0
  113. package/dist/dashboard.d.ts +12 -0
  114. package/dist/dashboard.js +149 -0
  115. package/dist/dashboard.js.map +1 -0
  116. package/dist/decisionTraceLog.d.ts +77 -0
  117. package/dist/decisionTraceLog.js +147 -0
  118. package/dist/decisionTraceLog.js.map +1 -0
  119. package/dist/errors.d.ts +32 -0
  120. package/dist/errors.js +34 -0
  121. package/dist/errors.js.map +1 -0
  122. package/dist/extensionClient.d.ts +279 -0
  123. package/dist/extensionClient.js +1253 -0
  124. package/dist/extensionClient.js.map +1 -0
  125. package/dist/fileLock.d.ts +36 -0
  126. package/dist/fileLock.js +121 -0
  127. package/dist/fileLock.js.map +1 -0
  128. package/dist/fp/activityAnalytics.d.ts +39 -0
  129. package/dist/fp/activityAnalytics.js +111 -0
  130. package/dist/fp/activityAnalytics.js.map +1 -0
  131. package/dist/fp/async.d.ts +48 -0
  132. package/dist/fp/async.js +60 -0
  133. package/dist/fp/async.js.map +1 -0
  134. package/dist/fp/automationInterpreter.d.ts +37 -0
  135. package/dist/fp/automationInterpreter.js +523 -0
  136. package/dist/fp/automationInterpreter.js.map +1 -0
  137. package/dist/fp/automationProgram.d.ts +89 -0
  138. package/dist/fp/automationProgram.js +29 -0
  139. package/dist/fp/automationProgram.js.map +1 -0
  140. package/dist/fp/automationState.d.ts +135 -0
  141. package/dist/fp/automationState.js +206 -0
  142. package/dist/fp/automationState.js.map +1 -0
  143. package/dist/fp/automationUtils.d.ts +31 -0
  144. package/dist/fp/automationUtils.js +61 -0
  145. package/dist/fp/automationUtils.js.map +1 -0
  146. package/dist/fp/brandedTypes.d.ts +32 -0
  147. package/dist/fp/brandedTypes.js +41 -0
  148. package/dist/fp/brandedTypes.js.map +1 -0
  149. package/dist/fp/commandDescription.d.ts +18 -0
  150. package/dist/fp/commandDescription.js +125 -0
  151. package/dist/fp/commandDescription.js.map +1 -0
  152. package/dist/fp/extensionSnapshot.d.ts +10 -0
  153. package/dist/fp/extensionSnapshot.js +14 -0
  154. package/dist/fp/extensionSnapshot.js.map +1 -0
  155. package/dist/fp/index.d.ts +8 -0
  156. package/dist/fp/index.js +9 -0
  157. package/dist/fp/index.js.map +1 -0
  158. package/dist/fp/interpreterContext.d.ts +69 -0
  159. package/dist/fp/interpreterContext.js +56 -0
  160. package/dist/fp/interpreterContext.js.map +1 -0
  161. package/dist/fp/policyParser.d.ts +16 -0
  162. package/dist/fp/policyParser.js +334 -0
  163. package/dist/fp/policyParser.js.map +1 -0
  164. package/dist/fp/result.d.ts +38 -0
  165. package/dist/fp/result.js +57 -0
  166. package/dist/fp/result.js.map +1 -0
  167. package/dist/fp/tokenBucket.d.ts +27 -0
  168. package/dist/fp/tokenBucket.js +36 -0
  169. package/dist/fp/tokenBucket.js.map +1 -0
  170. package/dist/index.d.ts +2 -0
  171. package/dist/index.js +1465 -0
  172. package/dist/index.js.map +1 -0
  173. package/dist/instructionsUtils.d.ts +17 -0
  174. package/dist/instructionsUtils.js +38 -0
  175. package/dist/instructionsUtils.js.map +1 -0
  176. package/dist/lockfile.d.ts +16 -0
  177. package/dist/lockfile.js +172 -0
  178. package/dist/lockfile.js.map +1 -0
  179. package/dist/logger.d.ts +16 -0
  180. package/dist/logger.js +68 -0
  181. package/dist/logger.js.map +1 -0
  182. package/dist/oauth.d.ts +105 -0
  183. package/dist/oauth.js +880 -0
  184. package/dist/oauth.js.map +1 -0
  185. package/dist/orchestrator/childBridgeClient.d.ts +33 -0
  186. package/dist/orchestrator/childBridgeClient.js +321 -0
  187. package/dist/orchestrator/childBridgeClient.js.map +1 -0
  188. package/dist/orchestrator/childBridgeRegistry.d.ts +67 -0
  189. package/dist/orchestrator/childBridgeRegistry.js +297 -0
  190. package/dist/orchestrator/childBridgeRegistry.js.map +1 -0
  191. package/dist/orchestrator/index.d.ts +3 -0
  192. package/dist/orchestrator/index.js +3 -0
  193. package/dist/orchestrator/index.js.map +1 -0
  194. package/dist/orchestrator/orchestratorBridge.d.ts +32 -0
  195. package/dist/orchestrator/orchestratorBridge.js +412 -0
  196. package/dist/orchestrator/orchestratorBridge.js.map +1 -0
  197. package/dist/orchestrator/orchestratorConfig.d.ts +11 -0
  198. package/dist/orchestrator/orchestratorConfig.js +85 -0
  199. package/dist/orchestrator/orchestratorConfig.js.map +1 -0
  200. package/dist/orchestrator/orchestratorTools.d.ts +16 -0
  201. package/dist/orchestrator/orchestratorTools.js +272 -0
  202. package/dist/orchestrator/orchestratorTools.js.map +1 -0
  203. package/dist/patchworkCli.d.ts +15 -0
  204. package/dist/patchworkCli.js +41 -0
  205. package/dist/patchworkCli.js.map +1 -0
  206. package/dist/patchworkConfig.d.ts +28 -0
  207. package/dist/patchworkConfig.js +30 -0
  208. package/dist/patchworkConfig.js.map +1 -0
  209. package/dist/plugin.d.ts +106 -0
  210. package/dist/plugin.js +31 -0
  211. package/dist/plugin.js.map +1 -0
  212. package/dist/pluginLoader.d.ts +44 -0
  213. package/dist/pluginLoader.js +357 -0
  214. package/dist/pluginLoader.js.map +1 -0
  215. package/dist/pluginWatcher.d.ts +24 -0
  216. package/dist/pluginWatcher.js +139 -0
  217. package/dist/pluginWatcher.js.map +1 -0
  218. package/dist/preToolUseHook.d.ts +10 -0
  219. package/dist/preToolUseHook.js +57 -0
  220. package/dist/preToolUseHook.js.map +1 -0
  221. package/dist/probe.d.ts +35 -0
  222. package/dist/probe.js +143 -0
  223. package/dist/probe.js.map +1 -0
  224. package/dist/prompts.d.ts +27 -0
  225. package/dist/prompts.js +1680 -0
  226. package/dist/prompts.js.map +1 -0
  227. package/dist/quickTaskPresets.d.ts +64 -0
  228. package/dist/quickTaskPresets.js +156 -0
  229. package/dist/quickTaskPresets.js.map +1 -0
  230. package/dist/recipes/compiler.d.ts +44 -0
  231. package/dist/recipes/compiler.js +140 -0
  232. package/dist/recipes/compiler.js.map +1 -0
  233. package/dist/recipes/installer.d.ts +25 -0
  234. package/dist/recipes/installer.js +62 -0
  235. package/dist/recipes/installer.js.map +1 -0
  236. package/dist/recipes/parser.d.ts +18 -0
  237. package/dist/recipes/parser.js +160 -0
  238. package/dist/recipes/parser.js.map +1 -0
  239. package/dist/recipes/scheduler.d.ts +45 -0
  240. package/dist/recipes/scheduler.js +110 -0
  241. package/dist/recipes/scheduler.js.map +1 -0
  242. package/dist/recipes/schema.d.ts +71 -0
  243. package/dist/recipes/schema.js +11 -0
  244. package/dist/recipes/schema.js.map +1 -0
  245. package/dist/recipesHttp.d.ts +63 -0
  246. package/dist/recipesHttp.js +183 -0
  247. package/dist/recipesHttp.js.map +1 -0
  248. package/dist/resources.d.ts +33 -0
  249. package/dist/resources.js +266 -0
  250. package/dist/resources.js.map +1 -0
  251. package/dist/riskTier.d.ts +40 -0
  252. package/dist/riskTier.js +142 -0
  253. package/dist/riskTier.js.map +1 -0
  254. package/dist/runLog.d.ts +90 -0
  255. package/dist/runLog.js +143 -0
  256. package/dist/runLog.js.map +1 -0
  257. package/dist/server.d.ts +160 -0
  258. package/dist/server.js +1244 -0
  259. package/dist/server.js.map +1 -0
  260. package/dist/sessionCheckpoint.d.ts +37 -0
  261. package/dist/sessionCheckpoint.js +123 -0
  262. package/dist/sessionCheckpoint.js.map +1 -0
  263. package/dist/streamableHttp.d.ts +86 -0
  264. package/dist/streamableHttp.js +702 -0
  265. package/dist/streamableHttp.js.map +1 -0
  266. package/dist/telemetry.d.ts +18 -0
  267. package/dist/telemetry.js +95 -0
  268. package/dist/telemetry.js.map +1 -0
  269. package/dist/tools/activityLog.d.ts +140 -0
  270. package/dist/tools/activityLog.js +204 -0
  271. package/dist/tools/activityLog.js.map +1 -0
  272. package/dist/tools/auditDependencies.d.ts +67 -0
  273. package/dist/tools/auditDependencies.js +298 -0
  274. package/dist/tools/auditDependencies.js.map +1 -0
  275. package/dist/tools/batchLsp.d.ts +262 -0
  276. package/dist/tools/batchLsp.js +328 -0
  277. package/dist/tools/batchLsp.js.map +1 -0
  278. package/dist/tools/blame-utils.d.ts +30 -0
  279. package/dist/tools/blame-utils.js +60 -0
  280. package/dist/tools/blame-utils.js.map +1 -0
  281. package/dist/tools/bridgeDoctor.d.ts +78 -0
  282. package/dist/tools/bridgeDoctor.js +542 -0
  283. package/dist/tools/bridgeDoctor.js.map +1 -0
  284. package/dist/tools/bridgeStatus.d.ts +122 -0
  285. package/dist/tools/bridgeStatus.js +250 -0
  286. package/dist/tools/bridgeStatus.js.map +1 -0
  287. package/dist/tools/cancelClaudeTask.d.ts +48 -0
  288. package/dist/tools/cancelClaudeTask.js +56 -0
  289. package/dist/tools/cancelClaudeTask.js.map +1 -0
  290. package/dist/tools/checkDocumentDirty.d.ts +56 -0
  291. package/dist/tools/checkDocumentDirty.js +74 -0
  292. package/dist/tools/checkDocumentDirty.js.map +1 -0
  293. package/dist/tools/clipboard.d.ts +80 -0
  294. package/dist/tools/clipboard.js +211 -0
  295. package/dist/tools/clipboard.js.map +1 -0
  296. package/dist/tools/closeTabs.d.ts +84 -0
  297. package/dist/tools/closeTabs.js +97 -0
  298. package/dist/tools/closeTabs.js.map +1 -0
  299. package/dist/tools/codeLens.d.ts +50 -0
  300. package/dist/tools/codeLens.js +47 -0
  301. package/dist/tools/codeLens.js.map +1 -0
  302. package/dist/tools/contextBundle.d.ts +75 -0
  303. package/dist/tools/contextBundle.js +218 -0
  304. package/dist/tools/contextBundle.js.map +1 -0
  305. package/dist/tools/createIssueFromAIComment.d.ts +75 -0
  306. package/dist/tools/createIssueFromAIComment.js +119 -0
  307. package/dist/tools/createIssueFromAIComment.js.map +1 -0
  308. package/dist/tools/ctxGetTaskContext.d.ts +103 -0
  309. package/dist/tools/ctxGetTaskContext.js +274 -0
  310. package/dist/tools/ctxGetTaskContext.js.map +1 -0
  311. package/dist/tools/ctxQueryTraces.d.ts +142 -0
  312. package/dist/tools/ctxQueryTraces.js +194 -0
  313. package/dist/tools/ctxQueryTraces.js.map +1 -0
  314. package/dist/tools/ctxSaveTrace.d.ts +87 -0
  315. package/dist/tools/ctxSaveTrace.js +94 -0
  316. package/dist/tools/ctxSaveTrace.js.map +1 -0
  317. package/dist/tools/debug.d.ts +206 -0
  318. package/dist/tools/debug.js +234 -0
  319. package/dist/tools/debug.js.map +1 -0
  320. package/dist/tools/decorations.d.ts +130 -0
  321. package/dist/tools/decorations.js +160 -0
  322. package/dist/tools/decorations.js.map +1 -0
  323. package/dist/tools/detectUnusedCode.d.ts +78 -0
  324. package/dist/tools/detectUnusedCode.js +173 -0
  325. package/dist/tools/detectUnusedCode.js.map +1 -0
  326. package/dist/tools/documentLinks.d.ts +62 -0
  327. package/dist/tools/documentLinks.js +55 -0
  328. package/dist/tools/documentLinks.js.map +1 -0
  329. package/dist/tools/editText.d.ts +108 -0
  330. package/dist/tools/editText.js +318 -0
  331. package/dist/tools/editText.js.map +1 -0
  332. package/dist/tools/enrichCommit.d.ts +89 -0
  333. package/dist/tools/enrichCommit.js +201 -0
  334. package/dist/tools/enrichCommit.js.map +1 -0
  335. package/dist/tools/enrichStackTrace.d.ts +121 -0
  336. package/dist/tools/enrichStackTrace.js +194 -0
  337. package/dist/tools/enrichStackTrace.js.map +1 -0
  338. package/dist/tools/explainDiagnostic.d.ts +137 -0
  339. package/dist/tools/explainDiagnostic.js +230 -0
  340. package/dist/tools/explainDiagnostic.js.map +1 -0
  341. package/dist/tools/explainSymbol.d.ts +119 -0
  342. package/dist/tools/explainSymbol.js +177 -0
  343. package/dist/tools/explainSymbol.js.map +1 -0
  344. package/dist/tools/fileOperations.d.ts +186 -0
  345. package/dist/tools/fileOperations.js +330 -0
  346. package/dist/tools/fileOperations.js.map +1 -0
  347. package/dist/tools/fileWatcher.d.ts +107 -0
  348. package/dist/tools/fileWatcher.js +121 -0
  349. package/dist/tools/fileWatcher.js.map +1 -0
  350. package/dist/tools/findFiles.d.ts +65 -0
  351. package/dist/tools/findFiles.js +142 -0
  352. package/dist/tools/findFiles.js.map +1 -0
  353. package/dist/tools/findRelatedTests.d.ts +83 -0
  354. package/dist/tools/findRelatedTests.js +196 -0
  355. package/dist/tools/findRelatedTests.js.map +1 -0
  356. package/dist/tools/fixAllLintErrors.d.ts +66 -0
  357. package/dist/tools/fixAllLintErrors.js +128 -0
  358. package/dist/tools/fixAllLintErrors.js.map +1 -0
  359. package/dist/tools/foldingRanges.d.ts +50 -0
  360. package/dist/tools/foldingRanges.js +51 -0
  361. package/dist/tools/foldingRanges.js.map +1 -0
  362. package/dist/tools/formatAndSave.d.ts +57 -0
  363. package/dist/tools/formatAndSave.js +87 -0
  364. package/dist/tools/formatAndSave.js.map +1 -0
  365. package/dist/tools/formatDocument.d.ts +61 -0
  366. package/dist/tools/formatDocument.js +144 -0
  367. package/dist/tools/formatDocument.js.map +1 -0
  368. package/dist/tools/generateAPIDocumentation.d.ts +62 -0
  369. package/dist/tools/generateAPIDocumentation.js +249 -0
  370. package/dist/tools/generateAPIDocumentation.js.map +1 -0
  371. package/dist/tools/generateTests.d.ts +75 -0
  372. package/dist/tools/generateTests.js +226 -0
  373. package/dist/tools/generateTests.js.map +1 -0
  374. package/dist/tools/getAIComments.d.ts +79 -0
  375. package/dist/tools/getAIComments.js +93 -0
  376. package/dist/tools/getAIComments.js.map +1 -0
  377. package/dist/tools/getAnalyticsReport.d.ts +102 -0
  378. package/dist/tools/getAnalyticsReport.js +137 -0
  379. package/dist/tools/getAnalyticsReport.js.map +1 -0
  380. package/dist/tools/getArchitectureContext.d.ts +85 -0
  381. package/dist/tools/getArchitectureContext.js +135 -0
  382. package/dist/tools/getArchitectureContext.js.map +1 -0
  383. package/dist/tools/getBufferContent.d.ts +80 -0
  384. package/dist/tools/getBufferContent.js +207 -0
  385. package/dist/tools/getBufferContent.js.map +1 -0
  386. package/dist/tools/getChangeImpact.d.ts +76 -0
  387. package/dist/tools/getChangeImpact.js +184 -0
  388. package/dist/tools/getChangeImpact.js.map +1 -0
  389. package/dist/tools/getClaudeTaskStatus.d.ts +87 -0
  390. package/dist/tools/getClaudeTaskStatus.js +89 -0
  391. package/dist/tools/getClaudeTaskStatus.js.map +1 -0
  392. package/dist/tools/getCodeCoverage.d.ts +86 -0
  393. package/dist/tools/getCodeCoverage.js +237 -0
  394. package/dist/tools/getCodeCoverage.js.map +1 -0
  395. package/dist/tools/getCommitsForIssue.d.ts +98 -0
  396. package/dist/tools/getCommitsForIssue.js +106 -0
  397. package/dist/tools/getCommitsForIssue.js.map +1 -0
  398. package/dist/tools/getCurrentSelection.d.ts +123 -0
  399. package/dist/tools/getCurrentSelection.js +113 -0
  400. package/dist/tools/getCurrentSelection.js.map +1 -0
  401. package/dist/tools/getDebugState.d.ts +140 -0
  402. package/dist/tools/getDebugState.js +109 -0
  403. package/dist/tools/getDebugState.js.map +1 -0
  404. package/dist/tools/getDependencyTree.d.ts +59 -0
  405. package/dist/tools/getDependencyTree.js +207 -0
  406. package/dist/tools/getDependencyTree.js.map +1 -0
  407. package/dist/tools/getDiagnostics.d.ts +108 -0
  408. package/dist/tools/getDiagnostics.js +371 -0
  409. package/dist/tools/getDiagnostics.js.map +1 -0
  410. package/dist/tools/getDiffFromHandoff.d.ts +89 -0
  411. package/dist/tools/getDiffFromHandoff.js +163 -0
  412. package/dist/tools/getDiffFromHandoff.js.map +1 -0
  413. package/dist/tools/getDocumentSymbols.d.ts +74 -0
  414. package/dist/tools/getDocumentSymbols.js +177 -0
  415. package/dist/tools/getDocumentSymbols.js.map +1 -0
  416. package/dist/tools/getFileTree.d.ts +66 -0
  417. package/dist/tools/getFileTree.js +131 -0
  418. package/dist/tools/getFileTree.js.map +1 -0
  419. package/dist/tools/getGitDiff.d.ts +50 -0
  420. package/dist/tools/getGitDiff.js +73 -0
  421. package/dist/tools/getGitDiff.js.map +1 -0
  422. package/dist/tools/getGitHotspots.d.ts +88 -0
  423. package/dist/tools/getGitHotspots.js +145 -0
  424. package/dist/tools/getGitHotspots.js.map +1 -0
  425. package/dist/tools/getGitLog.d.ts +62 -0
  426. package/dist/tools/getGitLog.js +87 -0
  427. package/dist/tools/getGitLog.js.map +1 -0
  428. package/dist/tools/getGitStatus.d.ts +72 -0
  429. package/dist/tools/getGitStatus.js +126 -0
  430. package/dist/tools/getGitStatus.js.map +1 -0
  431. package/dist/tools/getImportTree.d.ts +73 -0
  432. package/dist/tools/getImportTree.js +223 -0
  433. package/dist/tools/getImportTree.js.map +1 -0
  434. package/dist/tools/getImportedSignatures.d.ts +62 -0
  435. package/dist/tools/getImportedSignatures.js +255 -0
  436. package/dist/tools/getImportedSignatures.js.map +1 -0
  437. package/dist/tools/getOpenEditors.d.ts +62 -0
  438. package/dist/tools/getOpenEditors.js +126 -0
  439. package/dist/tools/getOpenEditors.js.map +1 -0
  440. package/dist/tools/getPRTemplate.d.ts +68 -0
  441. package/dist/tools/getPRTemplate.js +187 -0
  442. package/dist/tools/getPRTemplate.js.map +1 -0
  443. package/dist/tools/getProjectContext.d.ts +114 -0
  444. package/dist/tools/getProjectContext.js +344 -0
  445. package/dist/tools/getProjectContext.js.map +1 -0
  446. package/dist/tools/getProjectInfo.d.ts +51 -0
  447. package/dist/tools/getProjectInfo.js +325 -0
  448. package/dist/tools/getProjectInfo.js.map +1 -0
  449. package/dist/tools/getSecurityAdvisories.d.ts +105 -0
  450. package/dist/tools/getSecurityAdvisories.js +472 -0
  451. package/dist/tools/getSecurityAdvisories.js.map +1 -0
  452. package/dist/tools/getSessionUsage.d.ts +58 -0
  453. package/dist/tools/getSessionUsage.js +57 -0
  454. package/dist/tools/getSessionUsage.js.map +1 -0
  455. package/dist/tools/getSymbolHistory.d.ts +157 -0
  456. package/dist/tools/getSymbolHistory.js +256 -0
  457. package/dist/tools/getSymbolHistory.js.map +1 -0
  458. package/dist/tools/getToolCapabilities.d.ts +69 -0
  459. package/dist/tools/getToolCapabilities.js +298 -0
  460. package/dist/tools/getToolCapabilities.js.map +1 -0
  461. package/dist/tools/getTypeSignature.d.ts +70 -0
  462. package/dist/tools/getTypeSignature.js +132 -0
  463. package/dist/tools/getTypeSignature.js.map +1 -0
  464. package/dist/tools/getWorkspaceFolders.d.ts +58 -0
  465. package/dist/tools/getWorkspaceFolders.js +69 -0
  466. package/dist/tools/getWorkspaceFolders.js.map +1 -0
  467. package/dist/tools/getWorkspaceSettings.d.ts +44 -0
  468. package/dist/tools/getWorkspaceSettings.js +70 -0
  469. package/dist/tools/getWorkspaceSettings.js.map +1 -0
  470. package/dist/tools/git-utils.d.ts +16 -0
  471. package/dist/tools/git-utils.js +46 -0
  472. package/dist/tools/git-utils.js.map +1 -0
  473. package/dist/tools/gitHistory.d.ts +110 -0
  474. package/dist/tools/gitHistory.js +167 -0
  475. package/dist/tools/gitHistory.js.map +1 -0
  476. package/dist/tools/gitWrite.d.ts +612 -0
  477. package/dist/tools/gitWrite.js +983 -0
  478. package/dist/tools/gitWrite.js.map +1 -0
  479. package/dist/tools/github/actions.d.ts +152 -0
  480. package/dist/tools/github/actions.js +195 -0
  481. package/dist/tools/github/actions.js.map +1 -0
  482. package/dist/tools/github/index.d.ts +3 -0
  483. package/dist/tools/github/index.js +4 -0
  484. package/dist/tools/github/index.js.map +1 -0
  485. package/dist/tools/github/issues.d.ts +281 -0
  486. package/dist/tools/github/issues.js +340 -0
  487. package/dist/tools/github/issues.js.map +1 -0
  488. package/dist/tools/github/pr.d.ts +433 -0
  489. package/dist/tools/github/pr.js +588 -0
  490. package/dist/tools/github/pr.js.map +1 -0
  491. package/dist/tools/github/shared.d.ts +4 -0
  492. package/dist/tools/github/shared.js +12 -0
  493. package/dist/tools/github/shared.js.map +1 -0
  494. package/dist/tools/handoffNote.d.ts +106 -0
  495. package/dist/tools/handoffNote.js +232 -0
  496. package/dist/tools/handoffNote.js.map +1 -0
  497. package/dist/tools/headless/lspClient.d.ts +26 -0
  498. package/dist/tools/headless/lspClient.js +221 -0
  499. package/dist/tools/headless/lspClient.js.map +1 -0
  500. package/dist/tools/headless/lspFallback.d.ts +28 -0
  501. package/dist/tools/headless/lspFallback.js +122 -0
  502. package/dist/tools/headless/lspFallback.js.map +1 -0
  503. package/dist/tools/hoverAtCursor.d.ts +54 -0
  504. package/dist/tools/hoverAtCursor.js +68 -0
  505. package/dist/tools/hoverAtCursor.js.map +1 -0
  506. package/dist/tools/httpClient.d.ts +141 -0
  507. package/dist/tools/httpClient.js +486 -0
  508. package/dist/tools/httpClient.js.map +1 -0
  509. package/dist/tools/index.d.ts +49 -0
  510. package/dist/tools/index.js +672 -0
  511. package/dist/tools/index.js.map +1 -0
  512. package/dist/tools/inlayHints.d.ts +81 -0
  513. package/dist/tools/inlayHints.js +76 -0
  514. package/dist/tools/inlayHints.js.map +1 -0
  515. package/dist/tools/issueRefs.d.ts +14 -0
  516. package/dist/tools/issueRefs.js +27 -0
  517. package/dist/tools/issueRefs.js.map +1 -0
  518. package/dist/tools/jumpToFirstError.d.ts +63 -0
  519. package/dist/tools/jumpToFirstError.js +124 -0
  520. package/dist/tools/jumpToFirstError.js.map +1 -0
  521. package/dist/tools/launchQuickTask.d.ts +76 -0
  522. package/dist/tools/launchQuickTask.js +170 -0
  523. package/dist/tools/launchQuickTask.js.map +1 -0
  524. package/dist/tools/linters/biome.d.ts +2 -0
  525. package/dist/tools/linters/biome.js +44 -0
  526. package/dist/tools/linters/biome.js.map +1 -0
  527. package/dist/tools/linters/cargo.d.ts +2 -0
  528. package/dist/tools/linters/cargo.js +45 -0
  529. package/dist/tools/linters/cargo.js.map +1 -0
  530. package/dist/tools/linters/eslint.d.ts +2 -0
  531. package/dist/tools/linters/eslint.js +59 -0
  532. package/dist/tools/linters/eslint.js.map +1 -0
  533. package/dist/tools/linters/govet.d.ts +2 -0
  534. package/dist/tools/linters/govet.js +37 -0
  535. package/dist/tools/linters/govet.js.map +1 -0
  536. package/dist/tools/linters/pyright.d.ts +2 -0
  537. package/dist/tools/linters/pyright.js +34 -0
  538. package/dist/tools/linters/pyright.js.map +1 -0
  539. package/dist/tools/linters/ruff.d.ts +2 -0
  540. package/dist/tools/linters/ruff.js +30 -0
  541. package/dist/tools/linters/ruff.js.map +1 -0
  542. package/dist/tools/linters/types.d.ts +16 -0
  543. package/dist/tools/linters/types.js +2 -0
  544. package/dist/tools/linters/types.js.map +1 -0
  545. package/dist/tools/linters/typescript.d.ts +2 -0
  546. package/dist/tools/linters/typescript.js +38 -0
  547. package/dist/tools/linters/typescript.js.map +1 -0
  548. package/dist/tools/listClaudeTasks.d.ts +84 -0
  549. package/dist/tools/listClaudeTasks.js +88 -0
  550. package/dist/tools/listClaudeTasks.js.map +1 -0
  551. package/dist/tools/listTerminals.d.ts +55 -0
  552. package/dist/tools/listTerminals.js +78 -0
  553. package/dist/tools/listTerminals.js.map +1 -0
  554. package/dist/tools/lsp.d.ts +1086 -0
  555. package/dist/tools/lsp.js +1339 -0
  556. package/dist/tools/lsp.js.map +1 -0
  557. package/dist/tools/navigateToSymbolByName.d.ts +56 -0
  558. package/dist/tools/navigateToSymbolByName.js +170 -0
  559. package/dist/tools/navigateToSymbolByName.js.map +1 -0
  560. package/dist/tools/openDiff.d.ts +66 -0
  561. package/dist/tools/openDiff.js +126 -0
  562. package/dist/tools/openDiff.js.map +1 -0
  563. package/dist/tools/openFile.d.ts +69 -0
  564. package/dist/tools/openFile.js +129 -0
  565. package/dist/tools/openFile.js.map +1 -0
  566. package/dist/tools/openInBrowser.d.ts +55 -0
  567. package/dist/tools/openInBrowser.js +129 -0
  568. package/dist/tools/openInBrowser.js.map +1 -0
  569. package/dist/tools/organizeImports.d.ts +56 -0
  570. package/dist/tools/organizeImports.js +115 -0
  571. package/dist/tools/organizeImports.js.map +1 -0
  572. package/dist/tools/performanceReport.d.ts +133 -0
  573. package/dist/tools/performanceReport.js +218 -0
  574. package/dist/tools/performanceReport.js.map +1 -0
  575. package/dist/tools/planPersistence.d.ts +306 -0
  576. package/dist/tools/planPersistence.js +485 -0
  577. package/dist/tools/planPersistence.js.map +1 -0
  578. package/dist/tools/previewEdit.d.ts +107 -0
  579. package/dist/tools/previewEdit.js +270 -0
  580. package/dist/tools/previewEdit.js.map +1 -0
  581. package/dist/tools/recentTracesDigest.d.ts +35 -0
  582. package/dist/tools/recentTracesDigest.js +98 -0
  583. package/dist/tools/recentTracesDigest.js.map +1 -0
  584. package/dist/tools/refactorAnalyze.d.ts +78 -0
  585. package/dist/tools/refactorAnalyze.js +141 -0
  586. package/dist/tools/refactorAnalyze.js.map +1 -0
  587. package/dist/tools/refactorExtractFunction.d.ts +52 -0
  588. package/dist/tools/refactorExtractFunction.js +121 -0
  589. package/dist/tools/refactorExtractFunction.js.map +1 -0
  590. package/dist/tools/refactorPreview.d.ts +75 -0
  591. package/dist/tools/refactorPreview.js +93 -0
  592. package/dist/tools/refactorPreview.js.map +1 -0
  593. package/dist/tools/replaceBlock.d.ts +62 -0
  594. package/dist/tools/replaceBlock.js +125 -0
  595. package/dist/tools/replaceBlock.js.map +1 -0
  596. package/dist/tools/resumeClaudeTask.d.ts +75 -0
  597. package/dist/tools/resumeClaudeTask.js +149 -0
  598. package/dist/tools/resumeClaudeTask.js.map +1 -0
  599. package/dist/tools/runClaudeTask.d.ts +97 -0
  600. package/dist/tools/runClaudeTask.js +224 -0
  601. package/dist/tools/runClaudeTask.js.map +1 -0
  602. package/dist/tools/runCommand.d.ts +82 -0
  603. package/dist/tools/runCommand.js +101 -0
  604. package/dist/tools/runCommand.js.map +1 -0
  605. package/dist/tools/runTests.d.ts +146 -0
  606. package/dist/tools/runTests.js +315 -0
  607. package/dist/tools/runTests.js.map +1 -0
  608. package/dist/tools/saveDocument.d.ts +50 -0
  609. package/dist/tools/saveDocument.js +73 -0
  610. package/dist/tools/saveDocument.js.map +1 -0
  611. package/dist/tools/screenshot.d.ts +23 -0
  612. package/dist/tools/screenshot.js +43 -0
  613. package/dist/tools/screenshot.js.map +1 -0
  614. package/dist/tools/screenshotAndAnnotate.d.ts +103 -0
  615. package/dist/tools/screenshotAndAnnotate.js +192 -0
  616. package/dist/tools/screenshotAndAnnotate.js.map +1 -0
  617. package/dist/tools/searchAndReplace.d.ts +108 -0
  618. package/dist/tools/searchAndReplace.js +281 -0
  619. package/dist/tools/searchAndReplace.js.map +1 -0
  620. package/dist/tools/searchTools.d.ts +61 -0
  621. package/dist/tools/searchTools.js +85 -0
  622. package/dist/tools/searchTools.js.map +1 -0
  623. package/dist/tools/searchWorkspace.d.ts +99 -0
  624. package/dist/tools/searchWorkspace.js +189 -0
  625. package/dist/tools/searchWorkspace.js.map +1 -0
  626. package/dist/tools/selectionRanges.d.ts +58 -0
  627. package/dist/tools/selectionRanges.js +61 -0
  628. package/dist/tools/selectionRanges.js.map +1 -0
  629. package/dist/tools/semanticTokens.d.ts +87 -0
  630. package/dist/tools/semanticTokens.js +86 -0
  631. package/dist/tools/semanticTokens.js.map +1 -0
  632. package/dist/tools/setActiveWorkspaceFolder.d.ts +41 -0
  633. package/dist/tools/setActiveWorkspaceFolder.js +38 -0
  634. package/dist/tools/setActiveWorkspaceFolder.js.map +1 -0
  635. package/dist/tools/signatureHelp.d.ts +86 -0
  636. package/dist/tools/signatureHelp.js +79 -0
  637. package/dist/tools/signatureHelp.js.map +1 -0
  638. package/dist/tools/spawnWorkspace.d.ts +103 -0
  639. package/dist/tools/spawnWorkspace.js +268 -0
  640. package/dist/tools/spawnWorkspace.js.map +1 -0
  641. package/dist/tools/stackTraceParser.d.ts +43 -0
  642. package/dist/tools/stackTraceParser.js +139 -0
  643. package/dist/tools/stackTraceParser.js.map +1 -0
  644. package/dist/tools/terminal.d.ts +352 -0
  645. package/dist/tools/terminal.js +670 -0
  646. package/dist/tools/terminal.js.map +1 -0
  647. package/dist/tools/testRunners/cargoTest.d.ts +2 -0
  648. package/dist/tools/testRunners/cargoTest.js +129 -0
  649. package/dist/tools/testRunners/cargoTest.js.map +1 -0
  650. package/dist/tools/testRunners/goTest.d.ts +2 -0
  651. package/dist/tools/testRunners/goTest.js +108 -0
  652. package/dist/tools/testRunners/goTest.js.map +1 -0
  653. package/dist/tools/testRunners/pytest.d.ts +2 -0
  654. package/dist/tools/testRunners/pytest.js +135 -0
  655. package/dist/tools/testRunners/pytest.js.map +1 -0
  656. package/dist/tools/testRunners/types.d.ts +18 -0
  657. package/dist/tools/testRunners/types.js +2 -0
  658. package/dist/tools/testRunners/types.js.map +1 -0
  659. package/dist/tools/testRunners/vitestJest.d.ts +3 -0
  660. package/dist/tools/testRunners/vitestJest.js +215 -0
  661. package/dist/tools/testRunners/vitestJest.js.map +1 -0
  662. package/dist/tools/testTraceToSource.d.ts +80 -0
  663. package/dist/tools/testTraceToSource.js +206 -0
  664. package/dist/tools/testTraceToSource.js.map +1 -0
  665. package/dist/tools/transaction.d.ts +243 -0
  666. package/dist/tools/transaction.js +309 -0
  667. package/dist/tools/transaction.js.map +1 -0
  668. package/dist/tools/typeHierarchy.d.ts +77 -0
  669. package/dist/tools/typeHierarchy.js +86 -0
  670. package/dist/tools/typeHierarchy.js.map +1 -0
  671. package/dist/tools/utils.d.ts +124 -0
  672. package/dist/tools/utils.js +566 -0
  673. package/dist/tools/utils.js.map +1 -0
  674. package/dist/tools/vscodeCommands.d.ts +90 -0
  675. package/dist/tools/vscodeCommands.js +112 -0
  676. package/dist/tools/vscodeCommands.js.map +1 -0
  677. package/dist/tools/vscodeTasks.d.ts +102 -0
  678. package/dist/tools/vscodeTasks.js +110 -0
  679. package/dist/tools/vscodeTasks.js.map +1 -0
  680. package/dist/tools/watchDiagnostics.d.ts +64 -0
  681. package/dist/tools/watchDiagnostics.js +270 -0
  682. package/dist/tools/watchDiagnostics.js.map +1 -0
  683. package/dist/tools/workspaceSettings.d.ts +57 -0
  684. package/dist/tools/workspaceSettings.js +80 -0
  685. package/dist/tools/workspaceSettings.js.map +1 -0
  686. package/dist/transport.d.ts +207 -0
  687. package/dist/transport.js +1272 -0
  688. package/dist/transport.js.map +1 -0
  689. package/dist/version.d.ts +13 -0
  690. package/dist/version.js +31 -0
  691. package/dist/version.js.map +1 -0
  692. package/dist/wsUtils.d.ts +8 -0
  693. package/dist/wsUtils.js +54 -0
  694. package/dist/wsUtils.js.map +1 -0
  695. package/package.json +118 -0
  696. package/scripts/gen-claude-desktop-config.sh +124 -0
  697. package/scripts/gen-mcp-config.sh +390 -0
  698. package/scripts/install-extension.sh +106 -0
  699. package/scripts/mcp-stdio-shim.cjs +482 -0
  700. package/scripts/postinstall.mjs +68 -0
  701. package/scripts/start-all.sh +502 -0
  702. package/scripts/start-orchestrator.sh +186 -0
  703. package/scripts/start-remote.sh +126 -0
  704. package/scripts/start-vps.sh +116 -0
  705. package/templates/CLAUDE.bridge.md +125 -0
  706. package/templates/automation-policies/security-first.json +46 -0
  707. package/templates/automation-policies/strict-lint.json +41 -0
  708. package/templates/automation-policies/test-driven.json +54 -0
  709. package/templates/automation-policy.example.json +105 -0
  710. package/templates/bridge-tools.md +111 -0
  711. package/templates/dispatch-context.md +33 -0
  712. package/templates/managed-agent/code-review-agent.md +50 -0
  713. package/templates/managed-agent/managed-agent-mcp.json +102 -0
  714. package/templates/recipes/ambient-journal.yaml +11 -0
  715. package/templates/recipes/daily-status.yaml +21 -0
  716. package/templates/recipes/lint-on-save.yaml +13 -0
  717. package/templates/recipes/stale-branches.yaml +18 -0
  718. package/templates/recipes/watch-failing-tests.yaml +15 -0
  719. package/templates/scheduled-tasks/dependency-audit/SKILL.md +77 -0
  720. package/templates/scheduled-tasks/health-check/SKILL.md +73 -0
  721. package/templates/scheduled-tasks/nightly-review/SKILL.md +69 -0
@@ -0,0 +1,1272 @@
1
+ import { Ajv } from "ajv";
2
+ import { WebSocket } from "ws";
3
+ import { ErrorCodes } from "./errors.js";
4
+ import { consumeToken, refillBucket } from "./fp/tokenBucket.js";
5
+ import { withSpan } from "./telemetry.js";
6
+ import { BRIDGE_PROTOCOL_VERSION, PACKAGE_VERSION } from "./version.js";
7
+ import { safeSend } from "./wsUtils.js";
8
+ const TOOL_TIMEOUT_MS = 60_000; // 60s — prevents tools from blocking indefinitely
9
+ const MAX_CONCURRENT_TOOLS = 10;
10
+ const RATE_LIMIT_WINDOW_MS = 60_000; // 1 minute window
11
+ const RATE_LIMIT_MAX = 200; // max requests per window
12
+ const NOTIFICATION_RATE_LIMIT = 500; // max notifications per minute (separate from request limit)
13
+ const TOOLS_LIST_PAGE_SIZE = 200; // Most MCP clients (Claude Desktop) only fetch page 1 — keep all tools visible
14
+ // When a tool result's total text content exceeds this threshold, inject
15
+ // _meta["anthropic/maxResultSizeChars"] into the result so Claude Code 2.1.91+
16
+ // persists the full result instead of truncating at its own internal limit.
17
+ const META_SIZE_HINT_THRESHOLD = 50_000; // 50 KB
18
+ // Supported MCP protocol versions, newest first.
19
+ // Extend this array when new protocol versions are ratified; keep oldest supported version last.
20
+ const SUPPORTED_VERSIONS = ["2025-11-25"];
21
+ export class McpTransport {
22
+ logger;
23
+ tools = new Map();
24
+ /** Optional fallback for tool names not found in the static registry. Used by the orchestrator. */
25
+ dynamicToolDispatch = null;
26
+ /** Optional instructions string injected into the MCP initialize response. */
27
+ instructions = null;
28
+ setDynamicToolDispatch(fn) {
29
+ this.dynamicToolDispatch = fn;
30
+ }
31
+ setInstructions(text) {
32
+ this.instructions = text;
33
+ }
34
+ serverInfo = {
35
+ name: "claude-ide-bridge",
36
+ version: BRIDGE_PROTOCOL_VERSION,
37
+ _meta: { packageVersion: PACKAGE_VERSION },
38
+ };
39
+ activeWs = null;
40
+ workspace = "";
41
+ activeListener = null;
42
+ inFlightControllers = new Map();
43
+ inFlightToolNames = new Map();
44
+ /** Pending elicitation/create requests waiting for a client response. */
45
+ pendingElicitations = new Map();
46
+ initialized = false;
47
+ /** Called once after the MCP handshake completes (notifications/initialized received). */
48
+ onInitialized;
49
+ activeToolCalls = 0;
50
+ callCount = 0;
51
+ errorCount = 0;
52
+ generation = 0; // incremented on each attach; stale handlers check this
53
+ sessionStartedAt = Date.now();
54
+ resultSizeTracker = new Map();
55
+ // Ring buffer for O(1) sliding-window rate limiting — avoids array scan + splice
56
+ rateLimitBuf = new Float64Array(RATE_LIMIT_MAX); // initialised to 0 (epoch 1970, always outside window)
57
+ rateLimitHead = 0; // index of oldest entry / next write position
58
+ // Separate lightweight rate limit for notifications (no response sent, so can't use main ring buffer)
59
+ notifCount = 0;
60
+ notifWindowStart = 0;
61
+ sessionId = null;
62
+ /** X-Claude-Code-Session-Id from the HTTP initialize request, for proxy-level correlation. */
63
+ claudeCodeSessionId = null;
64
+ /** OAuth scope for this session. null = full access (static bridge token). "mcp:read" = read-only. */
65
+ sessionScope = null;
66
+ /** Per-session tool deny list. Tools in this set return isError:true at dispatch. */
67
+ denyTools = new Set();
68
+ /** Called on each progress notification — HTTP sessions use this to refresh lastActivity. */
69
+ onActivity = undefined;
70
+ activityLog = null;
71
+ isExtensionConnectedFn = null;
72
+ ajv = new Ajv({ strict: false, allErrors: true });
73
+ schemaValidators = new Map();
74
+ outputValidators = new Map();
75
+ /** Cached wire-schema array for tools/list. Invalidated on any tool registration change. */
76
+ wireSchemaCache = null;
77
+ /** Per-session tool-call rate limit (calls/minute). 0 = disabled. */
78
+ toolRateLimit = 60;
79
+ /**
80
+ * Token bucket for tool-call rate limiting. Stored as a mutable object so it
81
+ * can be shared across multiple transport instances (e.g. HTTP session cycling
82
+ * bypass prevention — see StreamableHttpHandler.sharedHttpRateLimitBucket).
83
+ */
84
+ toolBucket = {
85
+ tokens: 60,
86
+ lastRefill: Date.now(),
87
+ };
88
+ /**
89
+ * Optional approval gate invoked before executing a tool. Returns the
90
+ * decision or null to bypass gating for this call. Wired in by bridge.ts
91
+ * when the dashboard approval flow is enabled.
92
+ */
93
+ approvalGate = null;
94
+ setApprovalGate(fn) {
95
+ this.approvalGate = fn;
96
+ }
97
+ constructor(logger) {
98
+ this.logger = logger;
99
+ }
100
+ /**
101
+ * Set the OAuth scope for this session.
102
+ * - null / "mcp" → full access (all tools allowed)
103
+ * - "mcp:read" → read-only access (only readOnlyHint:true tools allowed)
104
+ */
105
+ setSessionScope(scope) {
106
+ this.sessionScope = scope;
107
+ }
108
+ /**
109
+ * Set a per-session tool deny list. Tools whose name is in this set will
110
+ * return isError:true at dispatch time (they still appear in tools/list).
111
+ * Called at HTTP session initialize time from X-Bridge-Deny-Tools header.
112
+ */
113
+ setDenyTools(tools) {
114
+ this.denyTools = tools;
115
+ }
116
+ /** Configure per-session tool call rate limiting (calls/minute, 0 = disabled). */
117
+ setToolRateLimit(limit) {
118
+ this.toolRateLimit = limit;
119
+ this.toolBucket.tokens = limit;
120
+ this.toolBucket.lastRefill = Date.now();
121
+ }
122
+ /**
123
+ * Replace the token bucket with a shared object.
124
+ * All transports using the same bucket share one rate-limit pool — prevents
125
+ * bypassing the limit by cycling HTTP sessions (each new session would otherwise
126
+ * start with a full bucket).
127
+ */
128
+ setSharedToolRateLimitBucket(bucket) {
129
+ this.toolBucket = bucket;
130
+ }
131
+ /** Refill the token bucket and return whether at least one token is available (does NOT consume). */
132
+ peekToolRateLimit() {
133
+ if (this.toolRateLimit <= 0)
134
+ return true;
135
+ const next = refillBucket(this.toolBucket, Date.now(), this.toolRateLimit);
136
+ this.toolBucket.tokens = next.tokens;
137
+ this.toolBucket.lastRefill = next.lastRefill;
138
+ return this.toolBucket.tokens >= 1;
139
+ }
140
+ /** Consume one token. Call only after peekToolRateLimit() returned true. */
141
+ consumeToolRateLimitToken() {
142
+ if (this.toolRateLimit <= 0)
143
+ return;
144
+ const { nextState } = consumeToken(this.toolBucket);
145
+ this.toolBucket.tokens = nextState.tokens;
146
+ this.toolBucket.lastRefill = nextState.lastRefill;
147
+ }
148
+ getValidator(toolName) {
149
+ if (this.schemaValidators.has(toolName)) {
150
+ // biome-ignore lint/style/noNonNullAssertion: has() guard above proves this is defined
151
+ return this.schemaValidators.get(toolName);
152
+ }
153
+ const tool = this.tools.get(toolName);
154
+ if (!tool)
155
+ return null;
156
+ const schema = tool.schema.inputSchema;
157
+ if (typeof schema !== "object" || schema === null)
158
+ return null;
159
+ const fn = this.ajv.compile(schema);
160
+ this.schemaValidators.set(toolName, fn);
161
+ return fn;
162
+ }
163
+ /** Returns true once the MCP handshake is complete (notifications/initialized received). */
164
+ get isReady() {
165
+ return this.initialized;
166
+ }
167
+ /**
168
+ * Invoke a registered tool by name, bypassing the JSON-RPC dispatch path.
169
+ * Used by HTTP endpoints (e.g. /launch-quick-task) that need to call tools
170
+ * in-process without a full MCP session. No AJV validation — callers must
171
+ * pre-validate args. Returns null if the tool is not registered.
172
+ */
173
+ invokeToolDirect(toolName, args) {
174
+ const tool = this.tools.get(toolName);
175
+ if (!tool)
176
+ return null;
177
+ return Promise.resolve(tool.handler(args));
178
+ }
179
+ /**
180
+ * Mark the transport as initialized without requiring the MCP handshake.
181
+ * Use this when the caller has already authenticated the client at a higher
182
+ * level (e.g. the orchestrator validates auth at WebSocket upgrade time).
183
+ */
184
+ markInitialized() {
185
+ this.initialized = true;
186
+ }
187
+ /** Number of tools currently registered (useful for /ready endpoint). */
188
+ get toolCount() {
189
+ return this.tools.size;
190
+ }
191
+ /**
192
+ * Return a static snapshot of all registered tool schemas.
193
+ * Used by scripts/audit-schema-changes.mjs to diff against a committed baseline.
194
+ */
195
+ getSchemaSnapshot() {
196
+ return Array.from(this.tools.values()).map((t) => ({
197
+ name: t.schema.name,
198
+ inputSchema: t.schema.inputSchema,
199
+ ...(t.schema.outputSchema !== undefined && {
200
+ outputSchema: t.schema.outputSchema,
201
+ }),
202
+ }));
203
+ }
204
+ setExtensionConnectedFn(fn) {
205
+ this.isExtensionConnectedFn = fn;
206
+ }
207
+ setActivityLog(log) {
208
+ this.activityLog = log;
209
+ }
210
+ /**
211
+ * Send an `elicitation/create` request to the MCP client (Claude Code 2.1.76+) and
212
+ * wait for the user's response. Resolves with the client's result object, or rejects
213
+ * if the client declines, disconnects, or does not support elicitation.
214
+ *
215
+ * @param message Human-readable question shown to the user.
216
+ * @param requestedSchema JSON Schema describing the shape of the expected response.
217
+ * @param timeoutMs Maximum time to wait for a response (default: 5 minutes).
218
+ */
219
+ async elicit(message, requestedSchema, timeoutMs = 300_000) {
220
+ if (!this.activeWs || this.activeWs.readyState !== WebSocket.OPEN) {
221
+ throw new Error("No active MCP client connected");
222
+ }
223
+ if (!this.initialized) {
224
+ throw new Error("MCP client not yet initialized");
225
+ }
226
+ const ws = this.activeWs;
227
+ const id = `elicit-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`;
228
+ const request = {
229
+ jsonrpc: "2.0",
230
+ id,
231
+ method: "elicitation/create",
232
+ params: { message, requestedSchema },
233
+ };
234
+ return new Promise((resolve, reject) => {
235
+ const timer = setTimeout(() => {
236
+ if (this.pendingElicitations.delete(id)) {
237
+ reject(new Error(`Elicitation timed out after ${timeoutMs}ms`));
238
+ }
239
+ }, timeoutMs);
240
+ this.pendingElicitations.set(id, {
241
+ resolve: (result) => {
242
+ clearTimeout(timer);
243
+ resolve(result);
244
+ },
245
+ reject: (err) => {
246
+ clearTimeout(timer);
247
+ reject(err);
248
+ },
249
+ requestedSchema,
250
+ });
251
+ safeSend(ws, JSON.stringify(request), this.logger).then((sent) => {
252
+ // Guard: only reject if the entry hasn't already been resolved (e.g. fast loopback
253
+ // where a response arrives before safeSend's .then() fires under backpressure).
254
+ if (!sent && this.pendingElicitations.has(id)) {
255
+ this.pendingElicitations.delete(id);
256
+ clearTimeout(timer);
257
+ reject(new Error("Failed to send elicitation/create — socket closed"));
258
+ }
259
+ });
260
+ });
261
+ }
262
+ registerTool(schema, handler, timeoutMs) {
263
+ if (!/^[a-zA-Z0-9_]+$/.test(schema.name)) {
264
+ throw new Error(`Invalid tool name "${schema.name}": must contain only letters, digits, and underscores`);
265
+ }
266
+ if (this.tools.has(schema.name)) {
267
+ throw new Error(`Duplicate tool name "${schema.name}": a tool with this name is already registered`);
268
+ }
269
+ this.tools.set(schema.name, {
270
+ schema,
271
+ handler,
272
+ timeoutMs: timeoutMs ?? schema.timeoutMs,
273
+ });
274
+ }
275
+ /** Upsert a tool by name — replaces if already registered, inserts if new. */
276
+ replaceTool(schema, handler, timeoutMs) {
277
+ if (!/^[a-zA-Z0-9_]+$/.test(schema.name)) {
278
+ throw new Error(`Invalid tool name "${schema.name}": must contain only letters, digits, and underscores`);
279
+ }
280
+ // Clear cached AJV validators and wire schema so the new schema takes effect
281
+ this.schemaValidators.delete(schema.name);
282
+ this.outputValidators.delete(schema.name);
283
+ this.wireSchemaCache = null;
284
+ this.tools.set(schema.name, {
285
+ schema,
286
+ handler,
287
+ timeoutMs: timeoutMs ?? schema.timeoutMs,
288
+ });
289
+ }
290
+ /** Remove all tools whose name starts with `prefix`. Returns count removed. */
291
+ deregisterTool(name) {
292
+ this.schemaValidators.delete(name);
293
+ this.outputValidators.delete(name);
294
+ this.wireSchemaCache = null;
295
+ return this.tools.delete(name);
296
+ }
297
+ deregisterToolsByPrefix(prefix) {
298
+ if (!prefix)
299
+ return 0;
300
+ let count = 0;
301
+ for (const name of [...this.tools.keys()]) {
302
+ if (name.startsWith(prefix)) {
303
+ this.tools.delete(name);
304
+ this.schemaValidators.delete(name);
305
+ this.outputValidators.delete(name);
306
+ count++;
307
+ }
308
+ }
309
+ if (count > 0)
310
+ this.wireSchemaCache = null;
311
+ return count;
312
+ }
313
+ detach() {
314
+ // Remove listener from old WebSocket to prevent accumulation
315
+ if (this.activeWs && this.activeListener) {
316
+ this.activeWs.removeListener("message", this.activeListener);
317
+ this.activeListener = null;
318
+ }
319
+ // Abort all in-flight tool calls to prevent resource leaks
320
+ for (const [, controller] of this.inFlightControllers) {
321
+ controller.abort();
322
+ }
323
+ this.inFlightControllers.clear();
324
+ this.inFlightToolNames.clear();
325
+ // Reject all pending elicitation requests so callers don't hang after disconnect
326
+ for (const [, pending] of this.pendingElicitations) {
327
+ pending.reject(new Error("Client disconnected before responding to elicitation"));
328
+ }
329
+ this.pendingElicitations.clear();
330
+ this.activeWs = null;
331
+ this.initialized = false;
332
+ this.activeToolCalls = 0;
333
+ // Do NOT reset rateLimitBuf/rateLimitHead here — resetting on reconnect would allow
334
+ // a client to bypass the rate limit by rapidly cycling connections (200 req / 50ms reconnect).
335
+ // The sliding window continues across reconnects from the same session.
336
+ // notifCount IS reset because it is a per-connection counter (counts outbound notifications
337
+ // since the current client connected), not a cross-session security gate.
338
+ this.notifCount = 0;
339
+ this.notifWindowStart = 0;
340
+ }
341
+ getStats() {
342
+ return {
343
+ callCount: this.callCount,
344
+ errorCount: this.errorCount,
345
+ activeToolCalls: this.activeToolCalls,
346
+ inFlightTools: [...this.inFlightToolNames.values()],
347
+ startedAt: this.sessionStartedAt,
348
+ };
349
+ }
350
+ /** Returns the byte length of the wire-schema cache, or null if not yet built. */
351
+ getWireSchemaCacheSize() {
352
+ if (this.wireSchemaCache === null)
353
+ return null;
354
+ return JSON.stringify(this.wireSchemaCache).length;
355
+ }
356
+ /** Bulk-apply category tags to registered tools. Called after registerAllTools(). */
357
+ applyToolCategories(map) {
358
+ for (const [name, cats] of Object.entries(map)) {
359
+ const entry = this.tools.get(name);
360
+ if (entry) {
361
+ entry.schema.categories = cats;
362
+ }
363
+ }
364
+ // Invalidate wire cache — categories are stripped but getToolSchemas reads them
365
+ this.wireSchemaCache = null;
366
+ }
367
+ /** Returns all registered tool schemas — used by searchTools for keyword/category discovery. */
368
+ getToolSchemas() {
369
+ return Array.from(this.tools.values()).map((t) => ({
370
+ name: t.schema.name,
371
+ description: t.schema.description,
372
+ categories: t.schema.categories,
373
+ }));
374
+ }
375
+ /** Top-N tools by largest result seen this session (descending by sizeChars). */
376
+ getTopResultSizes(n = 10) {
377
+ return [...this.resultSizeTracker.entries()]
378
+ .sort((a, b) => b[1] - a[1])
379
+ .slice(0, n)
380
+ .map(([tool, sizeChars]) => ({ tool, sizeChars }));
381
+ }
382
+ attach(ws) {
383
+ this.activeWs = ws;
384
+ this.initialized = false; // Force re-initialization on every new connection
385
+ const gen = ++this.generation;
386
+ const listener = async (data) => {
387
+ // Ignore messages from superseded connections
388
+ if (gen !== this.generation)
389
+ return;
390
+ let raw;
391
+ try {
392
+ raw = JSON.parse(data.toString("utf-8"));
393
+ }
394
+ catch {
395
+ // Malformed JSON — send PARSE_ERROR per JSON-RPC 2.0 spec §5.1
396
+ await safeSend(ws, JSON.stringify({
397
+ jsonrpc: "2.0",
398
+ id: null,
399
+ error: {
400
+ code: ErrorCodes.PARSE_ERROR,
401
+ message: "Parse error: message is not valid JSON",
402
+ },
403
+ }), this.logger);
404
+ return;
405
+ }
406
+ try {
407
+ if (typeof raw !== "object" || raw === null) {
408
+ this.logger.debug("Ignoring non-object JSON-RPC message");
409
+ return;
410
+ }
411
+ if (Array.isArray(raw)) {
412
+ await safeSend(ws, JSON.stringify([
413
+ {
414
+ jsonrpc: "2.0",
415
+ id: null,
416
+ error: {
417
+ code: ErrorCodes.INVALID_REQUEST,
418
+ message: "Batch requests are not supported",
419
+ },
420
+ },
421
+ ]), this.logger);
422
+ return;
423
+ }
424
+ const msg = raw;
425
+ // Sanitize method name before logging to prevent log injection.
426
+ // A malicious client could embed newlines or ANSI codes in the method field.
427
+ const safeMethod = (s) => typeof s === "string"
428
+ ? s.replace(/[\x00-\x1f\x7f]/g, "").slice(0, 128)
429
+ : String(s);
430
+ // Detect client-to-server responses (elicitation/create results, etc.)
431
+ // JSON-RPC responses have no "method" but have an "id" and "result" or "error".
432
+ if (!msg.method &&
433
+ msg.id !== undefined &&
434
+ msg.id !== null &&
435
+ ("result" in msg || "error" in msg)) {
436
+ const pending = this.pendingElicitations.get(msg.id);
437
+ if (pending) {
438
+ this.pendingElicitations.delete(msg.id);
439
+ const resp = raw;
440
+ if (resp.error) {
441
+ pending.reject(new Error(resp.error.message ?? "Elicitation declined"));
442
+ }
443
+ else {
444
+ // Validate that the result conforms to the schema type before resolving.
445
+ // Full AJV validation is deferred to callers; here we guard against
446
+ // obviously malformed payloads regardless of whether the schema has a
447
+ // top-level `type` field — null is never a valid elicitation result.
448
+ const result = resp.result;
449
+ const schemaType = pending.requestedSchema.type;
450
+ const jsType = result === null
451
+ ? "null"
452
+ : Array.isArray(result)
453
+ ? "array"
454
+ : typeof result;
455
+ let typeError = null;
456
+ if (result === null || result === undefined) {
457
+ typeError = "Elicitation result must not be null/undefined";
458
+ }
459
+ else if (schemaType === "object" &&
460
+ (typeof result !== "object" || Array.isArray(result))) {
461
+ typeError = `Elicitation result type mismatch: expected object, got ${jsType}`;
462
+ }
463
+ else if (schemaType === "string" &&
464
+ typeof result !== "string") {
465
+ typeError = `Elicitation result type mismatch: expected string, got ${jsType}`;
466
+ }
467
+ else if (schemaType === "number" &&
468
+ typeof result !== "number") {
469
+ typeError = `Elicitation result type mismatch: expected number, got ${jsType}`;
470
+ }
471
+ else if (schemaType === "boolean" &&
472
+ typeof result !== "boolean") {
473
+ typeError = `Elicitation result type mismatch: expected boolean, got ${jsType}`;
474
+ }
475
+ else if (schemaType === "array" && !Array.isArray(result)) {
476
+ typeError = `Elicitation result type mismatch: expected array, got ${jsType}`;
477
+ }
478
+ if (typeError) {
479
+ pending.reject(new Error(typeError));
480
+ }
481
+ else {
482
+ // Defense-in-depth: reject prototype-poisoning keys from object results
483
+ if (schemaType === "object" &&
484
+ result !== null &&
485
+ typeof result === "object") {
486
+ const dangerous = ["__proto__", "constructor", "prototype"];
487
+ if (dangerous.some((k) => Object.hasOwn(result, k))) {
488
+ pending.reject(new Error("Elicitation result contains disallowed keys (__proto__, constructor, prototype)"));
489
+ return; // don't fall through to pending.resolve
490
+ }
491
+ }
492
+ pending.resolve(result);
493
+ }
494
+ }
495
+ }
496
+ else {
497
+ this.logger.debug(`Received unexpected response for id=${msg.id} — ignored`);
498
+ }
499
+ return;
500
+ }
501
+ this.logger.debug(`<-- ${safeMethod(msg.method)} (id=${msg.id})`);
502
+ if (!msg.method)
503
+ return;
504
+ // Notifications (no id)
505
+ if (msg.id === undefined || msg.id === null) {
506
+ // Lightweight rate limit for notifications — they have no response channel
507
+ // so they bypass the main ring-buffer rate limiter.
508
+ const nowNotif = Date.now();
509
+ if (nowNotif - this.notifWindowStart >= RATE_LIMIT_WINDOW_MS) {
510
+ this.notifCount = 0;
511
+ this.notifWindowStart = nowNotif;
512
+ }
513
+ this.notifCount++;
514
+ if (this.notifCount >= NOTIFICATION_RATE_LIMIT) {
515
+ this.logger.warn(`Notification rate limit exceeded (${NOTIFICATION_RATE_LIMIT}/min) — dropping notification`);
516
+ return;
517
+ }
518
+ if (msg.method === "notifications/cancelled") {
519
+ const requestId = msg.params
520
+ ?.requestId;
521
+ if (requestId !== undefined) {
522
+ const controller = this.inFlightControllers.get(requestId);
523
+ if (controller)
524
+ controller.abort();
525
+ }
526
+ }
527
+ else if (msg.method === "notifications/initialized") {
528
+ this.initialized = true;
529
+ this.logger.debug("Client initialized");
530
+ this.onInitialized?.();
531
+ }
532
+ return;
533
+ }
534
+ // Rate limiting — O(1) ring buffer sliding window.
535
+ // rateLimitBuf holds the last RATE_LIMIT_MAX timestamps in insertion order.
536
+ // rateLimitHead points to the oldest entry (next write position).
537
+ // If the oldest timestamp is still within the window, all 200 slots are
538
+ // occupied by recent requests → limit exceeded.
539
+ const now = Date.now();
540
+ // biome-ignore lint/style/noNonNullAssertion: ring buffer is pre-filled with 0s, index always valid
541
+ const oldest = this.rateLimitBuf[this.rateLimitHead];
542
+ if (oldest >= now - RATE_LIMIT_WINDOW_MS) {
543
+ this.logger.warn(`Rate limit exceeded: ${RATE_LIMIT_MAX} requests in ${RATE_LIMIT_WINDOW_MS}ms`);
544
+ this.activityLog?.recordRateLimitRejection();
545
+ await safeSend(ws, JSON.stringify({
546
+ jsonrpc: "2.0",
547
+ id: msg.id,
548
+ error: {
549
+ code: ErrorCodes.RATE_LIMIT_EXCEEDED,
550
+ message: `Rate limit exceeded — max ${RATE_LIMIT_MAX} requests per ${RATE_LIMIT_WINDOW_MS / 1000}s window`,
551
+ },
552
+ }), this.logger);
553
+ return;
554
+ }
555
+ // Record this timestamp and advance the head pointer
556
+ this.rateLimitBuf[this.rateLimitHead] = now;
557
+ this.rateLimitHead = (this.rateLimitHead + 1) % RATE_LIMIT_MAX;
558
+ let response = {
559
+ jsonrpc: "2.0",
560
+ // biome-ignore lint/style/noNonNullAssertion: only reachable for requests (not notifications), which always have an id
561
+ id: msg.id,
562
+ error: { code: -32603, message: "Internal error" },
563
+ };
564
+ switch (msg.method) {
565
+ case "initialize": {
566
+ const clientParams = msg.params;
567
+ const clientVersion = clientParams?.protocolVersion;
568
+ const negotiatedVersion = clientVersion && SUPPORTED_VERSIONS.includes(clientVersion)
569
+ ? clientVersion
570
+ : // biome-ignore lint/style/noNonNullAssertion: SUPPORTED_VERSIONS is a non-empty constant array
571
+ SUPPORTED_VERSIONS[0];
572
+ if (clientVersion && !SUPPORTED_VERSIONS.includes(clientVersion)) {
573
+ this.logger.warn(`Client requested unsupported protocol version ${clientVersion}, responding with ${negotiatedVersion}`);
574
+ }
575
+ this.initialized = false; // Reset — waiting for notifications/initialized
576
+ response = {
577
+ jsonrpc: "2.0",
578
+ id: msg.id,
579
+ result: {
580
+ protocolVersion: negotiatedVersion,
581
+ capabilities: {
582
+ tools: { listChanged: true },
583
+ resources: { listChanged: false },
584
+ prompts: { listChanged: false },
585
+ logging: {},
586
+ // Advertise elicitation support (MCP 2025-11-25 / Claude Code 2.1.76+).
587
+ // Enables the bridge to send elicitation/create requests to the client
588
+ // so tools can ask the user for input mid-task.
589
+ elicitation: {},
590
+ },
591
+ serverInfo: this.serverInfo,
592
+ ...(this.instructions !== null && {
593
+ instructions: this.instructions,
594
+ }),
595
+ },
596
+ };
597
+ break;
598
+ }
599
+ case "tools/list": {
600
+ if (!this.initialized) {
601
+ response = {
602
+ jsonrpc: "2.0",
603
+ id: msg.id,
604
+ error: {
605
+ code: ErrorCodes.INVALID_REQUEST,
606
+ message: "Not initialized — send initialize first",
607
+ },
608
+ };
609
+ break;
610
+ }
611
+ if (!this.wireSchemaCache) {
612
+ this.wireSchemaCache = Array.from(this.tools.values()).map((t) => {
613
+ // Strip internal-only fields before sending on the wire.
614
+ // cache_control is intentionally NOT stripped — it passes through to clients.
615
+ const { extensionRequired: _ext, timeoutMs: _timeout, categories: _cat, outputSchema: _outputSchema, ...wireSchema } = t.schema;
616
+ return wireSchema;
617
+ });
618
+ }
619
+ const allTools = this.wireSchemaCache;
620
+ // Parse cursor (opaque base64-encoded decimal offset)
621
+ const listParams = msg.params;
622
+ let offset = 0;
623
+ if (typeof listParams?.cursor === "string") {
624
+ try {
625
+ const decoded = Number.parseInt(Buffer.from(listParams.cursor, "base64").toString("utf-8"), 10);
626
+ if (Number.isFinite(decoded) &&
627
+ decoded >= 0 &&
628
+ decoded < 1_000_000 // sanity cap: no realistic registry exceeds 1M tools
629
+ )
630
+ offset = decoded;
631
+ }
632
+ catch {
633
+ // malformed cursor — start from beginning
634
+ }
635
+ }
636
+ const page = allTools.slice(offset, offset + TOOLS_LIST_PAGE_SIZE);
637
+ const nextOffset = offset + TOOLS_LIST_PAGE_SIZE;
638
+ const hasMore = nextOffset < allTools.length;
639
+ const nextCursor = hasMore
640
+ ? Buffer.from(String(nextOffset)).toString("base64")
641
+ : undefined;
642
+ response = {
643
+ jsonrpc: "2.0",
644
+ id: msg.id,
645
+ result: {
646
+ tools: page,
647
+ ...(nextCursor !== undefined && { nextCursor }),
648
+ },
649
+ };
650
+ break;
651
+ }
652
+ case "tools/call": {
653
+ if (!this.initialized) {
654
+ response = {
655
+ jsonrpc: "2.0",
656
+ id: msg.id,
657
+ error: {
658
+ code: ErrorCodes.INVALID_REQUEST,
659
+ message: "Not initialized — send initialize first",
660
+ },
661
+ };
662
+ break;
663
+ }
664
+ // Reject duplicate request IDs — second call would overwrite the first's AbortController
665
+ if (msg.id !== undefined && this.inFlightControllers.has(msg.id)) {
666
+ response = {
667
+ jsonrpc: "2.0",
668
+ id: msg.id,
669
+ error: { code: -32600, message: "Duplicate request ID" },
670
+ };
671
+ break;
672
+ }
673
+ // Per-session token-bucket rate limit — consume token before dispatch
674
+ // (applies to both known tools and dynamic dispatch)
675
+ if (!this.peekToolRateLimit()) {
676
+ response = {
677
+ jsonrpc: "2.0",
678
+ id: msg.id,
679
+ error: {
680
+ code: ErrorCodes.RATE_LIMIT_EXCEEDED,
681
+ message: "Tool rate limit exceeded",
682
+ },
683
+ };
684
+ break;
685
+ }
686
+ // Concurrent tool-call limit
687
+ if (this.activeToolCalls >= MAX_CONCURRENT_TOOLS) {
688
+ response = {
689
+ jsonrpc: "2.0",
690
+ id: msg.id,
691
+ result: {
692
+ content: [
693
+ {
694
+ type: "text",
695
+ text: `Too many concurrent tool calls (max ${MAX_CONCURRENT_TOOLS}). Retry after current calls complete.`,
696
+ },
697
+ ],
698
+ isError: true,
699
+ },
700
+ };
701
+ break;
702
+ }
703
+ const params = msg.params;
704
+ const tool = this.tools.get(params.name);
705
+ if (!tool && this.dynamicToolDispatch) {
706
+ // Read-only scope check — unknown tools are not in the registry so
707
+ // we cannot inspect their annotations; block them under mcp:read.
708
+ if (this.sessionScope === "mcp:read") {
709
+ this.callCount++;
710
+ this.errorCount++;
711
+ response = {
712
+ jsonrpc: "2.0",
713
+ id: msg.id,
714
+ result: {
715
+ content: [
716
+ {
717
+ type: "text",
718
+ text: `Tool "${params.name}" is not available with read-only scope.`,
719
+ },
720
+ ],
721
+ isError: true,
722
+ },
723
+ };
724
+ break;
725
+ }
726
+ // Consume rate-limit token before dynamic dispatch
727
+ this.consumeToolRateLimitToken();
728
+ // Delegate to orchestrator proxy handler for unknown tool names
729
+ const dynDispatch = this.dynamicToolDispatch;
730
+ const dynId = msg.id;
731
+ const dynGen = this.generation;
732
+ const dynArgs = (typeof params.arguments === "object" &&
733
+ params.arguments !== null
734
+ ? params.arguments
735
+ : {});
736
+ this.activeToolCalls++;
737
+ setImmediate(async () => {
738
+ try {
739
+ const result = await dynDispatch(dynArgs);
740
+ if (dynGen !== this.generation || !this.activeWs)
741
+ return;
742
+ const dynResponse = {
743
+ jsonrpc: "2.0",
744
+ id: dynId ?? 0,
745
+ result,
746
+ };
747
+ safeSend(this.activeWs, JSON.stringify(dynResponse), this.logger);
748
+ }
749
+ catch (err) {
750
+ if (dynGen !== this.generation || !this.activeWs)
751
+ return;
752
+ const dynErrResponse = {
753
+ jsonrpc: "2.0",
754
+ id: dynId ?? 0,
755
+ error: {
756
+ code: -32603,
757
+ message: err instanceof Error ? err.message : String(err),
758
+ },
759
+ };
760
+ safeSend(this.activeWs, JSON.stringify(dynErrResponse), this.logger);
761
+ }
762
+ finally {
763
+ this.activeToolCalls--;
764
+ }
765
+ });
766
+ break;
767
+ }
768
+ if (!tool) {
769
+ response = {
770
+ jsonrpc: "2.0",
771
+ id: msg.id,
772
+ error: {
773
+ code: ErrorCodes.TOOL_NOT_FOUND,
774
+ message: "Tool not found",
775
+ data: params.name,
776
+ },
777
+ };
778
+ }
779
+ else if (this.sessionScope === "mcp:read" &&
780
+ !(tool.schema.annotations?.readOnlyHint === true &&
781
+ !tool.schema.annotations?.destructiveHint &&
782
+ !tool.schema.annotations?.openWorldHint)) {
783
+ this.callCount++;
784
+ this.errorCount++;
785
+ response = {
786
+ jsonrpc: "2.0",
787
+ id: msg.id,
788
+ result: {
789
+ content: [
790
+ {
791
+ type: "text",
792
+ text: `Tool "${params.name}" requires full mcp scope. This session has mcp:read (read-only) scope.`,
793
+ },
794
+ ],
795
+ isError: true,
796
+ },
797
+ };
798
+ }
799
+ else if (this.denyTools.has(params.name)) {
800
+ this.callCount++;
801
+ this.errorCount++;
802
+ response = {
803
+ jsonrpc: "2.0",
804
+ id: msg.id,
805
+ result: {
806
+ content: [
807
+ {
808
+ type: "text",
809
+ text: `Tool "${params.name}" is denied for this session.`,
810
+ },
811
+ ],
812
+ isError: true,
813
+ },
814
+ };
815
+ }
816
+ else if (tool.schema.extensionRequired &&
817
+ !(this.isExtensionConnectedFn?.() ?? true)) {
818
+ this.callCount++;
819
+ this.errorCount++;
820
+ response = {
821
+ jsonrpc: "2.0",
822
+ id: msg.id,
823
+ result: {
824
+ content: [
825
+ {
826
+ type: "text",
827
+ text: 'The VS Code extension is not connected. This tool requires the extension to be running.\n\nTo reconnect: open the Command Palette in VS Code and run "Claude IDE Bridge: Reconnect".\nIf the extension is not installed, install it from the marketplace: oolab-labs.claude-ide-bridge-extension',
828
+ },
829
+ ],
830
+ isError: true,
831
+ },
832
+ };
833
+ }
834
+ else {
835
+ const startTime = Date.now();
836
+ const callId = Math.random().toString(36).slice(2, 10);
837
+ const callLog = this.logger.child({ tool: params.name, callId });
838
+ let timedOut = false;
839
+ let handlerPromise = null;
840
+ try {
841
+ const rawArgs = params.arguments ?? {};
842
+ if (typeof rawArgs !== "object" ||
843
+ rawArgs === null ||
844
+ Array.isArray(rawArgs)) {
845
+ this.callCount++;
846
+ this.errorCount++;
847
+ response = {
848
+ jsonrpc: "2.0",
849
+ id: msg.id,
850
+ error: {
851
+ code: ErrorCodes.INVALID_PARAMS,
852
+ message: "Tool arguments must be a JSON object",
853
+ },
854
+ };
855
+ break;
856
+ }
857
+ // Guard against oversized argument payloads before they reach tool handlers.
858
+ // Check rawArgs (before _meta strip) so a large _meta cannot bypass the limit.
859
+ if (JSON.stringify(rawArgs).length > 1_048_576) {
860
+ this.callCount++;
861
+ this.errorCount++;
862
+ response = {
863
+ jsonrpc: "2.0",
864
+ id: msg.id,
865
+ error: {
866
+ code: ErrorCodes.INVALID_PARAMS,
867
+ message: "Tool arguments exceed 1 MB size limit",
868
+ },
869
+ };
870
+ break;
871
+ }
872
+ // Strip _meta from arguments — it's a reserved MCP protocol field
873
+ // that clients (e.g. Claude Code) may embed inside arguments. Tool
874
+ // schemas use additionalProperties:false, so leaving it in causes
875
+ // AJV to reject the call with -32602.
876
+ const { _meta: _stripped, ...toolArgs } = rawArgs;
877
+ // AJV structural validation
878
+ const validate = this.getValidator(params.name);
879
+ if (validate && !validate(toolArgs)) {
880
+ this.callCount++;
881
+ this.errorCount++;
882
+ const messages = (validate.errors ?? [])
883
+ .map((e) => `${e.instancePath || "."} ${e.message}`)
884
+ .join("; ");
885
+ response = {
886
+ jsonrpc: "2.0",
887
+ id: msg.id,
888
+ error: {
889
+ code: ErrorCodes.INVALID_PARAMS,
890
+ message: `Invalid tool arguments: ${messages}`,
891
+ },
892
+ };
893
+ break;
894
+ }
895
+ this.consumeToolRateLimitToken(); // Consume after AJV passes — not on validation failures
896
+ this.callCount++; // Count after validation — only real execution attempts
897
+ callLog.debug(`Calling tool: ${params.name}`);
898
+ this.logger.event("tool_call", { tool: params.name, callId });
899
+ const controller = new AbortController();
900
+ this.inFlightControllers.set(msg.id, controller);
901
+ // Build progress callback if client provided a valid progressToken.
902
+ // Reject non-primitive tokens to prevent object injection into notifications.
903
+ const rawToken = params._meta?.progressToken;
904
+ const progressToken = typeof rawToken === "string" || typeof rawToken === "number"
905
+ ? rawToken
906
+ : undefined;
907
+ const progressFn = progressToken !== undefined
908
+ ? (progress, total, message) => {
909
+ this.onActivity?.();
910
+ this.sendProgress(ws, progressToken, progress, total, message);
911
+ }
912
+ : undefined;
913
+ this.activeToolCalls++;
914
+ this.inFlightToolNames.set(msg.id, params.name);
915
+ let timeoutHandle;
916
+ if (this.approvalGate) {
917
+ const decision = await this.approvalGate({
918
+ toolName: params.name,
919
+ params: toolArgs,
920
+ sessionId: this.sessionId,
921
+ });
922
+ if (decision === "rejected" || decision === "expired") {
923
+ this.activeToolCalls--;
924
+ this.inFlightToolNames.delete(msg.id);
925
+ this.inFlightControllers.delete(msg.id);
926
+ this.errorCount++;
927
+ this.activityLog?.record(params.name, Date.now() - startTime, "error", undefined, this.sessionId ?? undefined);
928
+ response = {
929
+ jsonrpc: "2.0",
930
+ id: msg.id,
931
+ result: {
932
+ content: [
933
+ {
934
+ type: "text",
935
+ text: decision === "rejected"
936
+ ? `Tool call "${params.name}" rejected by human reviewer via Patchwork dashboard.`
937
+ : `Tool call "${params.name}" expired without human decision (no action taken).`,
938
+ },
939
+ ],
940
+ isError: true,
941
+ },
942
+ };
943
+ break;
944
+ }
945
+ }
946
+ try {
947
+ const effectiveTimeout = tool.timeoutMs ?? TOOL_TIMEOUT_MS;
948
+ const timeoutPromise = new Promise((_, reject) => {
949
+ timeoutHandle = setTimeout(() => {
950
+ timedOut = true;
951
+ controller.abort();
952
+ reject(new Error(`Tool "${params.name}" timed out after ${effectiveTimeout}ms`));
953
+ }, effectiveTimeout);
954
+ });
955
+ handlerPromise = withSpan("mcp.tool_call", {
956
+ "mcp.tool.name": params.name,
957
+ "mcp.session.id": this.sessionId ?? "unknown",
958
+ ...(this.claudeCodeSessionId && {
959
+ "claude.session.id": this.claudeCodeSessionId,
960
+ }),
961
+ }, async () => tool.handler(toolArgs, controller.signal, progressFn));
962
+ const result = await Promise.race([
963
+ handlerPromise,
964
+ timeoutPromise,
965
+ ]);
966
+ const durationMs = Date.now() - startTime;
967
+ this.activityLog?.record(params.name, durationMs, "success", undefined, this.sessionId ?? undefined);
968
+ callLog.debug(`Tool completed in ${durationMs}ms`);
969
+ // Validate structuredContent against outputSchema before sending.
970
+ // Strips structuredContent rather than forwarding non-conforming data
971
+ // to MCP clients (prevents plugin tools leaking out-of-schema fields).
972
+ const toolResult = result;
973
+ // Inject _meta["anthropic/maxResultSizeChars"] for large results so
974
+ // Claude Code 2.1.91+ persists the full content rather than truncating
975
+ // it at its own internal limit (MCP tool result persistence override).
976
+ const totalContentChars = Array.isArray(toolResult.content)
977
+ ? toolResult.content.reduce((sum, item) => sum +
978
+ (typeof item.text === "string"
979
+ ? item.text.length
980
+ : 0), 0)
981
+ : 0;
982
+ // Track largest result seen per tool for getSessionUsage reporting.
983
+ this.resultSizeTracker.set(params.name, Math.max(this.resultSizeTracker.get(params.name) ?? 0, totalContentChars));
984
+ const resultWithMeta = totalContentChars > META_SIZE_HINT_THRESHOLD
985
+ ? {
986
+ ...toolResult,
987
+ _meta: {
988
+ "anthropic/maxResultSizeChars": totalContentChars,
989
+ },
990
+ }
991
+ : toolResult;
992
+ if (tool.schema.outputSchema !== undefined &&
993
+ toolResult.structuredContent !== undefined) {
994
+ let outValidator = this.outputValidators.get(params.name);
995
+ if (!outValidator) {
996
+ outValidator = this.ajv.compile(tool.schema.outputSchema);
997
+ this.outputValidators.set(params.name, outValidator);
998
+ }
999
+ if (!outValidator(toolResult.structuredContent)) {
1000
+ callLog.warn(`structuredContent failed outputSchema validation for tool "${params.name}" — stripping`);
1001
+ const { structuredContent: _dropped, ...safeResult } = resultWithMeta;
1002
+ response = {
1003
+ jsonrpc: "2.0",
1004
+ id: msg.id,
1005
+ result: safeResult,
1006
+ };
1007
+ }
1008
+ else {
1009
+ response = {
1010
+ jsonrpc: "2.0",
1011
+ id: msg.id,
1012
+ result: resultWithMeta,
1013
+ };
1014
+ }
1015
+ }
1016
+ else {
1017
+ response = {
1018
+ jsonrpc: "2.0",
1019
+ id: msg.id,
1020
+ result: resultWithMeta,
1021
+ };
1022
+ }
1023
+ }
1024
+ finally {
1025
+ if (timeoutHandle !== undefined)
1026
+ clearTimeout(timeoutHandle);
1027
+ // Only touch shared state if we're still on the same generation.
1028
+ // detach() clears inFlightControllers and resets activeToolCalls for
1029
+ // new connections; an orphaned finally from a previous generation must
1030
+ // not delete the new session's controller or skew its counter.
1031
+ if (gen === this.generation) {
1032
+ this.inFlightControllers.delete(msg.id);
1033
+ this.inFlightToolNames.delete(msg.id);
1034
+ this.activeToolCalls = Math.max(0, this.activeToolCalls - 1);
1035
+ }
1036
+ }
1037
+ }
1038
+ catch (err) {
1039
+ // MCP spec: tool execution errors are returned as successful
1040
+ // JSON-RPC responses with isError: true in the content, NOT as
1041
+ // JSON-RPC error responses. This lets the LLM understand and
1042
+ // potentially recover from tool failures.
1043
+ const message = err instanceof Error ? err.message : String(err);
1044
+ const errCode = err instanceof Error &&
1045
+ typeof err.code === "string"
1046
+ ? err.code
1047
+ : undefined;
1048
+ callLog.error(`Tool ${params.name} failed: ${message}`);
1049
+ this.errorCount++;
1050
+ this.activityLog?.record(params.name, Date.now() - startTime, "error", message, this.sessionId ?? undefined);
1051
+ const errPayload = { error: message };
1052
+ if (errCode !== undefined)
1053
+ errPayload.code = errCode;
1054
+ response = {
1055
+ jsonrpc: "2.0",
1056
+ id: msg.id,
1057
+ result: {
1058
+ content: [
1059
+ { type: "text", text: JSON.stringify(errPayload) },
1060
+ ],
1061
+ isError: true,
1062
+ },
1063
+ };
1064
+ // Track zombie tool completion after timeout
1065
+ if (timedOut && handlerPromise) {
1066
+ handlerPromise
1067
+ .then(() => {
1068
+ this.logger.warn(`Zombie tool "${params.name}" completed ${Date.now() - startTime}ms after start (timed out at ${tool.timeoutMs ?? TOOL_TIMEOUT_MS}ms)`);
1069
+ })
1070
+ .catch(() => {
1071
+ // Already logged the timeout error above; suppress the zombie's error
1072
+ });
1073
+ }
1074
+ }
1075
+ }
1076
+ break;
1077
+ }
1078
+ case "resources/list": {
1079
+ if (!this.initialized) {
1080
+ response = {
1081
+ jsonrpc: "2.0",
1082
+ id: msg.id,
1083
+ error: {
1084
+ code: ErrorCodes.INVALID_REQUEST,
1085
+ message: "Not initialized — send initialize first",
1086
+ },
1087
+ };
1088
+ break;
1089
+ }
1090
+ const resListParams = msg.params;
1091
+ const resCursor = typeof resListParams?.cursor === "string"
1092
+ ? resListParams.cursor
1093
+ : undefined;
1094
+ const { listResources } = await import("./resources.js");
1095
+ const listResult = listResources(this.workspace, resCursor);
1096
+ response = {
1097
+ jsonrpc: "2.0",
1098
+ id: msg.id,
1099
+ result: listResult,
1100
+ };
1101
+ break;
1102
+ }
1103
+ case "resources/read": {
1104
+ if (!this.initialized) {
1105
+ response = {
1106
+ jsonrpc: "2.0",
1107
+ id: msg.id,
1108
+ error: {
1109
+ code: ErrorCodes.INVALID_REQUEST,
1110
+ message: "Not initialized — send initialize first",
1111
+ },
1112
+ };
1113
+ break;
1114
+ }
1115
+ const resReadParams = msg.params;
1116
+ if (typeof resReadParams?.uri !== "string") {
1117
+ response = {
1118
+ jsonrpc: "2.0",
1119
+ id: msg.id,
1120
+ error: {
1121
+ code: ErrorCodes.INVALID_PARAMS,
1122
+ message: "resources/read requires a string uri parameter",
1123
+ },
1124
+ };
1125
+ break;
1126
+ }
1127
+ const { readResource } = await import("./resources.js");
1128
+ const readResult = readResource(this.workspace, resReadParams.uri);
1129
+ if ("error" in readResult) {
1130
+ response = {
1131
+ jsonrpc: "2.0",
1132
+ id: msg.id,
1133
+ error: {
1134
+ code: ErrorCodes.INVALID_PARAMS,
1135
+ message: readResult.error,
1136
+ data: { code: readResult.code },
1137
+ },
1138
+ };
1139
+ }
1140
+ else {
1141
+ response = {
1142
+ jsonrpc: "2.0",
1143
+ id: msg.id,
1144
+ result: readResult,
1145
+ };
1146
+ }
1147
+ break;
1148
+ }
1149
+ case "prompts/list": {
1150
+ if (!this.initialized) {
1151
+ response = {
1152
+ jsonrpc: "2.0",
1153
+ id: msg.id,
1154
+ error: {
1155
+ code: ErrorCodes.INVALID_REQUEST,
1156
+ message: "Not initialized — send initialize first",
1157
+ },
1158
+ };
1159
+ break;
1160
+ }
1161
+ const { PROMPTS } = await import("./prompts.js");
1162
+ response = {
1163
+ jsonrpc: "2.0",
1164
+ id: msg.id,
1165
+ result: { prompts: PROMPTS },
1166
+ };
1167
+ break;
1168
+ }
1169
+ case "prompts/get": {
1170
+ if (!this.initialized) {
1171
+ response = {
1172
+ jsonrpc: "2.0",
1173
+ id: msg.id,
1174
+ error: {
1175
+ code: ErrorCodes.INVALID_REQUEST,
1176
+ message: "Not initialized — send initialize first",
1177
+ },
1178
+ };
1179
+ break;
1180
+ }
1181
+ const promptParams = msg.params;
1182
+ if (typeof promptParams?.name !== "string") {
1183
+ response = {
1184
+ jsonrpc: "2.0",
1185
+ id: msg.id,
1186
+ error: {
1187
+ code: ErrorCodes.INVALID_PARAMS,
1188
+ message: "prompts/get requires a string name parameter",
1189
+ },
1190
+ };
1191
+ break;
1192
+ }
1193
+ const promptArgs = typeof promptParams.arguments === "object" &&
1194
+ promptParams.arguments !== null &&
1195
+ !Array.isArray(promptParams.arguments)
1196
+ ? promptParams.arguments
1197
+ : {};
1198
+ const { getPrompt } = await import("./prompts.js");
1199
+ const promptResult = getPrompt(promptParams.name, promptArgs);
1200
+ if (!promptResult) {
1201
+ response = {
1202
+ jsonrpc: "2.0",
1203
+ id: msg.id,
1204
+ error: {
1205
+ code: ErrorCodes.INVALID_PARAMS,
1206
+ message: `Unknown prompt or missing required argument: "${promptParams.name}"`,
1207
+ },
1208
+ };
1209
+ break;
1210
+ }
1211
+ response = {
1212
+ jsonrpc: "2.0",
1213
+ id: msg.id,
1214
+ result: promptResult,
1215
+ };
1216
+ break;
1217
+ }
1218
+ case "ping":
1219
+ response = { jsonrpc: "2.0", id: msg.id, result: {} };
1220
+ break;
1221
+ default:
1222
+ response = {
1223
+ jsonrpc: "2.0",
1224
+ id: msg.id,
1225
+ error: {
1226
+ code: ErrorCodes.METHOD_NOT_FOUND,
1227
+ message: `Method not found: ${safeMethod(msg.method)}`,
1228
+ },
1229
+ };
1230
+ }
1231
+ this.logger.debug(`--> response for ${safeMethod(msg.method)}`);
1232
+ const sent = await safeSend(ws, JSON.stringify(response), this.logger);
1233
+ if (!sent) {
1234
+ this.logger.warn(`Response for ${safeMethod(msg.method)} (id=${msg.id}) dropped — socket closed`);
1235
+ }
1236
+ }
1237
+ catch (err) {
1238
+ this.logger.error(`Failed to handle message: ${err}`);
1239
+ }
1240
+ };
1241
+ this.activeListener = listener;
1242
+ ws.on("message", listener);
1243
+ }
1244
+ /** Send progress notification for a long-running tool */
1245
+ sendProgress(ws, progressToken, progress, total, message) {
1246
+ const msg = {
1247
+ jsonrpc: "2.0",
1248
+ method: "notifications/progress",
1249
+ params: {
1250
+ progressToken,
1251
+ progress,
1252
+ ...(total !== undefined && { total }),
1253
+ ...(message !== undefined && { message }),
1254
+ },
1255
+ };
1256
+ if (ws.readyState !== WebSocket.OPEN)
1257
+ return;
1258
+ safeSend(ws, JSON.stringify(msg), this.logger).catch(() => {
1259
+ /* best-effort */
1260
+ });
1261
+ }
1262
+ /** Send a server-initiated notification */
1263
+ static sendNotification(ws, method, params, logger) {
1264
+ const msg = { jsonrpc: "2.0", method, params };
1265
+ if (ws.readyState !== WebSocket.OPEN)
1266
+ return;
1267
+ safeSend(ws, JSON.stringify(msg), (logger ?? { warn: () => { }, error: () => { } })).catch((err) => {
1268
+ logger?.warn(`Failed to send notification ${method}: ${err}`);
1269
+ });
1270
+ }
1271
+ }
1272
+ //# sourceMappingURL=transport.js.map