apex-code 0.0.1-alpha.9 → 0.0.5

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 (513) hide show
  1. package/CHANGELOG.md +150 -2
  2. package/README.md +9 -8
  3. package/dist/cli/args.d.ts +25 -1
  4. package/dist/cli/args.d.ts.map +1 -1
  5. package/dist/cli/args.js +43 -1
  6. package/dist/cli/args.js.map +1 -1
  7. package/dist/cli/mcp-command.d.ts +9 -0
  8. package/dist/cli/mcp-command.d.ts.map +1 -0
  9. package/dist/cli/mcp-command.js +53 -0
  10. package/dist/cli/mcp-command.js.map +1 -0
  11. package/dist/cli/project-trust.d.ts.map +1 -1
  12. package/dist/cli/project-trust.js +3 -1
  13. package/dist/cli/project-trust.js.map +1 -1
  14. package/dist/cli/startup-ui.d.ts.map +1 -1
  15. package/dist/cli/startup-ui.js +2 -1
  16. package/dist/cli/startup-ui.js.map +1 -1
  17. package/dist/cli.d.ts.map +1 -1
  18. package/dist/cli.js +94 -5
  19. package/dist/cli.js.map +1 -1
  20. package/dist/config.d.ts.map +1 -1
  21. package/dist/config.js +24 -5
  22. package/dist/config.js.map +1 -1
  23. package/dist/core/agent-session-services.d.ts +3 -0
  24. package/dist/core/agent-session-services.d.ts.map +1 -1
  25. package/dist/core/agent-session-services.js +1 -0
  26. package/dist/core/agent-session-services.js.map +1 -1
  27. package/dist/core/agent-session.d.ts +157 -8
  28. package/dist/core/agent-session.d.ts.map +1 -1
  29. package/dist/core/agent-session.js +565 -22
  30. package/dist/core/agent-session.js.map +1 -1
  31. package/dist/core/bash-executor.d.ts.map +1 -1
  32. package/dist/core/bash-executor.js +7 -0
  33. package/dist/core/bash-executor.js.map +1 -1
  34. package/dist/core/checkpoints/git-checkpoints.d.ts +39 -0
  35. package/dist/core/checkpoints/git-checkpoints.d.ts.map +1 -0
  36. package/dist/core/checkpoints/git-checkpoints.js +251 -0
  37. package/dist/core/checkpoints/git-checkpoints.js.map +1 -0
  38. package/dist/core/checkpoints/session-checkpoints.d.ts +23 -0
  39. package/dist/core/checkpoints/session-checkpoints.d.ts.map +1 -0
  40. package/dist/core/checkpoints/session-checkpoints.js +36 -0
  41. package/dist/core/checkpoints/session-checkpoints.js.map +1 -0
  42. package/dist/core/compaction/branch-summarization.d.ts.map +1 -1
  43. package/dist/core/compaction/branch-summarization.js +4 -3
  44. package/dist/core/compaction/branch-summarization.js.map +1 -1
  45. package/dist/core/compaction/compaction.d.ts +5 -0
  46. package/dist/core/compaction/compaction.d.ts.map +1 -1
  47. package/dist/core/compaction/compaction.js +19 -5
  48. package/dist/core/compaction/compaction.js.map +1 -1
  49. package/dist/core/delegation/runtime.d.ts +6 -5
  50. package/dist/core/delegation/runtime.d.ts.map +1 -1
  51. package/dist/core/delegation/runtime.js +4 -3
  52. package/dist/core/delegation/runtime.js.map +1 -1
  53. package/dist/core/exec.d.ts.map +1 -1
  54. package/dist/core/exec.js +5 -2
  55. package/dist/core/exec.js.map +1 -1
  56. package/dist/core/extensions/index.d.ts +1 -1
  57. package/dist/core/extensions/index.d.ts.map +1 -1
  58. package/dist/core/extensions/index.js.map +1 -1
  59. package/dist/core/extensions/loader.d.ts.map +1 -1
  60. package/dist/core/extensions/loader.js +8 -2
  61. package/dist/core/extensions/loader.js.map +1 -1
  62. package/dist/core/extensions/runner.d.ts +5 -0
  63. package/dist/core/extensions/runner.d.ts.map +1 -1
  64. package/dist/core/extensions/runner.js +45 -1
  65. package/dist/core/extensions/runner.js.map +1 -1
  66. package/dist/core/extensions/source-runtime.d.ts +14 -0
  67. package/dist/core/extensions/source-runtime.d.ts.map +1 -0
  68. package/dist/core/extensions/source-runtime.js +56 -0
  69. package/dist/core/extensions/source-runtime.js.map +1 -0
  70. package/dist/core/extensions/types.d.ts +22 -2
  71. package/dist/core/extensions/types.d.ts.map +1 -1
  72. package/dist/core/extensions/types.js.map +1 -1
  73. package/dist/core/formatter-lifecycle.d.ts +69 -0
  74. package/dist/core/formatter-lifecycle.d.ts.map +1 -0
  75. package/dist/core/formatter-lifecycle.js +360 -0
  76. package/dist/core/formatter-lifecycle.js.map +1 -0
  77. package/dist/core/hooks/command-handler.d.ts +19 -0
  78. package/dist/core/hooks/command-handler.d.ts.map +1 -0
  79. package/dist/core/hooks/command-handler.js +112 -0
  80. package/dist/core/hooks/command-handler.js.map +1 -0
  81. package/dist/core/hooks/http-handler.d.ts +10 -0
  82. package/dist/core/hooks/http-handler.d.ts.map +1 -0
  83. package/dist/core/hooks/http-handler.js +36 -0
  84. package/dist/core/hooks/http-handler.js.map +1 -0
  85. package/dist/core/hooks/loader.d.ts +29 -0
  86. package/dist/core/hooks/loader.d.ts.map +1 -0
  87. package/dist/core/hooks/loader.js +139 -0
  88. package/dist/core/hooks/loader.js.map +1 -0
  89. package/dist/core/hooks/runtime.d.ts +13 -0
  90. package/dist/core/hooks/runtime.d.ts.map +1 -0
  91. package/dist/core/hooks/runtime.js +63 -0
  92. package/dist/core/hooks/runtime.js.map +1 -0
  93. package/dist/core/hooks/types.d.ts +83 -0
  94. package/dist/core/hooks/types.d.ts.map +1 -0
  95. package/dist/core/hooks/types.js +24 -0
  96. package/dist/core/hooks/types.js.map +1 -0
  97. package/dist/core/mcp/config.d.ts +16 -0
  98. package/dist/core/mcp/config.d.ts.map +1 -0
  99. package/dist/core/mcp/config.js +166 -0
  100. package/dist/core/mcp/config.js.map +1 -0
  101. package/dist/core/mcp/connector.d.ts +35 -0
  102. package/dist/core/mcp/connector.d.ts.map +1 -0
  103. package/dist/core/mcp/connector.js +105 -0
  104. package/dist/core/mcp/connector.js.map +1 -0
  105. package/dist/core/mcp/contract.d.ts +19 -0
  106. package/dist/core/mcp/contract.d.ts.map +1 -0
  107. package/dist/core/mcp/contract.js +89 -0
  108. package/dist/core/mcp/contract.js.map +1 -0
  109. package/dist/core/mcp/mcp-tool.d.ts +32 -0
  110. package/dist/core/mcp/mcp-tool.d.ts.map +1 -0
  111. package/dist/core/mcp/mcp-tool.js +171 -0
  112. package/dist/core/mcp/mcp-tool.js.map +1 -0
  113. package/dist/core/mcp/metadata-cache.d.ts +29 -0
  114. package/dist/core/mcp/metadata-cache.d.ts.map +1 -0
  115. package/dist/core/mcp/metadata-cache.js +99 -0
  116. package/dist/core/mcp/metadata-cache.js.map +1 -0
  117. package/dist/core/mcp/oauth/authorize.d.ts +21 -0
  118. package/dist/core/mcp/oauth/authorize.d.ts.map +1 -0
  119. package/dist/core/mcp/oauth/authorize.js +32 -0
  120. package/dist/core/mcp/oauth/authorize.js.map +1 -0
  121. package/dist/core/mcp/oauth/discover.d.ts +27 -0
  122. package/dist/core/mcp/oauth/discover.d.ts.map +1 -0
  123. package/dist/core/mcp/oauth/discover.js +116 -0
  124. package/dist/core/mcp/oauth/discover.js.map +1 -0
  125. package/dist/core/mcp/oauth/flow.d.ts +31 -0
  126. package/dist/core/mcp/oauth/flow.d.ts.map +1 -0
  127. package/dist/core/mcp/oauth/flow.js +175 -0
  128. package/dist/core/mcp/oauth/flow.js.map +1 -0
  129. package/dist/core/mcp/oauth/mcp-token.d.ts +51 -0
  130. package/dist/core/mcp/oauth/mcp-token.d.ts.map +1 -0
  131. package/dist/core/mcp/oauth/mcp-token.js +129 -0
  132. package/dist/core/mcp/oauth/mcp-token.js.map +1 -0
  133. package/dist/core/mcp/oauth/pkce.d.ts +12 -0
  134. package/dist/core/mcp/oauth/pkce.d.ts.map +1 -0
  135. package/dist/core/mcp/oauth/pkce.js +11 -0
  136. package/dist/core/mcp/oauth/pkce.js.map +1 -0
  137. package/dist/core/mcp/oauth/register.d.ts +16 -0
  138. package/dist/core/mcp/oauth/register.d.ts.map +1 -0
  139. package/dist/core/mcp/oauth/register.js +42 -0
  140. package/dist/core/mcp/oauth/register.js.map +1 -0
  141. package/dist/core/mcp/oauth/token-client.d.ts +33 -0
  142. package/dist/core/mcp/oauth/token-client.d.ts.map +1 -0
  143. package/dist/core/mcp/oauth/token-client.js +65 -0
  144. package/dist/core/mcp/oauth/token-client.js.map +1 -0
  145. package/dist/core/mcp/runtime.d.ts +20 -0
  146. package/dist/core/mcp/runtime.d.ts.map +1 -0
  147. package/dist/core/mcp/runtime.js +37 -0
  148. package/dist/core/mcp/runtime.js.map +1 -0
  149. package/dist/core/mcp/schema.d.ts +22 -0
  150. package/dist/core/mcp/schema.d.ts.map +1 -0
  151. package/dist/core/mcp/schema.js +20 -0
  152. package/dist/core/mcp/schema.js.map +1 -0
  153. package/dist/core/mcp/server-manager.d.ts +43 -0
  154. package/dist/core/mcp/server-manager.d.ts.map +1 -0
  155. package/dist/core/mcp/server-manager.js +142 -0
  156. package/dist/core/mcp/server-manager.js.map +1 -0
  157. package/dist/core/mcp/types.d.ts +100 -0
  158. package/dist/core/mcp/types.d.ts.map +1 -0
  159. package/dist/core/mcp/types.js +8 -0
  160. package/dist/core/mcp/types.js.map +1 -0
  161. package/dist/core/permissions/gate.d.ts.map +1 -1
  162. package/dist/core/permissions/gate.js +45 -4
  163. package/dist/core/permissions/gate.js.map +1 -1
  164. package/dist/core/permissions/operations.d.ts +20 -0
  165. package/dist/core/permissions/operations.d.ts.map +1 -0
  166. package/dist/core/permissions/operations.js +9 -0
  167. package/dist/core/permissions/operations.js.map +1 -0
  168. package/dist/core/permissions/policy-command.d.ts +42 -0
  169. package/dist/core/permissions/policy-command.d.ts.map +1 -0
  170. package/dist/core/permissions/policy-command.js +44 -0
  171. package/dist/core/permissions/policy-command.js.map +1 -0
  172. package/dist/core/permissions/responder.d.ts +49 -2
  173. package/dist/core/permissions/responder.d.ts.map +1 -1
  174. package/dist/core/permissions/responder.js +20 -8
  175. package/dist/core/permissions/responder.js.map +1 -1
  176. package/dist/core/permissions/rules.d.ts.map +1 -1
  177. package/dist/core/permissions/rules.js +11 -1
  178. package/dist/core/permissions/rules.js.map +1 -1
  179. package/dist/core/permissions/store.d.ts +18 -0
  180. package/dist/core/permissions/store.d.ts.map +1 -1
  181. package/dist/core/permissions/store.js +89 -8
  182. package/dist/core/permissions/store.js.map +1 -1
  183. package/dist/core/policy-executor.d.ts +47 -0
  184. package/dist/core/policy-executor.d.ts.map +1 -0
  185. package/dist/core/policy-executor.js +228 -0
  186. package/dist/core/policy-executor.js.map +1 -0
  187. package/dist/core/policy-loader.d.ts +72 -0
  188. package/dist/core/policy-loader.d.ts.map +1 -0
  189. package/dist/core/policy-loader.js +195 -0
  190. package/dist/core/policy-loader.js.map +1 -0
  191. package/dist/core/project-trust.d.ts +1 -1
  192. package/dist/core/project-trust.d.ts.map +1 -1
  193. package/dist/core/project-trust.js.map +1 -1
  194. package/dist/core/sandbox/bwrap-arguments.d.ts +41 -0
  195. package/dist/core/sandbox/bwrap-arguments.d.ts.map +1 -0
  196. package/dist/core/sandbox/bwrap-arguments.js +136 -0
  197. package/dist/core/sandbox/bwrap-arguments.js.map +1 -0
  198. package/dist/core/sandbox/child-entry.d.ts.map +1 -1
  199. package/dist/core/sandbox/child-entry.js +3 -1
  200. package/dist/core/sandbox/child-entry.js.map +1 -1
  201. package/dist/core/sandbox/cli-launch.d.ts +84 -4
  202. package/dist/core/sandbox/cli-launch.d.ts.map +1 -1
  203. package/dist/core/sandbox/cli-launch.js +115 -16
  204. package/dist/core/sandbox/cli-launch.js.map +1 -1
  205. package/dist/core/sandbox/cli-supervisor.d.ts +2 -0
  206. package/dist/core/sandbox/cli-supervisor.d.ts.map +1 -1
  207. package/dist/core/sandbox/cli-supervisor.js +50 -2
  208. package/dist/core/sandbox/cli-supervisor.js.map +1 -1
  209. package/dist/core/sandbox/full-access.d.ts +28 -0
  210. package/dist/core/sandbox/full-access.d.ts.map +1 -0
  211. package/dist/core/sandbox/full-access.js +63 -0
  212. package/dist/core/sandbox/full-access.js.map +1 -0
  213. package/dist/core/sandbox/git-identity.d.ts +57 -0
  214. package/dist/core/sandbox/git-identity.d.ts.map +1 -0
  215. package/dist/core/sandbox/git-identity.js +78 -0
  216. package/dist/core/sandbox/git-identity.js.map +1 -0
  217. package/dist/core/sandbox/host-approval.d.ts +42 -0
  218. package/dist/core/sandbox/host-approval.d.ts.map +1 -0
  219. package/dist/core/sandbox/host-approval.js +99 -0
  220. package/dist/core/sandbox/host-approval.js.map +1 -0
  221. package/dist/core/sandbox/linux-backend.d.ts +14 -0
  222. package/dist/core/sandbox/linux-backend.d.ts.map +1 -1
  223. package/dist/core/sandbox/linux-backend.js +158 -80
  224. package/dist/core/sandbox/linux-backend.js.map +1 -1
  225. package/dist/core/sandbox/macos-backend.d.ts +5 -0
  226. package/dist/core/sandbox/macos-backend.d.ts.map +1 -1
  227. package/dist/core/sandbox/macos-backend.js +164 -5
  228. package/dist/core/sandbox/macos-backend.js.map +1 -1
  229. package/dist/core/sandbox/network-proxy.d.ts +19 -0
  230. package/dist/core/sandbox/network-proxy.d.ts.map +1 -1
  231. package/dist/core/sandbox/network-proxy.js +93 -19
  232. package/dist/core/sandbox/network-proxy.js.map +1 -1
  233. package/dist/core/sandbox/policy.d.ts +8 -0
  234. package/dist/core/sandbox/policy.d.ts.map +1 -1
  235. package/dist/core/sandbox/policy.js +29 -12
  236. package/dist/core/sandbox/policy.js.map +1 -1
  237. package/dist/core/sandbox/profiles.d.ts +25 -0
  238. package/dist/core/sandbox/profiles.d.ts.map +1 -0
  239. package/dist/core/sandbox/profiles.js +23 -0
  240. package/dist/core/sandbox/profiles.js.map +1 -0
  241. package/dist/core/sandbox/rpc/command-client.d.ts +28 -0
  242. package/dist/core/sandbox/rpc/command-client.d.ts.map +1 -0
  243. package/dist/core/sandbox/rpc/command-client.js +86 -0
  244. package/dist/core/sandbox/rpc/command-client.js.map +1 -0
  245. package/dist/core/sandbox/rpc/command-proxy.d.ts +48 -0
  246. package/dist/core/sandbox/rpc/command-proxy.d.ts.map +1 -0
  247. package/dist/core/sandbox/rpc/command-proxy.js +120 -0
  248. package/dist/core/sandbox/rpc/command-proxy.js.map +1 -0
  249. package/dist/core/sandbox/rpc/credential-proxy.d.ts.map +1 -1
  250. package/dist/core/sandbox/rpc/credential-proxy.js +2 -96
  251. package/dist/core/sandbox/rpc/credential-proxy.js.map +1 -1
  252. package/dist/core/sandbox/rpc/framing.d.ts +35 -0
  253. package/dist/core/sandbox/rpc/framing.d.ts.map +1 -0
  254. package/dist/core/sandbox/rpc/framing.js +114 -0
  255. package/dist/core/sandbox/rpc/framing.js.map +1 -0
  256. package/dist/core/sandbox/rpc/git-credential-helper.d.ts +47 -0
  257. package/dist/core/sandbox/rpc/git-credential-helper.d.ts.map +1 -0
  258. package/dist/core/sandbox/rpc/git-credential-helper.js +222 -0
  259. package/dist/core/sandbox/rpc/git-credential-helper.js.map +1 -0
  260. package/dist/core/sandbox/rpc/git-credential-proxy.d.ts +66 -0
  261. package/dist/core/sandbox/rpc/git-credential-proxy.d.ts.map +1 -0
  262. package/dist/core/sandbox/rpc/git-credential-proxy.js +209 -0
  263. package/dist/core/sandbox/rpc/git-credential-proxy.js.map +1 -0
  264. package/dist/core/sandbox/supervisor-temp.d.ts +18 -0
  265. package/dist/core/sandbox/supervisor-temp.d.ts.map +1 -0
  266. package/dist/core/sandbox/supervisor-temp.js +21 -0
  267. package/dist/core/sandbox/supervisor-temp.js.map +1 -0
  268. package/dist/core/sandbox/supervisor.d.ts +12 -1
  269. package/dist/core/sandbox/supervisor.d.ts.map +1 -1
  270. package/dist/core/sandbox/supervisor.js +15 -1
  271. package/dist/core/sandbox/supervisor.js.map +1 -1
  272. package/dist/core/sandbox/terminal-handoff.d.ts +68 -0
  273. package/dist/core/sandbox/terminal-handoff.d.ts.map +1 -0
  274. package/dist/core/sandbox/terminal-handoff.js +221 -0
  275. package/dist/core/sandbox/terminal-handoff.js.map +1 -0
  276. package/dist/core/sandbox/terminal-size.d.ts +13 -0
  277. package/dist/core/sandbox/terminal-size.d.ts.map +1 -1
  278. package/dist/core/sandbox/terminal-size.js +23 -2
  279. package/dist/core/sandbox/terminal-size.js.map +1 -1
  280. package/dist/core/sdk.d.ts +22 -0
  281. package/dist/core/sdk.d.ts.map +1 -1
  282. package/dist/core/sdk.js +53 -1
  283. package/dist/core/sdk.js.map +1 -1
  284. package/dist/core/session-manager.d.ts +13 -1
  285. package/dist/core/session-manager.d.ts.map +1 -1
  286. package/dist/core/session-manager.js +32 -5
  287. package/dist/core/session-manager.js.map +1 -1
  288. package/dist/core/settings-manager.d.ts +163 -1
  289. package/dist/core/settings-manager.d.ts.map +1 -1
  290. package/dist/core/settings-manager.js +107 -0
  291. package/dist/core/settings-manager.js.map +1 -1
  292. package/dist/core/slash-commands.d.ts.map +1 -1
  293. package/dist/core/slash-commands.js +2 -0
  294. package/dist/core/slash-commands.js.map +1 -1
  295. package/dist/core/tools/background-shell.d.ts +39 -0
  296. package/dist/core/tools/background-shell.d.ts.map +1 -0
  297. package/dist/core/tools/background-shell.js +81 -0
  298. package/dist/core/tools/background-shell.js.map +1 -0
  299. package/dist/core/tools/bash-command-segments.d.ts.map +1 -1
  300. package/dist/core/tools/bash-command-segments.js +2 -0
  301. package/dist/core/tools/bash-command-segments.js.map +1 -1
  302. package/dist/core/tools/bash.d.ts +30 -15
  303. package/dist/core/tools/bash.d.ts.map +1 -1
  304. package/dist/core/tools/bash.js +228 -12
  305. package/dist/core/tools/bash.js.map +1 -1
  306. package/dist/core/tools/contract-snapshot.d.ts +58 -0
  307. package/dist/core/tools/contract-snapshot.d.ts.map +1 -0
  308. package/dist/core/tools/contract-snapshot.js +51 -0
  309. package/dist/core/tools/contract-snapshot.js.map +1 -0
  310. package/dist/core/tools/contract.d.ts +53 -5
  311. package/dist/core/tools/contract.d.ts.map +1 -1
  312. package/dist/core/tools/contract.js +22 -2
  313. package/dist/core/tools/contract.js.map +1 -1
  314. package/dist/core/tools/delegate.d.ts +6 -4
  315. package/dist/core/tools/delegate.d.ts.map +1 -1
  316. package/dist/core/tools/delegate.js +21 -11
  317. package/dist/core/tools/delegate.js.map +1 -1
  318. package/dist/core/tools/edit-diff.d.ts +48 -0
  319. package/dist/core/tools/edit-diff.d.ts.map +1 -1
  320. package/dist/core/tools/edit-diff.js +215 -3
  321. package/dist/core/tools/edit-diff.js.map +1 -1
  322. package/dist/core/tools/edit.d.ts +8 -0
  323. package/dist/core/tools/edit.d.ts.map +1 -1
  324. package/dist/core/tools/edit.js +53 -7
  325. package/dist/core/tools/edit.js.map +1 -1
  326. package/dist/core/tools/find.d.ts.map +1 -1
  327. package/dist/core/tools/find.js +5 -2
  328. package/dist/core/tools/find.js.map +1 -1
  329. package/dist/core/tools/grep.d.ts.map +1 -1
  330. package/dist/core/tools/grep.js +5 -2
  331. package/dist/core/tools/grep.js.map +1 -1
  332. package/dist/core/tools/index.d.ts +10 -1
  333. package/dist/core/tools/index.d.ts.map +1 -1
  334. package/dist/core/tools/index.js +3 -0
  335. package/dist/core/tools/index.js.map +1 -1
  336. package/dist/core/tools/ls.d.ts.map +1 -1
  337. package/dist/core/tools/ls.js +5 -2
  338. package/dist/core/tools/ls.js.map +1 -1
  339. package/dist/core/tools/lsp.d.ts +19 -1
  340. package/dist/core/tools/lsp.d.ts.map +1 -1
  341. package/dist/core/tools/lsp.js +163 -10
  342. package/dist/core/tools/lsp.js.map +1 -1
  343. package/dist/core/tools/path-permission.d.ts +3 -0
  344. package/dist/core/tools/path-permission.d.ts.map +1 -1
  345. package/dist/core/tools/path-permission.js +16 -4
  346. package/dist/core/tools/path-permission.js.map +1 -1
  347. package/dist/core/tools/path-utils.d.ts +27 -2
  348. package/dist/core/tools/path-utils.d.ts.map +1 -1
  349. package/dist/core/tools/path-utils.js +221 -4
  350. package/dist/core/tools/path-utils.js.map +1 -1
  351. package/dist/core/tools/read.d.ts.map +1 -1
  352. package/dist/core/tools/read.js +20 -11
  353. package/dist/core/tools/read.js.map +1 -1
  354. package/dist/core/tools/test.d.ts +54 -9
  355. package/dist/core/tools/test.d.ts.map +1 -1
  356. package/dist/core/tools/test.js +239 -19
  357. package/dist/core/tools/test.js.map +1 -1
  358. package/dist/core/tools/write.d.ts +7 -0
  359. package/dist/core/tools/write.d.ts.map +1 -1
  360. package/dist/core/tools/write.js +64 -9
  361. package/dist/core/tools/write.js.map +1 -1
  362. package/dist/core/verification-lifecycle.d.ts +77 -0
  363. package/dist/core/verification-lifecycle.d.ts.map +1 -0
  364. package/dist/core/verification-lifecycle.js +146 -0
  365. package/dist/core/verification-lifecycle.js.map +1 -0
  366. package/dist/core/workspace/artifacts.d.ts +45 -0
  367. package/dist/core/workspace/artifacts.d.ts.map +1 -0
  368. package/dist/core/workspace/artifacts.js +165 -0
  369. package/dist/core/workspace/artifacts.js.map +1 -0
  370. package/dist/core/workspace/comparison.d.ts +27 -0
  371. package/dist/core/workspace/comparison.d.ts.map +1 -0
  372. package/dist/core/workspace/comparison.js +94 -0
  373. package/dist/core/workspace/comparison.js.map +1 -0
  374. package/dist/core/workspace/git-observer.d.ts +48 -0
  375. package/dist/core/workspace/git-observer.d.ts.map +1 -0
  376. package/dist/core/workspace/git-observer.js +346 -0
  377. package/dist/core/workspace/git-observer.js.map +1 -0
  378. package/dist/core/workspace/projection.d.ts +3 -0
  379. package/dist/core/workspace/projection.d.ts.map +1 -0
  380. package/dist/core/workspace/projection.js +56 -0
  381. package/dist/core/workspace/projection.js.map +1 -0
  382. package/dist/core/workspace/state.d.ts +133 -0
  383. package/dist/core/workspace/state.d.ts.map +1 -0
  384. package/dist/core/workspace/state.js +96 -0
  385. package/dist/core/workspace/state.js.map +1 -0
  386. package/dist/extensions/llama/client.d.ts +6 -0
  387. package/dist/extensions/llama/client.d.ts.map +1 -1
  388. package/dist/extensions/llama/client.js +7 -0
  389. package/dist/extensions/llama/client.js.map +1 -1
  390. package/dist/extensions/llama/provider.d.ts +3 -1
  391. package/dist/extensions/llama/provider.d.ts.map +1 -1
  392. package/dist/extensions/llama/provider.js +27 -6
  393. package/dist/extensions/llama/provider.js.map +1 -1
  394. package/dist/index.d.ts +4 -1
  395. package/dist/index.d.ts.map +1 -1
  396. package/dist/index.js +3 -0
  397. package/dist/index.js.map +1 -1
  398. package/dist/main.d.ts +3 -1
  399. package/dist/main.d.ts.map +1 -1
  400. package/dist/main.js +41 -9
  401. package/dist/main.js.map +1 -1
  402. package/dist/modes/acp/server.d.ts +55 -0
  403. package/dist/modes/acp/server.d.ts.map +1 -0
  404. package/dist/modes/acp/server.js +244 -0
  405. package/dist/modes/acp/server.js.map +1 -0
  406. package/dist/modes/acp/translate.d.ts +37 -0
  407. package/dist/modes/acp/translate.d.ts.map +1 -0
  408. package/dist/modes/acp/translate.js +50 -0
  409. package/dist/modes/acp/translate.js.map +1 -0
  410. package/dist/modes/index.d.ts +1 -0
  411. package/dist/modes/index.d.ts.map +1 -1
  412. package/dist/modes/index.js +1 -0
  413. package/dist/modes/index.js.map +1 -1
  414. package/dist/modes/interactive/components/assistant-message.d.ts +29 -0
  415. package/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
  416. package/dist/modes/interactive/components/assistant-message.js +182 -23
  417. package/dist/modes/interactive/components/assistant-message.js.map +1 -1
  418. package/dist/modes/interactive/components/bash-execution.d.ts +7 -0
  419. package/dist/modes/interactive/components/bash-execution.d.ts.map +1 -1
  420. package/dist/modes/interactive/components/bash-execution.js +20 -0
  421. package/dist/modes/interactive/components/bash-execution.js.map +1 -1
  422. package/dist/modes/interactive/components/custom-editor.d.ts +9 -4
  423. package/dist/modes/interactive/components/custom-editor.d.ts.map +1 -1
  424. package/dist/modes/interactive/components/custom-editor.js +20 -9
  425. package/dist/modes/interactive/components/custom-editor.js.map +1 -1
  426. package/dist/modes/interactive/components/extension-selector.d.ts +7 -1
  427. package/dist/modes/interactive/components/extension-selector.d.ts.map +1 -1
  428. package/dist/modes/interactive/components/extension-selector.js +7 -3
  429. package/dist/modes/interactive/components/extension-selector.js.map +1 -1
  430. package/dist/modes/interactive/components/footer.d.ts +19 -0
  431. package/dist/modes/interactive/components/footer.d.ts.map +1 -1
  432. package/dist/modes/interactive/components/footer.js +70 -10
  433. package/dist/modes/interactive/components/footer.js.map +1 -1
  434. package/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
  435. package/dist/modes/interactive/components/model-selector.js +6 -6
  436. package/dist/modes/interactive/components/model-selector.js.map +1 -1
  437. package/dist/modes/interactive/components/permission-preview.d.ts +15 -0
  438. package/dist/modes/interactive/components/permission-preview.d.ts.map +1 -0
  439. package/dist/modes/interactive/components/permission-preview.js +33 -0
  440. package/dist/modes/interactive/components/permission-preview.js.map +1 -0
  441. package/dist/modes/interactive/components/session-selector.d.ts.map +1 -1
  442. package/dist/modes/interactive/components/session-selector.js +2 -2
  443. package/dist/modes/interactive/components/session-selector.js.map +1 -1
  444. package/dist/modes/interactive/components/settings-selector.d.ts +2 -0
  445. package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  446. package/dist/modes/interactive/components/settings-selector.js +10 -0
  447. package/dist/modes/interactive/components/settings-selector.js.map +1 -1
  448. package/dist/modes/interactive/components/tool-execution.d.ts +23 -1
  449. package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  450. package/dist/modes/interactive/components/tool-execution.js +64 -10
  451. package/dist/modes/interactive/components/tool-execution.js.map +1 -1
  452. package/dist/modes/interactive/interactive-mode.d.ts +34 -0
  453. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  454. package/dist/modes/interactive/interactive-mode.js +231 -26
  455. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  456. package/dist/modes/interactive/theme/theme.d.ts +12 -0
  457. package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
  458. package/dist/modes/interactive/theme/theme.js +15 -5
  459. package/dist/modes/interactive/theme/theme.js.map +1 -1
  460. package/dist/modes/print-mode.d.ts.map +1 -1
  461. package/dist/modes/print-mode.js +27 -12
  462. package/dist/modes/print-mode.js.map +1 -1
  463. package/dist/modes/rpc/rpc-client.d.ts +7 -0
  464. package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
  465. package/dist/modes/rpc/rpc-client.js +7 -0
  466. package/dist/modes/rpc/rpc-client.js.map +1 -1
  467. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  468. package/dist/modes/rpc/rpc-mode.js +3 -0
  469. package/dist/modes/rpc/rpc-mode.js.map +1 -1
  470. package/dist/modes/rpc/rpc-types.d.ts +12 -0
  471. package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
  472. package/dist/modes/rpc/rpc-types.js.map +1 -1
  473. package/dist/package-manager-cli.d.ts.map +1 -1
  474. package/dist/package-manager-cli.js +0 -6
  475. package/dist/package-manager-cli.js.map +1 -1
  476. package/dist/utils/shell.d.ts.map +1 -1
  477. package/dist/utils/shell.js +6 -4
  478. package/dist/utils/shell.js.map +1 -1
  479. package/dist/utils/version-check.d.ts.map +1 -1
  480. package/dist/utils/version-check.js +6 -4
  481. package/dist/utils/version-check.js.map +1 -1
  482. package/docs/compaction.md +4 -0
  483. package/docs/custom-provider.md +7 -7
  484. package/docs/environment-variables.md +1 -1
  485. package/docs/extensions.md +18 -0
  486. package/docs/keybindings.md +1 -1
  487. package/docs/quickstart.md +1 -1
  488. package/docs/rpc.md +23 -0
  489. package/docs/sdk.md +25 -0
  490. package/docs/settings.md +12 -4
  491. package/docs/terminal-setup.md +12 -0
  492. package/docs/usage.md +4 -3
  493. package/examples/extensions/built-in-tool-renderer.ts +8 -4
  494. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  495. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  496. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  497. package/examples/extensions/git-checkpoint.ts +24 -36
  498. package/examples/extensions/gondolin/package-lock.json +2 -2
  499. package/examples/extensions/gondolin/package.json +1 -1
  500. package/examples/extensions/minimal-mode.ts +2 -2
  501. package/examples/extensions/permission-gate.ts +1 -1
  502. package/examples/extensions/plan-mode/index.ts +1 -1
  503. package/examples/extensions/rpc-demo.ts +1 -1
  504. package/examples/extensions/sandbox/package-lock.json +2 -2
  505. package/examples/extensions/sandbox/package.json +1 -1
  506. package/examples/extensions/with-deps/package-lock.json +2 -2
  507. package/examples/extensions/with-deps/package.json +1 -1
  508. package/npm-shrinkwrap.json +194 -46
  509. package/package.json +14 -12
  510. package/dist/server/create-harness.d.ts +0 -26
  511. package/dist/server/create-harness.d.ts.map +0 -1
  512. package/dist/server/create-harness.js +0 -107
  513. package/dist/server/create-harness.js.map +0 -1
@@ -0,0 +1,33 @@
1
+ import { type FetchLike } from "./discover.ts";
2
+ export declare class TokenEndpointError extends Error {
3
+ /** The OAuth `error` code from the response body, when the server sent one (e.g. invalid_grant). */
4
+ code: string | undefined;
5
+ constructor(message: string, code: string | undefined);
6
+ }
7
+ /** The endpoint could not be reached at all — a path problem, not a server refusal. */
8
+ export declare class TokenEndpointUnreachableError extends TokenEndpointError {
9
+ }
10
+ export interface TokenResponse {
11
+ accessToken: string;
12
+ /** Absent when the server did not rotate; callers keep their previous refresh token then. */
13
+ refreshToken: string | undefined;
14
+ expiresInSeconds: number;
15
+ }
16
+ interface TokenEndpointOptions {
17
+ tokenEndpoint: URL;
18
+ clientId: string;
19
+ clientSecret?: string;
20
+ /** The MCP resource indicator; pins the issued token to this server (RFC 8707). */
21
+ resource?: string;
22
+ fetch?: FetchLike;
23
+ }
24
+ export declare function exchangeAuthorizationCode(options: TokenEndpointOptions & {
25
+ code: string;
26
+ verifier: string;
27
+ redirectUri: string;
28
+ }): Promise<TokenResponse>;
29
+ export declare function refreshAccessToken(options: TokenEndpointOptions & {
30
+ refreshToken: string;
31
+ }): Promise<TokenResponse>;
32
+ export {};
33
+ //# sourceMappingURL=token-client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"token-client.d.ts","sourceRoot":"","sources":["../../../../src/core/mcp/oauth/token-client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,SAAS,EAAqB,MAAM,eAAe,CAAC;AAElE,qBAAa,kBAAmB,SAAQ,KAAK;IAC5C,oGAAoG;IACpG,IAAI,EAAE,MAAM,GAAG,SAAS,CAAC;IAEzB,YAAY,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,SAAS,EAGpD;CACD;AAED,yFAAuF;AACvF,qBAAa,6BAA8B,SAAQ,kBAAkB;CAAG;AAExE,MAAM,WAAW,aAAa;IAC7B,WAAW,EAAE,MAAM,CAAC;IACpB,6FAA6F;IAC7F,YAAY,EAAE,MAAM,GAAG,SAAS,CAAC;IACjC,gBAAgB,EAAE,MAAM,CAAC;CACzB;AAED,UAAU,oBAAoB;IAC7B,aAAa,EAAE,GAAG,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,mFAAmF;IACnF,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,SAAS,CAAC;CAClB;AA4CD,wBAAgB,yBAAyB,CACxC,OAAO,EAAE,oBAAoB,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,GACrF,OAAO,CAAC,aAAa,CAAC,CAOxB;AAED,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,oBAAoB,GAAG;IAAE,YAAY,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAAC,aAAa,CAAC,CAEnH","sourcesContent":["import { type FetchLike, oauthErrorMessage } from \"./discover.ts\";\n\nexport class TokenEndpointError extends Error {\n\t/** The OAuth `error` code from the response body, when the server sent one (e.g. invalid_grant). */\n\tcode: string | undefined;\n\n\tconstructor(message: string, code: string | undefined) {\n\t\tsuper(message);\n\t\tthis.code = code;\n\t}\n}\n\n/** The endpoint could not be reached at all — a path problem, not a server refusal. */\nexport class TokenEndpointUnreachableError extends TokenEndpointError {}\n\nexport interface TokenResponse {\n\taccessToken: string;\n\t/** Absent when the server did not rotate; callers keep their previous refresh token then. */\n\trefreshToken: string | undefined;\n\texpiresInSeconds: number;\n}\n\ninterface TokenEndpointOptions {\n\ttokenEndpoint: URL;\n\tclientId: string;\n\tclientSecret?: string;\n\t/** The MCP resource indicator; pins the issued token to this server (RFC 8707). */\n\tresource?: string;\n\tfetch?: FetchLike;\n}\n\nasync function postTokenForm(options: TokenEndpointOptions, grant: Record<string, string>): Promise<TokenResponse> {\n\tconst fetch = options.fetch ?? globalThis.fetch;\n\tconst body = new URLSearchParams({ client_id: options.clientId, ...grant });\n\tif (options.clientSecret) body.set(\"client_secret\", options.clientSecret);\n\tif (options.resource) body.set(\"resource\", options.resource);\n\n\tlet response: Response;\n\ttry {\n\t\tresponse = await fetch(options.tokenEndpoint, {\n\t\t\tmethod: \"POST\",\n\t\t\theaders: { \"content-type\": \"application/x-www-form-urlencoded\" },\n\t\t\tbody,\n\t\t});\n\t} catch (error) {\n\t\tthrow new TokenEndpointUnreachableError(\n\t\t\t`token endpoint unreachable at ${options.tokenEndpoint}: ${oauthErrorMessage(error)}`,\n\t\t\tundefined,\n\t\t);\n\t}\n\n\tconst text = await response.text();\n\tlet parsed: Record<string, unknown> = {};\n\ttry {\n\t\tconst value: unknown = JSON.parse(text);\n\t\tif (typeof value === \"object\" && value !== null) parsed = value as Record<string, unknown>;\n\t} catch {\n\t\t// A non-JSON error body still lands in the status branch below.\n\t}\n\tif (!response.ok) {\n\t\tconst code = typeof parsed.error === \"string\" ? parsed.error : undefined;\n\t\tthrow new TokenEndpointError(`token endpoint answered HTTP ${response.status}${code ? ` (${code})` : \"\"}`, code);\n\t}\n\tif (typeof parsed.access_token !== \"string\" || typeof parsed.expires_in !== \"number\") {\n\t\tthrow new TokenEndpointError(\"token endpoint response is missing access_token or expires_in\", undefined);\n\t}\n\treturn {\n\t\taccessToken: parsed.access_token,\n\t\trefreshToken: typeof parsed.refresh_token === \"string\" ? parsed.refresh_token : undefined,\n\t\texpiresInSeconds: parsed.expires_in,\n\t};\n}\n\nexport function exchangeAuthorizationCode(\n\toptions: TokenEndpointOptions & { code: string; verifier: string; redirectUri: string },\n): Promise<TokenResponse> {\n\treturn postTokenForm(options, {\n\t\tgrant_type: \"authorization_code\",\n\t\tcode: options.code,\n\t\tcode_verifier: options.verifier,\n\t\tredirect_uri: options.redirectUri,\n\t});\n}\n\nexport function refreshAccessToken(options: TokenEndpointOptions & { refreshToken: string }): Promise<TokenResponse> {\n\treturn postTokenForm(options, { grant_type: \"refresh_token\", refresh_token: options.refreshToken });\n}\n"]}
@@ -0,0 +1,65 @@
1
+ import { oauthErrorMessage } from "./discover.js";
2
+ export class TokenEndpointError extends Error {
3
+ /** The OAuth `error` code from the response body, when the server sent one (e.g. invalid_grant). */
4
+ code;
5
+ constructor(message, code) {
6
+ super(message);
7
+ this.code = code;
8
+ }
9
+ }
10
+ /** The endpoint could not be reached at all — a path problem, not a server refusal. */
11
+ export class TokenEndpointUnreachableError extends TokenEndpointError {
12
+ }
13
+ async function postTokenForm(options, grant) {
14
+ const fetch = options.fetch ?? globalThis.fetch;
15
+ const body = new URLSearchParams({ client_id: options.clientId, ...grant });
16
+ if (options.clientSecret)
17
+ body.set("client_secret", options.clientSecret);
18
+ if (options.resource)
19
+ body.set("resource", options.resource);
20
+ let response;
21
+ try {
22
+ response = await fetch(options.tokenEndpoint, {
23
+ method: "POST",
24
+ headers: { "content-type": "application/x-www-form-urlencoded" },
25
+ body,
26
+ });
27
+ }
28
+ catch (error) {
29
+ throw new TokenEndpointUnreachableError(`token endpoint unreachable at ${options.tokenEndpoint}: ${oauthErrorMessage(error)}`, undefined);
30
+ }
31
+ const text = await response.text();
32
+ let parsed = {};
33
+ try {
34
+ const value = JSON.parse(text);
35
+ if (typeof value === "object" && value !== null)
36
+ parsed = value;
37
+ }
38
+ catch {
39
+ // A non-JSON error body still lands in the status branch below.
40
+ }
41
+ if (!response.ok) {
42
+ const code = typeof parsed.error === "string" ? parsed.error : undefined;
43
+ throw new TokenEndpointError(`token endpoint answered HTTP ${response.status}${code ? ` (${code})` : ""}`, code);
44
+ }
45
+ if (typeof parsed.access_token !== "string" || typeof parsed.expires_in !== "number") {
46
+ throw new TokenEndpointError("token endpoint response is missing access_token or expires_in", undefined);
47
+ }
48
+ return {
49
+ accessToken: parsed.access_token,
50
+ refreshToken: typeof parsed.refresh_token === "string" ? parsed.refresh_token : undefined,
51
+ expiresInSeconds: parsed.expires_in,
52
+ };
53
+ }
54
+ export function exchangeAuthorizationCode(options) {
55
+ return postTokenForm(options, {
56
+ grant_type: "authorization_code",
57
+ code: options.code,
58
+ code_verifier: options.verifier,
59
+ redirect_uri: options.redirectUri,
60
+ });
61
+ }
62
+ export function refreshAccessToken(options) {
63
+ return postTokenForm(options, { grant_type: "refresh_token", refresh_token: options.refreshToken });
64
+ }
65
+ //# sourceMappingURL=token-client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"token-client.js","sourceRoot":"","sources":["../../../../src/core/mcp/oauth/token-client.ts"],"names":[],"mappings":"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","sourcesContent":["import { type FetchLike, oauthErrorMessage } from \"./discover.ts\";\n\nexport class TokenEndpointError extends Error {\n\t/** The OAuth `error` code from the response body, when the server sent one (e.g. invalid_grant). */\n\tcode: string | undefined;\n\n\tconstructor(message: string, code: string | undefined) {\n\t\tsuper(message);\n\t\tthis.code = code;\n\t}\n}\n\n/** The endpoint could not be reached at all — a path problem, not a server refusal. */\nexport class TokenEndpointUnreachableError extends TokenEndpointError {}\n\nexport interface TokenResponse {\n\taccessToken: string;\n\t/** Absent when the server did not rotate; callers keep their previous refresh token then. */\n\trefreshToken: string | undefined;\n\texpiresInSeconds: number;\n}\n\ninterface TokenEndpointOptions {\n\ttokenEndpoint: URL;\n\tclientId: string;\n\tclientSecret?: string;\n\t/** The MCP resource indicator; pins the issued token to this server (RFC 8707). */\n\tresource?: string;\n\tfetch?: FetchLike;\n}\n\nasync function postTokenForm(options: TokenEndpointOptions, grant: Record<string, string>): Promise<TokenResponse> {\n\tconst fetch = options.fetch ?? globalThis.fetch;\n\tconst body = new URLSearchParams({ client_id: options.clientId, ...grant });\n\tif (options.clientSecret) body.set(\"client_secret\", options.clientSecret);\n\tif (options.resource) body.set(\"resource\", options.resource);\n\n\tlet response: Response;\n\ttry {\n\t\tresponse = await fetch(options.tokenEndpoint, {\n\t\t\tmethod: \"POST\",\n\t\t\theaders: { \"content-type\": \"application/x-www-form-urlencoded\" },\n\t\t\tbody,\n\t\t});\n\t} catch (error) {\n\t\tthrow new TokenEndpointUnreachableError(\n\t\t\t`token endpoint unreachable at ${options.tokenEndpoint}: ${oauthErrorMessage(error)}`,\n\t\t\tundefined,\n\t\t);\n\t}\n\n\tconst text = await response.text();\n\tlet parsed: Record<string, unknown> = {};\n\ttry {\n\t\tconst value: unknown = JSON.parse(text);\n\t\tif (typeof value === \"object\" && value !== null) parsed = value as Record<string, unknown>;\n\t} catch {\n\t\t// A non-JSON error body still lands in the status branch below.\n\t}\n\tif (!response.ok) {\n\t\tconst code = typeof parsed.error === \"string\" ? parsed.error : undefined;\n\t\tthrow new TokenEndpointError(`token endpoint answered HTTP ${response.status}${code ? ` (${code})` : \"\"}`, code);\n\t}\n\tif (typeof parsed.access_token !== \"string\" || typeof parsed.expires_in !== \"number\") {\n\t\tthrow new TokenEndpointError(\"token endpoint response is missing access_token or expires_in\", undefined);\n\t}\n\treturn {\n\t\taccessToken: parsed.access_token,\n\t\trefreshToken: typeof parsed.refresh_token === \"string\" ? parsed.refresh_token : undefined,\n\t\texpiresInSeconds: parsed.expires_in,\n\t};\n}\n\nexport function exchangeAuthorizationCode(\n\toptions: TokenEndpointOptions & { code: string; verifier: string; redirectUri: string },\n): Promise<TokenResponse> {\n\treturn postTokenForm(options, {\n\t\tgrant_type: \"authorization_code\",\n\t\tcode: options.code,\n\t\tcode_verifier: options.verifier,\n\t\tredirect_uri: options.redirectUri,\n\t});\n}\n\nexport function refreshAccessToken(options: TokenEndpointOptions & { refreshToken: string }): Promise<TokenResponse> {\n\treturn postTokenForm(options, { grant_type: \"refresh_token\", refresh_token: options.refreshToken });\n}\n"]}
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Assembles the MCP subsystem for one session, or returns undefined when nothing is
3
+ * configured.
4
+ *
5
+ * The undefined case is the point. A session with no `.mcp.json` builds no cache, no
6
+ * manager, and no tool, so its registry and static prompt prefix are identical to a
7
+ * build from before this subsystem existed.
8
+ */
9
+ import type { McpToolOptions } from "./mcp-tool.ts";
10
+ import type { McpConfigDiagnostic, McpConnector } from "./types.ts";
11
+ export interface McpRuntime extends McpToolOptions {
12
+ diagnostics: readonly McpConfigDiagnostic[];
13
+ /** Connect `eager` servers and cache their tools, so they are discoverable cold. */
14
+ warm(): Promise<void>;
15
+ close(): Promise<void>;
16
+ }
17
+ export declare function createMcpRuntime(cwd: string, connector?: McpConnector, options?: {
18
+ projectTrusted?: boolean;
19
+ }): McpRuntime | undefined;
20
+ //# sourceMappingURL=runtime.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../../../src/core/mcp/runtime.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAKH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAGpD,OAAO,KAAK,EAAE,mBAAmB,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAEpE,MAAM,WAAW,UAAW,SAAQ,cAAc;IACjD,WAAW,EAAE,SAAS,mBAAmB,EAAE,CAAC;IAC5C,oFAAoF;IACpF,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACtB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACvB;AAED,wBAAgB,gBAAgB,CAC/B,GAAG,EAAE,MAAM,EACX,SAAS,GAAE,YAA+B,EAC1C,OAAO,GAAE;IAAE,cAAc,CAAC,EAAE,OAAO,CAAA;CAAO,GACxC,UAAU,GAAG,SAAS,CAwBxB","sourcesContent":["/**\n * Assembles the MCP subsystem for one session, or returns undefined when nothing is\n * configured.\n *\n * The undefined case is the point. A session with no `.mcp.json` builds no cache, no\n * manager, and no tool, so its registry and static prompt prefix are identical to a\n * build from before this subsystem existed.\n */\n\nimport { join } from \"node:path\";\nimport { loadMcpConfig, PROJECT_CONFIG_FILENAME } from \"./config.ts\";\nimport { connectMcpServer } from \"./connector.ts\";\nimport type { McpToolOptions } from \"./mcp-tool.ts\";\nimport { McpMetadataCache } from \"./metadata-cache.ts\";\nimport { McpServerManager } from \"./server-manager.ts\";\nimport type { McpConfigDiagnostic, McpConnector } from \"./types.ts\";\n\nexport interface McpRuntime extends McpToolOptions {\n\tdiagnostics: readonly McpConfigDiagnostic[];\n\t/** Connect `eager` servers and cache their tools, so they are discoverable cold. */\n\twarm(): Promise<void>;\n\tclose(): Promise<void>;\n}\n\nexport function createMcpRuntime(\n\tcwd: string,\n\tconnector: McpConnector = connectMcpServer,\n\toptions: { projectTrusted?: boolean } = {},\n): McpRuntime | undefined {\n\tconst { servers, diagnostics } = loadMcpConfig({\n\t\tprojectPath: options.projectTrusted === false ? undefined : join(cwd, PROJECT_CONFIG_FILENAME),\n\t});\n\tif (servers.size === 0) return undefined;\n\n\tconst cache = new McpMetadataCache();\n\t// A server removed from config stops appearing in search immediately, rather than\n\t// lingering until something happens to overwrite its cache entry.\n\tcache.prune([...servers.values()]);\n\n\tconst manager = new McpServerManager({ servers, connector });\n\n\treturn {\n\t\tservers,\n\t\tcache,\n\t\tmanager,\n\t\tdiagnostics,\n\t\twarm: async () => {\n\t\t\tawait manager.warmEagerServers((server, tools) => cache.set(server, tools));\n\t\t\tcache.save();\n\t\t},\n\t\tclose: () => manager.closeAll(),\n\t};\n}\n"]}
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Assembles the MCP subsystem for one session, or returns undefined when nothing is
3
+ * configured.
4
+ *
5
+ * The undefined case is the point. A session with no `.mcp.json` builds no cache, no
6
+ * manager, and no tool, so its registry and static prompt prefix are identical to a
7
+ * build from before this subsystem existed.
8
+ */
9
+ import { join } from "node:path";
10
+ import { loadMcpConfig, PROJECT_CONFIG_FILENAME } from "./config.js";
11
+ import { connectMcpServer } from "./connector.js";
12
+ import { McpMetadataCache } from "./metadata-cache.js";
13
+ import { McpServerManager } from "./server-manager.js";
14
+ export function createMcpRuntime(cwd, connector = connectMcpServer, options = {}) {
15
+ const { servers, diagnostics } = loadMcpConfig({
16
+ projectPath: options.projectTrusted === false ? undefined : join(cwd, PROJECT_CONFIG_FILENAME),
17
+ });
18
+ if (servers.size === 0)
19
+ return undefined;
20
+ const cache = new McpMetadataCache();
21
+ // A server removed from config stops appearing in search immediately, rather than
22
+ // lingering until something happens to overwrite its cache entry.
23
+ cache.prune([...servers.values()]);
24
+ const manager = new McpServerManager({ servers, connector });
25
+ return {
26
+ servers,
27
+ cache,
28
+ manager,
29
+ diagnostics,
30
+ warm: async () => {
31
+ await manager.warmEagerServers((server, tools) => cache.set(server, tools));
32
+ cache.save();
33
+ },
34
+ close: () => manager.closeAll(),
35
+ };
36
+ }
37
+ //# sourceMappingURL=runtime.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runtime.js","sourceRoot":"","sources":["../../../src/core/mcp/runtime.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,aAAa,EAAE,uBAAuB,EAAE,MAAM,aAAa,CAAC;AACrE,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAElD,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AACvD,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAUvD,MAAM,UAAU,gBAAgB,CAC/B,GAAW,EACX,SAAS,GAAiB,gBAAgB,EAC1C,OAAO,GAAiC,EAAE,EACjB;IACzB,MAAM,EAAE,OAAO,EAAE,WAAW,EAAE,GAAG,aAAa,CAAC;QAC9C,WAAW,EAAE,OAAO,CAAC,cAAc,KAAK,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,uBAAuB,CAAC;KAC9F,CAAC,CAAC;IACH,IAAI,OAAO,CAAC,IAAI,KAAK,CAAC;QAAE,OAAO,SAAS,CAAC;IAEzC,MAAM,KAAK,GAAG,IAAI,gBAAgB,EAAE,CAAC;IACrC,kFAAkF;IAClF,kEAAkE;IAClE,KAAK,CAAC,KAAK,CAAC,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;IAEnC,MAAM,OAAO,GAAG,IAAI,gBAAgB,CAAC,EAAE,OAAO,EAAE,SAAS,EAAE,CAAC,CAAC;IAE7D,OAAO;QACN,OAAO;QACP,KAAK;QACL,OAAO;QACP,WAAW;QACX,IAAI,EAAE,KAAK,IAAI,EAAE,CAAC;YACjB,MAAM,OAAO,CAAC,gBAAgB,CAAC,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC,CAAC;YAC5E,KAAK,CAAC,IAAI,EAAE,CAAC;QAAA,CACb;QACD,KAAK,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,QAAQ,EAAE;KAC/B,CAAC;AAAA,CACF","sourcesContent":["/**\n * Assembles the MCP subsystem for one session, or returns undefined when nothing is\n * configured.\n *\n * The undefined case is the point. A session with no `.mcp.json` builds no cache, no\n * manager, and no tool, so its registry and static prompt prefix are identical to a\n * build from before this subsystem existed.\n */\n\nimport { join } from \"node:path\";\nimport { loadMcpConfig, PROJECT_CONFIG_FILENAME } from \"./config.ts\";\nimport { connectMcpServer } from \"./connector.ts\";\nimport type { McpToolOptions } from \"./mcp-tool.ts\";\nimport { McpMetadataCache } from \"./metadata-cache.ts\";\nimport { McpServerManager } from \"./server-manager.ts\";\nimport type { McpConfigDiagnostic, McpConnector } from \"./types.ts\";\n\nexport interface McpRuntime extends McpToolOptions {\n\tdiagnostics: readonly McpConfigDiagnostic[];\n\t/** Connect `eager` servers and cache their tools, so they are discoverable cold. */\n\twarm(): Promise<void>;\n\tclose(): Promise<void>;\n}\n\nexport function createMcpRuntime(\n\tcwd: string,\n\tconnector: McpConnector = connectMcpServer,\n\toptions: { projectTrusted?: boolean } = {},\n): McpRuntime | undefined {\n\tconst { servers, diagnostics } = loadMcpConfig({\n\t\tprojectPath: options.projectTrusted === false ? undefined : join(cwd, PROJECT_CONFIG_FILENAME),\n\t});\n\tif (servers.size === 0) return undefined;\n\n\tconst cache = new McpMetadataCache();\n\t// A server removed from config stops appearing in search immediately, rather than\n\t// lingering until something happens to overwrite its cache entry.\n\tcache.prune([...servers.values()]);\n\n\tconst manager = new McpServerManager({ servers, connector });\n\n\treturn {\n\t\tservers,\n\t\tcache,\n\t\tmanager,\n\t\tdiagnostics,\n\t\twarm: async () => {\n\t\t\tawait manager.warmEagerServers((server, tools) => cache.set(server, tools));\n\t\t\tcache.save();\n\t\t},\n\t\tclose: () => manager.closeAll(),\n\t};\n}\n"]}
@@ -0,0 +1,22 @@
1
+ /**
2
+ * The `mcp` tool's parameter schema, and the single source of its param type.
3
+ *
4
+ * It lives apart from the tool itself so the contract (`contract.ts`) can type its
5
+ * permission grammar against the same shape without importing the tool, which would
6
+ * be a cycle: the tool needs the contract.
7
+ */
8
+ import { type Static, Type } from "typebox";
9
+ export declare const mcpToolSchema: Type.TObject<{
10
+ search: Type.TOptional<Type.TString>;
11
+ server: Type.TOptional<Type.TString>;
12
+ describe: Type.TOptional<Type.TString>;
13
+ tool: Type.TOptional<Type.TString>;
14
+ args: Type.TOptional<Type.TUnknown>;
15
+ }>;
16
+ export type McpToolParams = Static<typeof mcpToolSchema>;
17
+ export interface McpToolDetails {
18
+ action: "search" | "describe" | "list" | "call";
19
+ server: string | undefined;
20
+ tool: string | undefined;
21
+ }
22
+ //# sourceMappingURL=schema.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../../src/core/mcp/schema.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,KAAK,MAAM,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AAE5C,eAAO,MAAM,aAAa;;;;;;EAmBxB,CAAC;AAEH,MAAM,MAAM,aAAa,GAAG,MAAM,CAAC,OAAO,aAAa,CAAC,CAAC;AAEzD,MAAM,WAAW,cAAc;IAC9B,MAAM,EAAE,QAAQ,GAAG,UAAU,GAAG,MAAM,GAAG,MAAM,CAAC;IAChD,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3B,IAAI,EAAE,MAAM,GAAG,SAAS,CAAC;CACzB","sourcesContent":["/**\n * The `mcp` tool's parameter schema, and the single source of its param type.\n *\n * It lives apart from the tool itself so the contract (`contract.ts`) can type its\n * permission grammar against the same shape without importing the tool, which would\n * be a cycle: the tool needs the contract.\n */\n\nimport { type Static, Type } from \"typebox\";\n\nexport const mcpToolSchema = Type.Object({\n\tsearch: Type.Optional(\n\t\tType.String({\n\t\t\tdescription:\n\t\t\t\t\"Find tools across configured MCP servers by keyword. Reads a local cache, so it works without starting a server.\",\n\t\t}),\n\t),\n\tserver: Type.Optional(\n\t\tType.String({ description: \"Restrict a search to one server, or list that server's tools when used alone.\" }),\n\t),\n\tdescribe: Type.Optional(\n\t\tType.String({ description: 'Show one tool\\'s full input schema. Qualified as \"<server>:<tool>\".' }),\n\t),\n\ttool: Type.Optional(\n\t\tType.String({\n\t\t\tdescription: 'The tool to call, qualified as \"<server>:<tool>\". Requires the server to be reachable.',\n\t\t}),\n\t),\n\targs: Type.Optional(Type.Unknown({ description: \"Arguments for the called tool, matching its input schema.\" })),\n});\n\nexport type McpToolParams = Static<typeof mcpToolSchema>;\n\nexport interface McpToolDetails {\n\taction: \"search\" | \"describe\" | \"list\" | \"call\";\n\tserver: string | undefined;\n\ttool: string | undefined;\n}\n"]}
@@ -0,0 +1,20 @@
1
+ /**
2
+ * The `mcp` tool's parameter schema, and the single source of its param type.
3
+ *
4
+ * It lives apart from the tool itself so the contract (`contract.ts`) can type its
5
+ * permission grammar against the same shape without importing the tool, which would
6
+ * be a cycle: the tool needs the contract.
7
+ */
8
+ import { Type } from "typebox";
9
+ export const mcpToolSchema = Type.Object({
10
+ search: Type.Optional(Type.String({
11
+ description: "Find tools across configured MCP servers by keyword. Reads a local cache, so it works without starting a server.",
12
+ })),
13
+ server: Type.Optional(Type.String({ description: "Restrict a search to one server, or list that server's tools when used alone." })),
14
+ describe: Type.Optional(Type.String({ description: 'Show one tool\'s full input schema. Qualified as "<server>:<tool>".' })),
15
+ tool: Type.Optional(Type.String({
16
+ description: 'The tool to call, qualified as "<server>:<tool>". Requires the server to be reachable.',
17
+ })),
18
+ args: Type.Optional(Type.Unknown({ description: "Arguments for the called tool, matching its input schema." })),
19
+ });
20
+ //# sourceMappingURL=schema.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema.js","sourceRoot":"","sources":["../../../src/core/mcp/schema.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAe,IAAI,EAAE,MAAM,SAAS,CAAC;AAE5C,MAAM,CAAC,MAAM,aAAa,GAAG,IAAI,CAAC,MAAM,CAAC;IACxC,MAAM,EAAE,IAAI,CAAC,QAAQ,CACpB,IAAI,CAAC,MAAM,CAAC;QACX,WAAW,EACV,kHAAkH;KACnH,CAAC,CACF;IACD,MAAM,EAAE,IAAI,CAAC,QAAQ,CACpB,IAAI,CAAC,MAAM,CAAC,EAAE,WAAW,EAAE,+EAA+E,EAAE,CAAC,CAC7G;IACD,QAAQ,EAAE,IAAI,CAAC,QAAQ,CACtB,IAAI,CAAC,MAAM,CAAC,EAAE,WAAW,EAAE,qEAAqE,EAAE,CAAC,CACnG;IACD,IAAI,EAAE,IAAI,CAAC,QAAQ,CAClB,IAAI,CAAC,MAAM,CAAC;QACX,WAAW,EAAE,wFAAwF;KACrG,CAAC,CACF;IACD,IAAI,EAAE,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,WAAW,EAAE,2DAA2D,EAAE,CAAC,CAAC;CAC/G,CAAC,CAAC","sourcesContent":["/**\n * The `mcp` tool's parameter schema, and the single source of its param type.\n *\n * It lives apart from the tool itself so the contract (`contract.ts`) can type its\n * permission grammar against the same shape without importing the tool, which would\n * be a cycle: the tool needs the contract.\n */\n\nimport { type Static, Type } from \"typebox\";\n\nexport const mcpToolSchema = Type.Object({\n\tsearch: Type.Optional(\n\t\tType.String({\n\t\t\tdescription:\n\t\t\t\t\"Find tools across configured MCP servers by keyword. Reads a local cache, so it works without starting a server.\",\n\t\t}),\n\t),\n\tserver: Type.Optional(\n\t\tType.String({ description: \"Restrict a search to one server, or list that server's tools when used alone.\" }),\n\t),\n\tdescribe: Type.Optional(\n\t\tType.String({ description: 'Show one tool\\'s full input schema. Qualified as \"<server>:<tool>\".' }),\n\t),\n\ttool: Type.Optional(\n\t\tType.String({\n\t\t\tdescription: 'The tool to call, qualified as \"<server>:<tool>\". Requires the server to be reachable.',\n\t\t}),\n\t),\n\targs: Type.Optional(Type.Unknown({ description: \"Arguments for the called tool, matching its input schema.\" })),\n});\n\nexport type McpToolParams = Static<typeof mcpToolSchema>;\n\nexport interface McpToolDetails {\n\taction: \"search\" | \"describe\" | \"list\" | \"call\";\n\tserver: string | undefined;\n\ttool: string | undefined;\n}\n"]}
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Owns when a server is running and nothing about how it is reached.
3
+ *
4
+ * The `McpConnection` seam is why this module has no MCP SDK import and no child
5
+ * process: lifecycle is the part with the interesting failure modes (a connect that
6
+ * never returns, a broken server retried on every call, two callers racing the same
7
+ * spawn) and all of it is testable against a fake.
8
+ */
9
+ import type { CachedTool, McpConnection, McpConnector, McpServerConfig, ServerState } from "./types.ts";
10
+ export interface McpServerManagerOptions {
11
+ servers: ReadonlyMap<string, McpServerConfig>;
12
+ connector: McpConnector;
13
+ now?: () => number;
14
+ connectTimeoutMs?: number;
15
+ }
16
+ export declare class McpServerManager {
17
+ private readonly servers;
18
+ private readonly connector;
19
+ private readonly now;
20
+ private readonly connectTimeoutMs;
21
+ private readonly entries;
22
+ constructor(options: McpServerManagerOptions);
23
+ state(name: string): ServerState;
24
+ private entry;
25
+ private connect;
26
+ private acquire;
27
+ withConnection<T>(name: string, use: (connection: McpConnection) => Promise<T>): Promise<T>;
28
+ /**
29
+ * Connect every `eager` server and cache what it publishes.
30
+ *
31
+ * This is the answer to the cold-cache problem. A lazily configured server has no
32
+ * cached tools until something calls one, so it cannot be discovered by search; a
33
+ * user who wants a server discoverable from the first turn declares it `eager`.
34
+ * Failures are swallowed on purpose, because a broken server must not stop a
35
+ * session from starting.
36
+ */
37
+ warmEagerServers(onTools: (server: McpServerConfig, tools: CachedTool[]) => void): Promise<void>;
38
+ /** Close every `ready` server whose idle window has passed, unless it is kept alive. */
39
+ sweepIdle(): Promise<void>;
40
+ private disconnect;
41
+ closeAll(): Promise<void>;
42
+ }
43
+ //# sourceMappingURL=server-manager.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server-manager.d.ts","sourceRoot":"","sources":["../../../src/core/mcp/server-manager.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,aAAa,EAAE,YAAY,EAAE,eAAe,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAYxG,MAAM,WAAW,uBAAuB;IACvC,OAAO,EAAE,WAAW,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;IAC9C,SAAS,EAAE,YAAY,CAAC;IACxB,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IACnB,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC1B;AAMD,qBAAa,gBAAgB;IAC5B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAuC;IAC/D,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAe;IACzC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAe;IACnC,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAS;IAC1C,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA4B;IAEpD,YAAY,OAAO,EAAE,uBAAuB,EAK3C;IAED,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,WAAW,CAE/B;IAED,OAAO,CAAC,KAAK;YASC,OAAO;YAiCP,OAAO;IAkBf,cAAc,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,UAAU,EAAE,aAAa,KAAK,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAOhG;IAED;;;;;;;;OAQG;IACG,gBAAgB,CAAC,OAAO,EAAE,CAAC,MAAM,EAAE,eAAe,EAAE,KAAK,EAAE,UAAU,EAAE,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAWrG;IAED,wFAAwF;IAClF,SAAS,IAAI,OAAO,CAAC,IAAI,CAAC,CAQ/B;YAEa,UAAU;IAUlB,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC,CAI9B;CACD","sourcesContent":["/**\n * Owns when a server is running and nothing about how it is reached.\n *\n * The `McpConnection` seam is why this module has no MCP SDK import and no child\n * process: lifecycle is the part with the interesting failure modes (a connect that\n * never returns, a broken server retried on every call, two callers racing the same\n * spawn) and all of it is testable against a fake.\n */\n\nimport type { CachedTool, McpConnection, McpConnector, McpServerConfig, ServerState } from \"./types.ts\";\n\nconst DEFAULT_CONNECT_TIMEOUT_MS = 30_000;\nconst FAILURE_BACKOFF_MS = 60_000;\n\ninterface Entry {\n\tstate: ServerState;\n\tconnection: McpConnection | undefined;\n\t/** Held so concurrent callers await one spawn instead of racing a second. */\n\tpending: Promise<McpConnection> | undefined;\n}\n\nexport interface McpServerManagerOptions {\n\tservers: ReadonlyMap<string, McpServerConfig>;\n\tconnector: McpConnector;\n\tnow?: () => number;\n\tconnectTimeoutMs?: number;\n}\n\nfunction isKeptAlive(server: McpServerConfig): boolean {\n\treturn server.lifecycle === \"keep-alive\" || server.lifecycle === \"lazy-keep-alive\";\n}\n\nexport class McpServerManager {\n\tprivate readonly servers: ReadonlyMap<string, McpServerConfig>;\n\tprivate readonly connector: McpConnector;\n\tprivate readonly now: () => number;\n\tprivate readonly connectTimeoutMs: number;\n\tprivate readonly entries = new Map<string, Entry>();\n\n\tconstructor(options: McpServerManagerOptions) {\n\t\tthis.servers = options.servers;\n\t\tthis.connector = options.connector;\n\t\tthis.now = options.now ?? Date.now;\n\t\tthis.connectTimeoutMs = options.connectTimeoutMs ?? DEFAULT_CONNECT_TIMEOUT_MS;\n\t}\n\n\tstate(name: string): ServerState {\n\t\treturn this.entries.get(name)?.state ?? { kind: \"disconnected\" };\n\t}\n\n\tprivate entry(name: string): Entry {\n\t\tlet entry = this.entries.get(name);\n\t\tif (!entry) {\n\t\t\tentry = { state: { kind: \"disconnected\" }, connection: undefined, pending: undefined };\n\t\t\tthis.entries.set(name, entry);\n\t\t}\n\t\treturn entry;\n\t}\n\n\tprivate async connect(server: McpServerConfig, entry: Entry): Promise<McpConnection> {\n\t\tentry.state = { kind: \"connecting\", since: this.now() };\n\n\t\tlet timer: ReturnType<typeof setTimeout> | undefined;\n\t\tconst timeout = new Promise<never>((_, reject) => {\n\t\t\ttimer = setTimeout(\n\t\t\t\t() => reject(new Error(`Connecting to MCP server \"${server.name}\" timed out`)),\n\t\t\t\tthis.connectTimeoutMs,\n\t\t\t);\n\t\t});\n\n\t\ttry {\n\t\t\tconst connection = await Promise.race([this.connector(server), timeout]);\n\t\t\tconst at = this.now();\n\t\t\tentry.connection = connection;\n\t\t\tentry.state = { kind: \"ready\", since: at, lastUsed: at };\n\t\t\treturn connection;\n\t\t} catch (error) {\n\t\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\t\tentry.connection = undefined;\n\t\t\tentry.state = {\n\t\t\t\tkind: \"failed\",\n\t\t\t\terror: message,\n\t\t\t\tfailedAt: this.now(),\n\t\t\t\tretryAfter: this.now() + FAILURE_BACKOFF_MS,\n\t\t\t};\n\t\t\tthrow error;\n\t\t} finally {\n\t\t\tif (timer) clearTimeout(timer);\n\t\t\tentry.pending = undefined;\n\t\t}\n\t}\n\n\tprivate async acquire(name: string): Promise<McpConnection> {\n\t\tconst server = this.servers.get(name);\n\t\tif (!server) throw new Error(`MCP server \"${name}\" is not configured`);\n\n\t\tconst entry = this.entry(name);\n\t\tif (entry.state.kind === \"ready\" && entry.connection) return entry.connection;\n\t\tif (entry.pending) return entry.pending;\n\n\t\t// A server that just failed is not retried until its window passes, so one\n\t\t// broken server cannot cost every call a spawn attempt.\n\t\tif (entry.state.kind === \"failed\" && this.now() < entry.state.retryAfter) {\n\t\t\tthrow new Error(entry.state.error);\n\t\t}\n\n\t\tentry.pending = this.connect(server, entry);\n\t\treturn entry.pending;\n\t}\n\n\tasync withConnection<T>(name: string, use: (connection: McpConnection) => Promise<T>): Promise<T> {\n\t\tconst connection = await this.acquire(name);\n\t\tconst result = await use(connection);\n\n\t\tconst entry = this.entry(name);\n\t\tif (entry.state.kind === \"ready\") entry.state = { ...entry.state, lastUsed: this.now() };\n\t\treturn result;\n\t}\n\n\t/**\n\t * Connect every `eager` server and cache what it publishes.\n\t *\n\t * This is the answer to the cold-cache problem. A lazily configured server has no\n\t * cached tools until something calls one, so it cannot be discovered by search; a\n\t * user who wants a server discoverable from the first turn declares it `eager`.\n\t * Failures are swallowed on purpose, because a broken server must not stop a\n\t * session from starting.\n\t */\n\tasync warmEagerServers(onTools: (server: McpServerConfig, tools: CachedTool[]) => void): Promise<void> {\n\t\tfor (const server of this.servers.values()) {\n\t\t\tif (server.lifecycle !== \"eager\") continue;\n\t\t\ttry {\n\t\t\t\tawait this.withConnection(server.name, async (connection) => {\n\t\t\t\t\tonTools(server, await connection.listTools());\n\t\t\t\t});\n\t\t\t} catch {\n\t\t\t\t// Recorded as `failed` by `connect`; the next call reports it.\n\t\t\t}\n\t\t}\n\t}\n\n\t/** Close every `ready` server whose idle window has passed, unless it is kept alive. */\n\tasync sweepIdle(): Promise<void> {\n\t\tconst at = this.now();\n\t\tfor (const [name, entry] of this.entries) {\n\t\t\tconst server = this.servers.get(name);\n\t\t\tif (!server || entry.state.kind !== \"ready\" || isKeptAlive(server)) continue;\n\t\t\tif (at - entry.state.lastUsed < server.idleTimeoutMinutes * 60_000) continue;\n\t\t\tawait this.disconnect(name, entry);\n\t\t}\n\t}\n\n\tprivate async disconnect(name: string, entry: Entry): Promise<void> {\n\t\tconst connection = entry.connection;\n\t\tentry.connection = undefined;\n\t\tentry.state = { kind: \"disconnected\" };\n\t\tif (connection) {\n\t\t\tawait connection.close().catch(() => undefined);\n\t\t}\n\t\tthis.entries.set(name, entry);\n\t}\n\n\tasync closeAll(): Promise<void> {\n\t\tfor (const [name, entry] of this.entries) {\n\t\t\tawait this.disconnect(name, entry);\n\t\t}\n\t}\n}\n"]}
@@ -0,0 +1,142 @@
1
+ /**
2
+ * Owns when a server is running and nothing about how it is reached.
3
+ *
4
+ * The `McpConnection` seam is why this module has no MCP SDK import and no child
5
+ * process: lifecycle is the part with the interesting failure modes (a connect that
6
+ * never returns, a broken server retried on every call, two callers racing the same
7
+ * spawn) and all of it is testable against a fake.
8
+ */
9
+ const DEFAULT_CONNECT_TIMEOUT_MS = 30_000;
10
+ const FAILURE_BACKOFF_MS = 60_000;
11
+ function isKeptAlive(server) {
12
+ return server.lifecycle === "keep-alive" || server.lifecycle === "lazy-keep-alive";
13
+ }
14
+ export class McpServerManager {
15
+ servers;
16
+ connector;
17
+ now;
18
+ connectTimeoutMs;
19
+ entries = new Map();
20
+ constructor(options) {
21
+ this.servers = options.servers;
22
+ this.connector = options.connector;
23
+ this.now = options.now ?? Date.now;
24
+ this.connectTimeoutMs = options.connectTimeoutMs ?? DEFAULT_CONNECT_TIMEOUT_MS;
25
+ }
26
+ state(name) {
27
+ return this.entries.get(name)?.state ?? { kind: "disconnected" };
28
+ }
29
+ entry(name) {
30
+ let entry = this.entries.get(name);
31
+ if (!entry) {
32
+ entry = { state: { kind: "disconnected" }, connection: undefined, pending: undefined };
33
+ this.entries.set(name, entry);
34
+ }
35
+ return entry;
36
+ }
37
+ async connect(server, entry) {
38
+ entry.state = { kind: "connecting", since: this.now() };
39
+ let timer;
40
+ const timeout = new Promise((_, reject) => {
41
+ timer = setTimeout(() => reject(new Error(`Connecting to MCP server "${server.name}" timed out`)), this.connectTimeoutMs);
42
+ });
43
+ try {
44
+ const connection = await Promise.race([this.connector(server), timeout]);
45
+ const at = this.now();
46
+ entry.connection = connection;
47
+ entry.state = { kind: "ready", since: at, lastUsed: at };
48
+ return connection;
49
+ }
50
+ catch (error) {
51
+ const message = error instanceof Error ? error.message : String(error);
52
+ entry.connection = undefined;
53
+ entry.state = {
54
+ kind: "failed",
55
+ error: message,
56
+ failedAt: this.now(),
57
+ retryAfter: this.now() + FAILURE_BACKOFF_MS,
58
+ };
59
+ throw error;
60
+ }
61
+ finally {
62
+ if (timer)
63
+ clearTimeout(timer);
64
+ entry.pending = undefined;
65
+ }
66
+ }
67
+ async acquire(name) {
68
+ const server = this.servers.get(name);
69
+ if (!server)
70
+ throw new Error(`MCP server "${name}" is not configured`);
71
+ const entry = this.entry(name);
72
+ if (entry.state.kind === "ready" && entry.connection)
73
+ return entry.connection;
74
+ if (entry.pending)
75
+ return entry.pending;
76
+ // A server that just failed is not retried until its window passes, so one
77
+ // broken server cannot cost every call a spawn attempt.
78
+ if (entry.state.kind === "failed" && this.now() < entry.state.retryAfter) {
79
+ throw new Error(entry.state.error);
80
+ }
81
+ entry.pending = this.connect(server, entry);
82
+ return entry.pending;
83
+ }
84
+ async withConnection(name, use) {
85
+ const connection = await this.acquire(name);
86
+ const result = await use(connection);
87
+ const entry = this.entry(name);
88
+ if (entry.state.kind === "ready")
89
+ entry.state = { ...entry.state, lastUsed: this.now() };
90
+ return result;
91
+ }
92
+ /**
93
+ * Connect every `eager` server and cache what it publishes.
94
+ *
95
+ * This is the answer to the cold-cache problem. A lazily configured server has no
96
+ * cached tools until something calls one, so it cannot be discovered by search; a
97
+ * user who wants a server discoverable from the first turn declares it `eager`.
98
+ * Failures are swallowed on purpose, because a broken server must not stop a
99
+ * session from starting.
100
+ */
101
+ async warmEagerServers(onTools) {
102
+ for (const server of this.servers.values()) {
103
+ if (server.lifecycle !== "eager")
104
+ continue;
105
+ try {
106
+ await this.withConnection(server.name, async (connection) => {
107
+ onTools(server, await connection.listTools());
108
+ });
109
+ }
110
+ catch {
111
+ // Recorded as `failed` by `connect`; the next call reports it.
112
+ }
113
+ }
114
+ }
115
+ /** Close every `ready` server whose idle window has passed, unless it is kept alive. */
116
+ async sweepIdle() {
117
+ const at = this.now();
118
+ for (const [name, entry] of this.entries) {
119
+ const server = this.servers.get(name);
120
+ if (!server || entry.state.kind !== "ready" || isKeptAlive(server))
121
+ continue;
122
+ if (at - entry.state.lastUsed < server.idleTimeoutMinutes * 60_000)
123
+ continue;
124
+ await this.disconnect(name, entry);
125
+ }
126
+ }
127
+ async disconnect(name, entry) {
128
+ const connection = entry.connection;
129
+ entry.connection = undefined;
130
+ entry.state = { kind: "disconnected" };
131
+ if (connection) {
132
+ await connection.close().catch(() => undefined);
133
+ }
134
+ this.entries.set(name, entry);
135
+ }
136
+ async closeAll() {
137
+ for (const [name, entry] of this.entries) {
138
+ await this.disconnect(name, entry);
139
+ }
140
+ }
141
+ }
142
+ //# sourceMappingURL=server-manager.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server-manager.js","sourceRoot":"","sources":["../../../src/core/mcp/server-manager.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Owns when a server is running and nothing about how it is reached.\n *\n * The `McpConnection` seam is why this module has no MCP SDK import and no child\n * process: lifecycle is the part with the interesting failure modes (a connect that\n * never returns, a broken server retried on every call, two callers racing the same\n * spawn) and all of it is testable against a fake.\n */\n\nimport type { CachedTool, McpConnection, McpConnector, McpServerConfig, ServerState } from \"./types.ts\";\n\nconst DEFAULT_CONNECT_TIMEOUT_MS = 30_000;\nconst FAILURE_BACKOFF_MS = 60_000;\n\ninterface Entry {\n\tstate: ServerState;\n\tconnection: McpConnection | undefined;\n\t/** Held so concurrent callers await one spawn instead of racing a second. */\n\tpending: Promise<McpConnection> | undefined;\n}\n\nexport interface McpServerManagerOptions {\n\tservers: ReadonlyMap<string, McpServerConfig>;\n\tconnector: McpConnector;\n\tnow?: () => number;\n\tconnectTimeoutMs?: number;\n}\n\nfunction isKeptAlive(server: McpServerConfig): boolean {\n\treturn server.lifecycle === \"keep-alive\" || server.lifecycle === \"lazy-keep-alive\";\n}\n\nexport class McpServerManager {\n\tprivate readonly servers: ReadonlyMap<string, McpServerConfig>;\n\tprivate readonly connector: McpConnector;\n\tprivate readonly now: () => number;\n\tprivate readonly connectTimeoutMs: number;\n\tprivate readonly entries = new Map<string, Entry>();\n\n\tconstructor(options: McpServerManagerOptions) {\n\t\tthis.servers = options.servers;\n\t\tthis.connector = options.connector;\n\t\tthis.now = options.now ?? Date.now;\n\t\tthis.connectTimeoutMs = options.connectTimeoutMs ?? DEFAULT_CONNECT_TIMEOUT_MS;\n\t}\n\n\tstate(name: string): ServerState {\n\t\treturn this.entries.get(name)?.state ?? { kind: \"disconnected\" };\n\t}\n\n\tprivate entry(name: string): Entry {\n\t\tlet entry = this.entries.get(name);\n\t\tif (!entry) {\n\t\t\tentry = { state: { kind: \"disconnected\" }, connection: undefined, pending: undefined };\n\t\t\tthis.entries.set(name, entry);\n\t\t}\n\t\treturn entry;\n\t}\n\n\tprivate async connect(server: McpServerConfig, entry: Entry): Promise<McpConnection> {\n\t\tentry.state = { kind: \"connecting\", since: this.now() };\n\n\t\tlet timer: ReturnType<typeof setTimeout> | undefined;\n\t\tconst timeout = new Promise<never>((_, reject) => {\n\t\t\ttimer = setTimeout(\n\t\t\t\t() => reject(new Error(`Connecting to MCP server \"${server.name}\" timed out`)),\n\t\t\t\tthis.connectTimeoutMs,\n\t\t\t);\n\t\t});\n\n\t\ttry {\n\t\t\tconst connection = await Promise.race([this.connector(server), timeout]);\n\t\t\tconst at = this.now();\n\t\t\tentry.connection = connection;\n\t\t\tentry.state = { kind: \"ready\", since: at, lastUsed: at };\n\t\t\treturn connection;\n\t\t} catch (error) {\n\t\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\t\tentry.connection = undefined;\n\t\t\tentry.state = {\n\t\t\t\tkind: \"failed\",\n\t\t\t\terror: message,\n\t\t\t\tfailedAt: this.now(),\n\t\t\t\tretryAfter: this.now() + FAILURE_BACKOFF_MS,\n\t\t\t};\n\t\t\tthrow error;\n\t\t} finally {\n\t\t\tif (timer) clearTimeout(timer);\n\t\t\tentry.pending = undefined;\n\t\t}\n\t}\n\n\tprivate async acquire(name: string): Promise<McpConnection> {\n\t\tconst server = this.servers.get(name);\n\t\tif (!server) throw new Error(`MCP server \"${name}\" is not configured`);\n\n\t\tconst entry = this.entry(name);\n\t\tif (entry.state.kind === \"ready\" && entry.connection) return entry.connection;\n\t\tif (entry.pending) return entry.pending;\n\n\t\t// A server that just failed is not retried until its window passes, so one\n\t\t// broken server cannot cost every call a spawn attempt.\n\t\tif (entry.state.kind === \"failed\" && this.now() < entry.state.retryAfter) {\n\t\t\tthrow new Error(entry.state.error);\n\t\t}\n\n\t\tentry.pending = this.connect(server, entry);\n\t\treturn entry.pending;\n\t}\n\n\tasync withConnection<T>(name: string, use: (connection: McpConnection) => Promise<T>): Promise<T> {\n\t\tconst connection = await this.acquire(name);\n\t\tconst result = await use(connection);\n\n\t\tconst entry = this.entry(name);\n\t\tif (entry.state.kind === \"ready\") entry.state = { ...entry.state, lastUsed: this.now() };\n\t\treturn result;\n\t}\n\n\t/**\n\t * Connect every `eager` server and cache what it publishes.\n\t *\n\t * This is the answer to the cold-cache problem. A lazily configured server has no\n\t * cached tools until something calls one, so it cannot be discovered by search; a\n\t * user who wants a server discoverable from the first turn declares it `eager`.\n\t * Failures are swallowed on purpose, because a broken server must not stop a\n\t * session from starting.\n\t */\n\tasync warmEagerServers(onTools: (server: McpServerConfig, tools: CachedTool[]) => void): Promise<void> {\n\t\tfor (const server of this.servers.values()) {\n\t\t\tif (server.lifecycle !== \"eager\") continue;\n\t\t\ttry {\n\t\t\t\tawait this.withConnection(server.name, async (connection) => {\n\t\t\t\t\tonTools(server, await connection.listTools());\n\t\t\t\t});\n\t\t\t} catch {\n\t\t\t\t// Recorded as `failed` by `connect`; the next call reports it.\n\t\t\t}\n\t\t}\n\t}\n\n\t/** Close every `ready` server whose idle window has passed, unless it is kept alive. */\n\tasync sweepIdle(): Promise<void> {\n\t\tconst at = this.now();\n\t\tfor (const [name, entry] of this.entries) {\n\t\t\tconst server = this.servers.get(name);\n\t\t\tif (!server || entry.state.kind !== \"ready\" || isKeptAlive(server)) continue;\n\t\t\tif (at - entry.state.lastUsed < server.idleTimeoutMinutes * 60_000) continue;\n\t\t\tawait this.disconnect(name, entry);\n\t\t}\n\t}\n\n\tprivate async disconnect(name: string, entry: Entry): Promise<void> {\n\t\tconst connection = entry.connection;\n\t\tentry.connection = undefined;\n\t\tentry.state = { kind: \"disconnected\" };\n\t\tif (connection) {\n\t\t\tawait connection.close().catch(() => undefined);\n\t\t}\n\t\tthis.entries.set(name, entry);\n\t}\n\n\tasync closeAll(): Promise<void> {\n\t\tfor (const [name, entry] of this.entries) {\n\t\t\tawait this.disconnect(name, entry);\n\t\t}\n\t}\n}\n"]}
@@ -0,0 +1,100 @@
1
+ /**
2
+ * Core MCP data shapes. Settled before any logic consumes them, per the plan's U1:
3
+ * a late change to `ServerState` or `CachedTool` is a rewrite of the server manager
4
+ * and the proxy tool, where an early one is a one-line diff.
5
+ */
6
+ import type { Capability } from "../tools/contract.ts";
7
+ /**
8
+ * How to reach a server. Inferred from which fields the config entry carries, never
9
+ * declared: every MCP host writes `command` for stdio and `url` for HTTP, and none
10
+ * writes a `transport` field, so requiring one would reject the config files users
11
+ * already have.
12
+ */
13
+ export type McpTransport = {
14
+ kind: "stdio";
15
+ command: string;
16
+ args: string[];
17
+ env: Record<string, string>;
18
+ cwd: string | undefined;
19
+ } | {
20
+ kind: "http";
21
+ url: string;
22
+ headers: Record<string, string>;
23
+ /** Environment variable holding a static bearer token. OAuth lives in the credential store (spec 2026-09-01-mcp-oauth). */
24
+ bearerTokenEnv: string | undefined;
25
+ };
26
+ export type McpLifecycle = "lazy" | "eager" | "keep-alive" | "lazy-keep-alive";
27
+ export declare const DEFAULT_LIFECYCLE: McpLifecycle;
28
+ export declare const DEFAULT_IDLE_TIMEOUT_MINUTES = 10;
29
+ /**
30
+ * A server's declared authority, and the only place it is stated. The capability set
31
+ * feeds the tool contract every tool on this server projects (ADR 0010), which is why
32
+ * an absent set resolves to the full one rather than the empty one.
33
+ */
34
+ export interface McpServerConfig {
35
+ name: string;
36
+ transport: McpTransport;
37
+ /** Set when the user opted this HTTP server into OAuth (spec 2026-09-01-mcp-oauth). Server-level, so metadata cache keys ignore it. */
38
+ auth?: "oauth";
39
+ capabilities: ReadonlySet<Capability>;
40
+ lifecycle: McpLifecycle;
41
+ idleTimeoutMinutes: number;
42
+ }
43
+ /**
44
+ * Connection state. A union rather than a `connected` boolean beside a `lastError`
45
+ * string, so a state cannot carry data that is invalid for it.
46
+ */
47
+ export type ServerState = {
48
+ kind: "disconnected";
49
+ } | {
50
+ kind: "connecting";
51
+ since: number;
52
+ } | {
53
+ kind: "ready";
54
+ since: number;
55
+ lastUsed: number;
56
+ } | {
57
+ kind: "failed";
58
+ error: string;
59
+ failedAt: number;
60
+ retryAfter: number;
61
+ };
62
+ /**
63
+ * What the disk cache holds. Everything here is producible without a live connection,
64
+ * which is the property that lets search and describe answer with no server running.
65
+ */
66
+ export interface CachedTool {
67
+ server: string;
68
+ name: string;
69
+ description: string;
70
+ inputSchema: unknown;
71
+ }
72
+ /** A config problem that dropped an entry or fell back to a default. Never thrown. */
73
+ export interface McpConfigDiagnostic {
74
+ path: string;
75
+ server: string | undefined;
76
+ message: string;
77
+ }
78
+ export interface McpConfig {
79
+ servers: ReadonlyMap<string, McpServerConfig>;
80
+ diagnostics: readonly McpConfigDiagnostic[];
81
+ }
82
+ /** One tool call's result, in the shape the proxy tool converts to `AgentToolResult`. */
83
+ export interface McpCallResult {
84
+ content: Array<{
85
+ type: "text";
86
+ text: string;
87
+ }>;
88
+ isError?: boolean;
89
+ }
90
+ /**
91
+ * A live session with one server. The seam that keeps the MCP SDK out of the
92
+ * lifecycle logic, and lets the server manager be tested without a child process.
93
+ */
94
+ export interface McpConnection {
95
+ listTools(): Promise<CachedTool[]>;
96
+ callTool(name: string, args: unknown): Promise<McpCallResult>;
97
+ close(): Promise<void>;
98
+ }
99
+ export type McpConnector = (server: McpServerConfig) => Promise<McpConnection>;
100
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/core/mcp/types.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAEvD;;;;;GAKG;AACH,MAAM,MAAM,YAAY,GACrB;IACA,IAAI,EAAE,OAAO,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC5B,GAAG,EAAE,MAAM,GAAG,SAAS,CAAC;CACvB,GACD;IACA,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,2HAA2H;IAC3H,cAAc,EAAE,MAAM,GAAG,SAAS,CAAC;CAClC,CAAC;AAEL,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,OAAO,GAAG,YAAY,GAAG,iBAAiB,CAAC;AAE/E,eAAO,MAAM,iBAAiB,EAAE,YAAqB,CAAC;AACtD,eAAO,MAAM,4BAA4B,KAAK,CAAC;AAE/C;;;;GAIG;AACH,MAAM,WAAW,eAAe;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,YAAY,CAAC;IACxB,uIAAuI;IACvI,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,YAAY,EAAE,WAAW,CAAC,UAAU,CAAC,CAAC;IACtC,SAAS,EAAE,YAAY,CAAC;IACxB,kBAAkB,EAAE,MAAM,CAAC;CAC3B;AAED;;;GAGG;AACH,MAAM,MAAM,WAAW,GACpB;IAAE,IAAI,EAAE,cAAc,CAAA;CAAE,GACxB;IAAE,IAAI,EAAE,YAAY,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GACrC;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GAClD;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,CAAC;AAE3E;;;GAGG;AACH,MAAM,WAAW,UAAU;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,OAAO,CAAC;CACrB;AAED,sFAAsF;AACtF,MAAM,WAAW,mBAAmB;IACnC,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3B,OAAO,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,SAAS;IACzB,OAAO,EAAE,WAAW,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;IAC9C,WAAW,EAAE,SAAS,mBAAmB,EAAE,CAAC;CAC5C;AAED,yFAAyF;AACzF,MAAM,WAAW,aAAa;IAC7B,OAAO,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC/C,OAAO,CAAC,EAAE,OAAO,CAAC;CAClB;AAED;;;GAGG;AACH,MAAM,WAAW,aAAa;IAC7B,SAAS,IAAI,OAAO,CAAC,UAAU,EAAE,CAAC,CAAC;IACnC,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,aAAa,CAAC,CAAC;IAC9D,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACvB;AAED,MAAM,MAAM,YAAY,GAAG,CAAC,MAAM,EAAE,eAAe,KAAK,OAAO,CAAC,aAAa,CAAC,CAAC","sourcesContent":["/**\n * Core MCP data shapes. Settled before any logic consumes them, per the plan's U1:\n * a late change to `ServerState` or `CachedTool` is a rewrite of the server manager\n * and the proxy tool, where an early one is a one-line diff.\n */\n\nimport type { Capability } from \"../tools/contract.ts\";\n\n/**\n * How to reach a server. Inferred from which fields the config entry carries, never\n * declared: every MCP host writes `command` for stdio and `url` for HTTP, and none\n * writes a `transport` field, so requiring one would reject the config files users\n * already have.\n */\nexport type McpTransport =\n\t| {\n\t\t\tkind: \"stdio\";\n\t\t\tcommand: string;\n\t\t\targs: string[];\n\t\t\tenv: Record<string, string>;\n\t\t\tcwd: string | undefined;\n\t }\n\t| {\n\t\t\tkind: \"http\";\n\t\t\turl: string;\n\t\t\theaders: Record<string, string>;\n\t\t\t/** Environment variable holding a static bearer token. OAuth lives in the credential store (spec 2026-09-01-mcp-oauth). */\n\t\t\tbearerTokenEnv: string | undefined;\n\t };\n\nexport type McpLifecycle = \"lazy\" | \"eager\" | \"keep-alive\" | \"lazy-keep-alive\";\n\nexport const DEFAULT_LIFECYCLE: McpLifecycle = \"lazy\";\nexport const DEFAULT_IDLE_TIMEOUT_MINUTES = 10;\n\n/**\n * A server's declared authority, and the only place it is stated. The capability set\n * feeds the tool contract every tool on this server projects (ADR 0010), which is why\n * an absent set resolves to the full one rather than the empty one.\n */\nexport interface McpServerConfig {\n\tname: string;\n\ttransport: McpTransport;\n\t/** Set when the user opted this HTTP server into OAuth (spec 2026-09-01-mcp-oauth). Server-level, so metadata cache keys ignore it. */\n\tauth?: \"oauth\";\n\tcapabilities: ReadonlySet<Capability>;\n\tlifecycle: McpLifecycle;\n\tidleTimeoutMinutes: number;\n}\n\n/**\n * Connection state. A union rather than a `connected` boolean beside a `lastError`\n * string, so a state cannot carry data that is invalid for it.\n */\nexport type ServerState =\n\t| { kind: \"disconnected\" }\n\t| { kind: \"connecting\"; since: number }\n\t| { kind: \"ready\"; since: number; lastUsed: number }\n\t| { kind: \"failed\"; error: string; failedAt: number; retryAfter: number };\n\n/**\n * What the disk cache holds. Everything here is producible without a live connection,\n * which is the property that lets search and describe answer with no server running.\n */\nexport interface CachedTool {\n\tserver: string;\n\tname: string;\n\tdescription: string;\n\tinputSchema: unknown;\n}\n\n/** A config problem that dropped an entry or fell back to a default. Never thrown. */\nexport interface McpConfigDiagnostic {\n\tpath: string;\n\tserver: string | undefined;\n\tmessage: string;\n}\n\nexport interface McpConfig {\n\tservers: ReadonlyMap<string, McpServerConfig>;\n\tdiagnostics: readonly McpConfigDiagnostic[];\n}\n\n/** One tool call's result, in the shape the proxy tool converts to `AgentToolResult`. */\nexport interface McpCallResult {\n\tcontent: Array<{ type: \"text\"; text: string }>;\n\tisError?: boolean;\n}\n\n/**\n * A live session with one server. The seam that keeps the MCP SDK out of the\n * lifecycle logic, and lets the server manager be tested without a child process.\n */\nexport interface McpConnection {\n\tlistTools(): Promise<CachedTool[]>;\n\tcallTool(name: string, args: unknown): Promise<McpCallResult>;\n\tclose(): Promise<void>;\n}\n\nexport type McpConnector = (server: McpServerConfig) => Promise<McpConnection>;\n"]}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Core MCP data shapes. Settled before any logic consumes them, per the plan's U1:
3
+ * a late change to `ServerState` or `CachedTool` is a rewrite of the server manager
4
+ * and the proxy tool, where an early one is a one-line diff.
5
+ */
6
+ export const DEFAULT_LIFECYCLE = "lazy";
7
+ export const DEFAULT_IDLE_TIMEOUT_MINUTES = 10;
8
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/core/mcp/types.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AA4BH,MAAM,CAAC,MAAM,iBAAiB,GAAiB,MAAM,CAAC;AACtD,MAAM,CAAC,MAAM,4BAA4B,GAAG,EAAE,CAAC","sourcesContent":["/**\n * Core MCP data shapes. Settled before any logic consumes them, per the plan's U1:\n * a late change to `ServerState` or `CachedTool` is a rewrite of the server manager\n * and the proxy tool, where an early one is a one-line diff.\n */\n\nimport type { Capability } from \"../tools/contract.ts\";\n\n/**\n * How to reach a server. Inferred from which fields the config entry carries, never\n * declared: every MCP host writes `command` for stdio and `url` for HTTP, and none\n * writes a `transport` field, so requiring one would reject the config files users\n * already have.\n */\nexport type McpTransport =\n\t| {\n\t\t\tkind: \"stdio\";\n\t\t\tcommand: string;\n\t\t\targs: string[];\n\t\t\tenv: Record<string, string>;\n\t\t\tcwd: string | undefined;\n\t }\n\t| {\n\t\t\tkind: \"http\";\n\t\t\turl: string;\n\t\t\theaders: Record<string, string>;\n\t\t\t/** Environment variable holding a static bearer token. OAuth lives in the credential store (spec 2026-09-01-mcp-oauth). */\n\t\t\tbearerTokenEnv: string | undefined;\n\t };\n\nexport type McpLifecycle = \"lazy\" | \"eager\" | \"keep-alive\" | \"lazy-keep-alive\";\n\nexport const DEFAULT_LIFECYCLE: McpLifecycle = \"lazy\";\nexport const DEFAULT_IDLE_TIMEOUT_MINUTES = 10;\n\n/**\n * A server's declared authority, and the only place it is stated. The capability set\n * feeds the tool contract every tool on this server projects (ADR 0010), which is why\n * an absent set resolves to the full one rather than the empty one.\n */\nexport interface McpServerConfig {\n\tname: string;\n\ttransport: McpTransport;\n\t/** Set when the user opted this HTTP server into OAuth (spec 2026-09-01-mcp-oauth). Server-level, so metadata cache keys ignore it. */\n\tauth?: \"oauth\";\n\tcapabilities: ReadonlySet<Capability>;\n\tlifecycle: McpLifecycle;\n\tidleTimeoutMinutes: number;\n}\n\n/**\n * Connection state. A union rather than a `connected` boolean beside a `lastError`\n * string, so a state cannot carry data that is invalid for it.\n */\nexport type ServerState =\n\t| { kind: \"disconnected\" }\n\t| { kind: \"connecting\"; since: number }\n\t| { kind: \"ready\"; since: number; lastUsed: number }\n\t| { kind: \"failed\"; error: string; failedAt: number; retryAfter: number };\n\n/**\n * What the disk cache holds. Everything here is producible without a live connection,\n * which is the property that lets search and describe answer with no server running.\n */\nexport interface CachedTool {\n\tserver: string;\n\tname: string;\n\tdescription: string;\n\tinputSchema: unknown;\n}\n\n/** A config problem that dropped an entry or fell back to a default. Never thrown. */\nexport interface McpConfigDiagnostic {\n\tpath: string;\n\tserver: string | undefined;\n\tmessage: string;\n}\n\nexport interface McpConfig {\n\tservers: ReadonlyMap<string, McpServerConfig>;\n\tdiagnostics: readonly McpConfigDiagnostic[];\n}\n\n/** One tool call's result, in the shape the proxy tool converts to `AgentToolResult`. */\nexport interface McpCallResult {\n\tcontent: Array<{ type: \"text\"; text: string }>;\n\tisError?: boolean;\n}\n\n/**\n * A live session with one server. The seam that keeps the MCP SDK out of the\n * lifecycle logic, and lets the server manager be tested without a child process.\n */\nexport interface McpConnection {\n\tlistTools(): Promise<CachedTool[]>;\n\tcallTool(name: string, args: unknown): Promise<McpCallResult>;\n\tclose(): Promise<void>;\n}\n\nexport type McpConnector = (server: McpServerConfig) => Promise<McpConnection>;\n"]}