apex-code 0.0.1-alpha.8 → 0.0.4

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 (634) hide show
  1. package/CHANGELOG.md +231 -0
  2. package/README.md +9 -8
  3. package/dist/cli/args.d.ts +26 -1
  4. package/dist/cli/args.d.ts.map +1 -1
  5. package/dist/cli/args.js +72 -10
  6. package/dist/cli/args.js.map +1 -1
  7. package/dist/cli/file-processor.d.ts.map +1 -1
  8. package/dist/cli/file-processor.js +2 -1
  9. package/dist/cli/file-processor.js.map +1 -1
  10. package/dist/cli/mcp-command.d.ts +9 -0
  11. package/dist/cli/mcp-command.d.ts.map +1 -0
  12. package/dist/cli/mcp-command.js +53 -0
  13. package/dist/cli/mcp-command.js.map +1 -0
  14. package/dist/cli/project-trust.d.ts.map +1 -1
  15. package/dist/cli/project-trust.js +3 -1
  16. package/dist/cli/project-trust.js.map +1 -1
  17. package/dist/cli/startup-ui.d.ts.map +1 -1
  18. package/dist/cli/startup-ui.js +2 -1
  19. package/dist/cli/startup-ui.js.map +1 -1
  20. package/dist/cli.d.ts.map +1 -1
  21. package/dist/cli.js +94 -5
  22. package/dist/cli.js.map +1 -1
  23. package/dist/config.d.ts +3 -2
  24. package/dist/config.d.ts.map +1 -1
  25. package/dist/config.js +45 -18
  26. package/dist/config.js.map +1 -1
  27. package/dist/core/agent-session-services.d.ts +3 -0
  28. package/dist/core/agent-session-services.d.ts.map +1 -1
  29. package/dist/core/agent-session-services.js +1 -0
  30. package/dist/core/agent-session-services.js.map +1 -1
  31. package/dist/core/agent-session.d.ts +183 -15
  32. package/dist/core/agent-session.d.ts.map +1 -1
  33. package/dist/core/agent-session.js +728 -117
  34. package/dist/core/agent-session.js.map +1 -1
  35. package/dist/core/auth-storage.d.ts.map +1 -1
  36. package/dist/core/auth-storage.js +6 -7
  37. package/dist/core/auth-storage.js.map +1 -1
  38. package/dist/core/bash-executor.d.ts.map +1 -1
  39. package/dist/core/bash-executor.js +7 -0
  40. package/dist/core/bash-executor.js.map +1 -1
  41. package/dist/core/checkpoints/git-checkpoints.d.ts +39 -0
  42. package/dist/core/checkpoints/git-checkpoints.d.ts.map +1 -0
  43. package/dist/core/checkpoints/git-checkpoints.js +251 -0
  44. package/dist/core/checkpoints/git-checkpoints.js.map +1 -0
  45. package/dist/core/checkpoints/session-checkpoints.d.ts +23 -0
  46. package/dist/core/checkpoints/session-checkpoints.d.ts.map +1 -0
  47. package/dist/core/checkpoints/session-checkpoints.js +36 -0
  48. package/dist/core/checkpoints/session-checkpoints.js.map +1 -0
  49. package/dist/core/compaction/branch-summarization.d.ts.map +1 -1
  50. package/dist/core/compaction/branch-summarization.js +7 -3
  51. package/dist/core/compaction/branch-summarization.js.map +1 -1
  52. package/dist/core/compaction/compaction.d.ts +9 -3
  53. package/dist/core/compaction/compaction.d.ts.map +1 -1
  54. package/dist/core/compaction/compaction.js +59 -36
  55. package/dist/core/compaction/compaction.js.map +1 -1
  56. package/dist/core/defaults.d.ts +1 -0
  57. package/dist/core/defaults.d.ts.map +1 -1
  58. package/dist/core/defaults.js +9 -0
  59. package/dist/core/defaults.js.map +1 -1
  60. package/dist/core/delegation/runtime.d.ts +6 -5
  61. package/dist/core/delegation/runtime.d.ts.map +1 -1
  62. package/dist/core/delegation/runtime.js +4 -3
  63. package/dist/core/delegation/runtime.js.map +1 -1
  64. package/dist/core/durable-state/sqlite.d.ts.map +1 -1
  65. package/dist/core/durable-state/sqlite.js +26 -2
  66. package/dist/core/durable-state/sqlite.js.map +1 -1
  67. package/dist/core/exec.d.ts.map +1 -1
  68. package/dist/core/exec.js +5 -2
  69. package/dist/core/exec.js.map +1 -1
  70. package/dist/core/extensions/index.d.ts +2 -2
  71. package/dist/core/extensions/index.d.ts.map +1 -1
  72. package/dist/core/extensions/index.js +1 -1
  73. package/dist/core/extensions/index.js.map +1 -1
  74. package/dist/core/extensions/loader.d.ts.map +1 -1
  75. package/dist/core/extensions/loader.js +123 -50
  76. package/dist/core/extensions/loader.js.map +1 -1
  77. package/dist/core/extensions/runner.d.ts +5 -0
  78. package/dist/core/extensions/runner.d.ts.map +1 -1
  79. package/dist/core/extensions/runner.js +45 -1
  80. package/dist/core/extensions/runner.js.map +1 -1
  81. package/dist/core/extensions/source-runtime.d.ts +14 -0
  82. package/dist/core/extensions/source-runtime.d.ts.map +1 -0
  83. package/dist/core/extensions/source-runtime.js +56 -0
  84. package/dist/core/extensions/source-runtime.js.map +1 -0
  85. package/dist/core/extensions/types.d.ts +60 -11
  86. package/dist/core/extensions/types.d.ts.map +1 -1
  87. package/dist/core/extensions/types.js +3 -0
  88. package/dist/core/extensions/types.js.map +1 -1
  89. package/dist/core/footer-data-provider.d.ts +1 -1
  90. package/dist/core/footer-data-provider.d.ts.map +1 -1
  91. package/dist/core/footer-data-provider.js +1 -1
  92. package/dist/core/footer-data-provider.js.map +1 -1
  93. package/dist/core/formatter-lifecycle.d.ts +69 -0
  94. package/dist/core/formatter-lifecycle.d.ts.map +1 -0
  95. package/dist/core/formatter-lifecycle.js +360 -0
  96. package/dist/core/formatter-lifecycle.js.map +1 -0
  97. package/dist/core/hooks/command-handler.d.ts +19 -0
  98. package/dist/core/hooks/command-handler.d.ts.map +1 -0
  99. package/dist/core/hooks/command-handler.js +112 -0
  100. package/dist/core/hooks/command-handler.js.map +1 -0
  101. package/dist/core/hooks/http-handler.d.ts +10 -0
  102. package/dist/core/hooks/http-handler.d.ts.map +1 -0
  103. package/dist/core/hooks/http-handler.js +36 -0
  104. package/dist/core/hooks/http-handler.js.map +1 -0
  105. package/dist/core/hooks/loader.d.ts +29 -0
  106. package/dist/core/hooks/loader.d.ts.map +1 -0
  107. package/dist/core/hooks/loader.js +139 -0
  108. package/dist/core/hooks/loader.js.map +1 -0
  109. package/dist/core/hooks/runtime.d.ts +13 -0
  110. package/dist/core/hooks/runtime.d.ts.map +1 -0
  111. package/dist/core/hooks/runtime.js +63 -0
  112. package/dist/core/hooks/runtime.js.map +1 -0
  113. package/dist/core/hooks/types.d.ts +83 -0
  114. package/dist/core/hooks/types.d.ts.map +1 -0
  115. package/dist/core/hooks/types.js +24 -0
  116. package/dist/core/hooks/types.js.map +1 -0
  117. package/dist/core/keybindings.d.ts +20 -19
  118. package/dist/core/keybindings.d.ts.map +1 -1
  119. package/dist/core/keybindings.js +26 -5
  120. package/dist/core/keybindings.js.map +1 -1
  121. package/dist/core/mcp/config.d.ts +16 -0
  122. package/dist/core/mcp/config.d.ts.map +1 -0
  123. package/dist/core/mcp/config.js +166 -0
  124. package/dist/core/mcp/config.js.map +1 -0
  125. package/dist/core/mcp/connector.d.ts +35 -0
  126. package/dist/core/mcp/connector.d.ts.map +1 -0
  127. package/dist/core/mcp/connector.js +105 -0
  128. package/dist/core/mcp/connector.js.map +1 -0
  129. package/dist/core/mcp/contract.d.ts +19 -0
  130. package/dist/core/mcp/contract.d.ts.map +1 -0
  131. package/dist/core/mcp/contract.js +89 -0
  132. package/dist/core/mcp/contract.js.map +1 -0
  133. package/dist/core/mcp/mcp-tool.d.ts +32 -0
  134. package/dist/core/mcp/mcp-tool.d.ts.map +1 -0
  135. package/dist/core/mcp/mcp-tool.js +171 -0
  136. package/dist/core/mcp/mcp-tool.js.map +1 -0
  137. package/dist/core/mcp/metadata-cache.d.ts +29 -0
  138. package/dist/core/mcp/metadata-cache.d.ts.map +1 -0
  139. package/dist/core/mcp/metadata-cache.js +99 -0
  140. package/dist/core/mcp/metadata-cache.js.map +1 -0
  141. package/dist/core/mcp/oauth/authorize.d.ts +21 -0
  142. package/dist/core/mcp/oauth/authorize.d.ts.map +1 -0
  143. package/dist/core/mcp/oauth/authorize.js +32 -0
  144. package/dist/core/mcp/oauth/authorize.js.map +1 -0
  145. package/dist/core/mcp/oauth/discover.d.ts +27 -0
  146. package/dist/core/mcp/oauth/discover.d.ts.map +1 -0
  147. package/dist/core/mcp/oauth/discover.js +116 -0
  148. package/dist/core/mcp/oauth/discover.js.map +1 -0
  149. package/dist/core/mcp/oauth/flow.d.ts +31 -0
  150. package/dist/core/mcp/oauth/flow.d.ts.map +1 -0
  151. package/dist/core/mcp/oauth/flow.js +175 -0
  152. package/dist/core/mcp/oauth/flow.js.map +1 -0
  153. package/dist/core/mcp/oauth/mcp-token.d.ts +51 -0
  154. package/dist/core/mcp/oauth/mcp-token.d.ts.map +1 -0
  155. package/dist/core/mcp/oauth/mcp-token.js +129 -0
  156. package/dist/core/mcp/oauth/mcp-token.js.map +1 -0
  157. package/dist/core/mcp/oauth/pkce.d.ts +12 -0
  158. package/dist/core/mcp/oauth/pkce.d.ts.map +1 -0
  159. package/dist/core/mcp/oauth/pkce.js +11 -0
  160. package/dist/core/mcp/oauth/pkce.js.map +1 -0
  161. package/dist/core/mcp/oauth/register.d.ts +16 -0
  162. package/dist/core/mcp/oauth/register.d.ts.map +1 -0
  163. package/dist/core/mcp/oauth/register.js +42 -0
  164. package/dist/core/mcp/oauth/register.js.map +1 -0
  165. package/dist/core/mcp/oauth/token-client.d.ts +33 -0
  166. package/dist/core/mcp/oauth/token-client.d.ts.map +1 -0
  167. package/dist/core/mcp/oauth/token-client.js +65 -0
  168. package/dist/core/mcp/oauth/token-client.js.map +1 -0
  169. package/dist/core/mcp/runtime.d.ts +20 -0
  170. package/dist/core/mcp/runtime.d.ts.map +1 -0
  171. package/dist/core/mcp/runtime.js +37 -0
  172. package/dist/core/mcp/runtime.js.map +1 -0
  173. package/dist/core/mcp/schema.d.ts +22 -0
  174. package/dist/core/mcp/schema.d.ts.map +1 -0
  175. package/dist/core/mcp/schema.js +20 -0
  176. package/dist/core/mcp/schema.js.map +1 -0
  177. package/dist/core/mcp/server-manager.d.ts +43 -0
  178. package/dist/core/mcp/server-manager.d.ts.map +1 -0
  179. package/dist/core/mcp/server-manager.js +142 -0
  180. package/dist/core/mcp/server-manager.js.map +1 -0
  181. package/dist/core/mcp/types.d.ts +100 -0
  182. package/dist/core/mcp/types.d.ts.map +1 -0
  183. package/dist/core/mcp/types.js +8 -0
  184. package/dist/core/mcp/types.js.map +1 -0
  185. package/dist/core/model-config.d.ts +5 -0
  186. package/dist/core/model-config.d.ts.map +1 -1
  187. package/dist/core/model-config.js +3 -1
  188. package/dist/core/model-config.js.map +1 -1
  189. package/dist/core/model-resolver.d.ts +1 -0
  190. package/dist/core/model-resolver.d.ts.map +1 -1
  191. package/dist/core/model-resolver.js +14 -8
  192. package/dist/core/model-resolver.js.map +1 -1
  193. package/dist/core/models-store.d.ts.map +1 -1
  194. package/dist/core/models-store.js +2 -1
  195. package/dist/core/models-store.js.map +1 -1
  196. package/dist/core/package-manager.d.ts.map +1 -1
  197. package/dist/core/package-manager.js +22 -14
  198. package/dist/core/package-manager.js.map +1 -1
  199. package/dist/core/permissions/gate.d.ts.map +1 -1
  200. package/dist/core/permissions/gate.js +45 -4
  201. package/dist/core/permissions/gate.js.map +1 -1
  202. package/dist/core/permissions/operations.d.ts +20 -0
  203. package/dist/core/permissions/operations.d.ts.map +1 -0
  204. package/dist/core/permissions/operations.js +9 -0
  205. package/dist/core/permissions/operations.js.map +1 -0
  206. package/dist/core/permissions/policy-command.d.ts +42 -0
  207. package/dist/core/permissions/policy-command.d.ts.map +1 -0
  208. package/dist/core/permissions/policy-command.js +44 -0
  209. package/dist/core/permissions/policy-command.js.map +1 -0
  210. package/dist/core/permissions/responder.d.ts +49 -2
  211. package/dist/core/permissions/responder.d.ts.map +1 -1
  212. package/dist/core/permissions/responder.js +20 -8
  213. package/dist/core/permissions/responder.js.map +1 -1
  214. package/dist/core/permissions/rules.d.ts.map +1 -1
  215. package/dist/core/permissions/rules.js +11 -1
  216. package/dist/core/permissions/rules.js.map +1 -1
  217. package/dist/core/permissions/store.d.ts +18 -0
  218. package/dist/core/permissions/store.d.ts.map +1 -1
  219. package/dist/core/permissions/store.js +89 -8
  220. package/dist/core/permissions/store.js.map +1 -1
  221. package/dist/core/pi-manifest.d.ts.map +1 -1
  222. package/dist/core/pi-manifest.js +2 -1
  223. package/dist/core/pi-manifest.js.map +1 -1
  224. package/dist/core/policy-executor.d.ts +47 -0
  225. package/dist/core/policy-executor.d.ts.map +1 -0
  226. package/dist/core/policy-executor.js +228 -0
  227. package/dist/core/policy-executor.js.map +1 -0
  228. package/dist/core/policy-loader.d.ts +72 -0
  229. package/dist/core/policy-loader.d.ts.map +1 -0
  230. package/dist/core/policy-loader.js +195 -0
  231. package/dist/core/policy-loader.js.map +1 -0
  232. package/dist/core/project-trust.d.ts +1 -1
  233. package/dist/core/project-trust.d.ts.map +1 -1
  234. package/dist/core/project-trust.js.map +1 -1
  235. package/dist/core/remote-catalog-provider.d.ts.map +1 -1
  236. package/dist/core/remote-catalog-provider.js +2 -1
  237. package/dist/core/remote-catalog-provider.js.map +1 -1
  238. package/dist/core/resource-loader.d.ts.map +1 -1
  239. package/dist/core/resource-loader.js +3 -2
  240. package/dist/core/resource-loader.js.map +1 -1
  241. package/dist/core/sandbox/bwrap-arguments.d.ts +41 -0
  242. package/dist/core/sandbox/bwrap-arguments.d.ts.map +1 -0
  243. package/dist/core/sandbox/bwrap-arguments.js +136 -0
  244. package/dist/core/sandbox/bwrap-arguments.js.map +1 -0
  245. package/dist/core/sandbox/child-entry.d.ts.map +1 -1
  246. package/dist/core/sandbox/child-entry.js +3 -1
  247. package/dist/core/sandbox/child-entry.js.map +1 -1
  248. package/dist/core/sandbox/cli-launch.d.ts +84 -4
  249. package/dist/core/sandbox/cli-launch.d.ts.map +1 -1
  250. package/dist/core/sandbox/cli-launch.js +115 -16
  251. package/dist/core/sandbox/cli-launch.js.map +1 -1
  252. package/dist/core/sandbox/cli-supervisor.d.ts +2 -0
  253. package/dist/core/sandbox/cli-supervisor.d.ts.map +1 -1
  254. package/dist/core/sandbox/cli-supervisor.js +50 -2
  255. package/dist/core/sandbox/cli-supervisor.js.map +1 -1
  256. package/dist/core/sandbox/full-access.d.ts +28 -0
  257. package/dist/core/sandbox/full-access.d.ts.map +1 -0
  258. package/dist/core/sandbox/full-access.js +63 -0
  259. package/dist/core/sandbox/full-access.js.map +1 -0
  260. package/dist/core/sandbox/git-identity.d.ts +57 -0
  261. package/dist/core/sandbox/git-identity.d.ts.map +1 -0
  262. package/dist/core/sandbox/git-identity.js +78 -0
  263. package/dist/core/sandbox/git-identity.js.map +1 -0
  264. package/dist/core/sandbox/host-approval.d.ts +42 -0
  265. package/dist/core/sandbox/host-approval.d.ts.map +1 -0
  266. package/dist/core/sandbox/host-approval.js +99 -0
  267. package/dist/core/sandbox/host-approval.js.map +1 -0
  268. package/dist/core/sandbox/linux-backend.d.ts +14 -0
  269. package/dist/core/sandbox/linux-backend.d.ts.map +1 -1
  270. package/dist/core/sandbox/linux-backend.js +158 -80
  271. package/dist/core/sandbox/linux-backend.js.map +1 -1
  272. package/dist/core/sandbox/macos-backend.d.ts +5 -0
  273. package/dist/core/sandbox/macos-backend.d.ts.map +1 -1
  274. package/dist/core/sandbox/macos-backend.js +164 -5
  275. package/dist/core/sandbox/macos-backend.js.map +1 -1
  276. package/dist/core/sandbox/network-proxy.d.ts +19 -0
  277. package/dist/core/sandbox/network-proxy.d.ts.map +1 -1
  278. package/dist/core/sandbox/network-proxy.js +93 -19
  279. package/dist/core/sandbox/network-proxy.js.map +1 -1
  280. package/dist/core/sandbox/policy.d.ts +8 -0
  281. package/dist/core/sandbox/policy.d.ts.map +1 -1
  282. package/dist/core/sandbox/policy.js +29 -12
  283. package/dist/core/sandbox/policy.js.map +1 -1
  284. package/dist/core/sandbox/profiles.d.ts +25 -0
  285. package/dist/core/sandbox/profiles.d.ts.map +1 -0
  286. package/dist/core/sandbox/profiles.js +23 -0
  287. package/dist/core/sandbox/profiles.js.map +1 -0
  288. package/dist/core/sandbox/rpc/command-client.d.ts +28 -0
  289. package/dist/core/sandbox/rpc/command-client.d.ts.map +1 -0
  290. package/dist/core/sandbox/rpc/command-client.js +86 -0
  291. package/dist/core/sandbox/rpc/command-client.js.map +1 -0
  292. package/dist/core/sandbox/rpc/command-proxy.d.ts +48 -0
  293. package/dist/core/sandbox/rpc/command-proxy.d.ts.map +1 -0
  294. package/dist/core/sandbox/rpc/command-proxy.js +120 -0
  295. package/dist/core/sandbox/rpc/command-proxy.js.map +1 -0
  296. package/dist/core/sandbox/rpc/credential-proxy.d.ts.map +1 -1
  297. package/dist/core/sandbox/rpc/credential-proxy.js +2 -96
  298. package/dist/core/sandbox/rpc/credential-proxy.js.map +1 -1
  299. package/dist/core/sandbox/rpc/framing.d.ts +35 -0
  300. package/dist/core/sandbox/rpc/framing.d.ts.map +1 -0
  301. package/dist/core/sandbox/rpc/framing.js +114 -0
  302. package/dist/core/sandbox/rpc/framing.js.map +1 -0
  303. package/dist/core/sandbox/rpc/git-credential-helper.d.ts +47 -0
  304. package/dist/core/sandbox/rpc/git-credential-helper.d.ts.map +1 -0
  305. package/dist/core/sandbox/rpc/git-credential-helper.js +222 -0
  306. package/dist/core/sandbox/rpc/git-credential-helper.js.map +1 -0
  307. package/dist/core/sandbox/rpc/git-credential-proxy.d.ts +66 -0
  308. package/dist/core/sandbox/rpc/git-credential-proxy.d.ts.map +1 -0
  309. package/dist/core/sandbox/rpc/git-credential-proxy.js +209 -0
  310. package/dist/core/sandbox/rpc/git-credential-proxy.js.map +1 -0
  311. package/dist/core/sandbox/supervisor-temp.d.ts +18 -0
  312. package/dist/core/sandbox/supervisor-temp.d.ts.map +1 -0
  313. package/dist/core/sandbox/supervisor-temp.js +21 -0
  314. package/dist/core/sandbox/supervisor-temp.js.map +1 -0
  315. package/dist/core/sandbox/supervisor.d.ts +12 -1
  316. package/dist/core/sandbox/supervisor.d.ts.map +1 -1
  317. package/dist/core/sandbox/supervisor.js +15 -1
  318. package/dist/core/sandbox/supervisor.js.map +1 -1
  319. package/dist/core/sandbox/terminal-handoff.d.ts +68 -0
  320. package/dist/core/sandbox/terminal-handoff.d.ts.map +1 -0
  321. package/dist/core/sandbox/terminal-handoff.js +221 -0
  322. package/dist/core/sandbox/terminal-handoff.js.map +1 -0
  323. package/dist/core/sandbox/terminal-size.d.ts +13 -0
  324. package/dist/core/sandbox/terminal-size.d.ts.map +1 -1
  325. package/dist/core/sandbox/terminal-size.js +23 -2
  326. package/dist/core/sandbox/terminal-size.js.map +1 -1
  327. package/dist/core/sdk.d.ts +24 -2
  328. package/dist/core/sdk.d.ts.map +1 -1
  329. package/dist/core/sdk.js +63 -4
  330. package/dist/core/sdk.js.map +1 -1
  331. package/dist/core/session-export.d.ts +4 -0
  332. package/dist/core/session-export.d.ts.map +1 -0
  333. package/dist/core/session-export.js +32 -0
  334. package/dist/core/session-export.js.map +1 -0
  335. package/dist/core/session-manager.d.ts +13 -1
  336. package/dist/core/session-manager.d.ts.map +1 -1
  337. package/dist/core/session-manager.js +34 -6
  338. package/dist/core/session-manager.js.map +1 -1
  339. package/dist/core/settings-diagnostics.d.ts +9 -0
  340. package/dist/core/settings-diagnostics.d.ts.map +1 -0
  341. package/dist/core/settings-diagnostics.js +21 -0
  342. package/dist/core/settings-diagnostics.js.map +1 -0
  343. package/dist/core/settings-manager.d.ts +172 -1
  344. package/dist/core/settings-manager.d.ts.map +1 -1
  345. package/dist/core/settings-manager.js +160 -11
  346. package/dist/core/settings-manager.js.map +1 -1
  347. package/dist/core/skills.d.ts.map +1 -1
  348. package/dist/core/skills.js +51 -33
  349. package/dist/core/skills.js.map +1 -1
  350. package/dist/core/slash-commands.d.ts.map +1 -1
  351. package/dist/core/slash-commands.js +4 -1
  352. package/dist/core/slash-commands.js.map +1 -1
  353. package/dist/core/system-prompt.d.ts.map +1 -1
  354. package/dist/core/system-prompt.js +8 -2
  355. package/dist/core/system-prompt.js.map +1 -1
  356. package/dist/core/tools/background-shell.d.ts +39 -0
  357. package/dist/core/tools/background-shell.d.ts.map +1 -0
  358. package/dist/core/tools/background-shell.js +81 -0
  359. package/dist/core/tools/background-shell.js.map +1 -0
  360. package/dist/core/tools/bash-command-segments.d.ts.map +1 -1
  361. package/dist/core/tools/bash-command-segments.js +2 -0
  362. package/dist/core/tools/bash-command-segments.js.map +1 -1
  363. package/dist/core/tools/bash.d.ts +44 -16
  364. package/dist/core/tools/bash.d.ts.map +1 -1
  365. package/dist/core/tools/bash.js +264 -32
  366. package/dist/core/tools/bash.js.map +1 -1
  367. package/dist/core/tools/contract-snapshot.d.ts +58 -0
  368. package/dist/core/tools/contract-snapshot.d.ts.map +1 -0
  369. package/dist/core/tools/contract-snapshot.js +51 -0
  370. package/dist/core/tools/contract-snapshot.js.map +1 -0
  371. package/dist/core/tools/contract.d.ts +53 -5
  372. package/dist/core/tools/contract.d.ts.map +1 -1
  373. package/dist/core/tools/contract.js +22 -2
  374. package/dist/core/tools/contract.js.map +1 -1
  375. package/dist/core/tools/delegate.d.ts +6 -4
  376. package/dist/core/tools/delegate.d.ts.map +1 -1
  377. package/dist/core/tools/delegate.js +21 -11
  378. package/dist/core/tools/delegate.js.map +1 -1
  379. package/dist/core/tools/edit-diff.d.ts +48 -5
  380. package/dist/core/tools/edit-diff.d.ts.map +1 -1
  381. package/dist/core/tools/edit-diff.js +217 -8
  382. package/dist/core/tools/edit-diff.js.map +1 -1
  383. package/dist/core/tools/edit.d.ts +8 -0
  384. package/dist/core/tools/edit.d.ts.map +1 -1
  385. package/dist/core/tools/edit.js +72 -10
  386. package/dist/core/tools/edit.js.map +1 -1
  387. package/dist/core/tools/find.d.ts.map +1 -1
  388. package/dist/core/tools/find.js +5 -2
  389. package/dist/core/tools/find.js.map +1 -1
  390. package/dist/core/tools/grep.d.ts.map +1 -1
  391. package/dist/core/tools/grep.js +5 -2
  392. package/dist/core/tools/grep.js.map +1 -1
  393. package/dist/core/tools/index.d.ts +14 -2
  394. package/dist/core/tools/index.d.ts.map +1 -1
  395. package/dist/core/tools/index.js +12 -0
  396. package/dist/core/tools/index.js.map +1 -1
  397. package/dist/core/tools/ls.d.ts.map +1 -1
  398. package/dist/core/tools/ls.js +5 -2
  399. package/dist/core/tools/ls.js.map +1 -1
  400. package/dist/core/tools/lsp.d.ts +19 -1
  401. package/dist/core/tools/lsp.d.ts.map +1 -1
  402. package/dist/core/tools/lsp.js +163 -10
  403. package/dist/core/tools/lsp.js.map +1 -1
  404. package/dist/core/tools/path-permission.d.ts +3 -0
  405. package/dist/core/tools/path-permission.d.ts.map +1 -1
  406. package/dist/core/tools/path-permission.js +16 -4
  407. package/dist/core/tools/path-permission.js.map +1 -1
  408. package/dist/core/tools/path-utils.d.ts +27 -2
  409. package/dist/core/tools/path-utils.d.ts.map +1 -1
  410. package/dist/core/tools/path-utils.js +221 -4
  411. package/dist/core/tools/path-utils.js.map +1 -1
  412. package/dist/core/tools/powershell.d.ts +16 -0
  413. package/dist/core/tools/powershell.d.ts.map +1 -0
  414. package/dist/core/tools/powershell.js +39 -0
  415. package/dist/core/tools/powershell.js.map +1 -0
  416. package/dist/core/tools/read.d.ts.map +1 -1
  417. package/dist/core/tools/read.js +20 -11
  418. package/dist/core/tools/read.js.map +1 -1
  419. package/dist/core/tools/test.d.ts +54 -9
  420. package/dist/core/tools/test.d.ts.map +1 -1
  421. package/dist/core/tools/test.js +239 -19
  422. package/dist/core/tools/test.js.map +1 -1
  423. package/dist/core/tools/write.d.ts +7 -0
  424. package/dist/core/tools/write.d.ts.map +1 -1
  425. package/dist/core/tools/write.js +64 -9
  426. package/dist/core/tools/write.js.map +1 -1
  427. package/dist/core/trust-manager.d.ts.map +1 -1
  428. package/dist/core/trust-manager.js +2 -1
  429. package/dist/core/trust-manager.js.map +1 -1
  430. package/dist/core/verification-lifecycle.d.ts +77 -0
  431. package/dist/core/verification-lifecycle.d.ts.map +1 -0
  432. package/dist/core/verification-lifecycle.js +146 -0
  433. package/dist/core/verification-lifecycle.js.map +1 -0
  434. package/dist/core/workspace/artifacts.d.ts +45 -0
  435. package/dist/core/workspace/artifacts.d.ts.map +1 -0
  436. package/dist/core/workspace/artifacts.js +165 -0
  437. package/dist/core/workspace/artifacts.js.map +1 -0
  438. package/dist/core/workspace/comparison.d.ts +27 -0
  439. package/dist/core/workspace/comparison.d.ts.map +1 -0
  440. package/dist/core/workspace/comparison.js +94 -0
  441. package/dist/core/workspace/comparison.js.map +1 -0
  442. package/dist/core/workspace/git-observer.d.ts +48 -0
  443. package/dist/core/workspace/git-observer.d.ts.map +1 -0
  444. package/dist/core/workspace/git-observer.js +346 -0
  445. package/dist/core/workspace/git-observer.js.map +1 -0
  446. package/dist/core/workspace/projection.d.ts +3 -0
  447. package/dist/core/workspace/projection.d.ts.map +1 -0
  448. package/dist/core/workspace/projection.js +56 -0
  449. package/dist/core/workspace/projection.js.map +1 -0
  450. package/dist/core/workspace/state.d.ts +133 -0
  451. package/dist/core/workspace/state.d.ts.map +1 -0
  452. package/dist/core/workspace/state.js +96 -0
  453. package/dist/core/workspace/state.js.map +1 -0
  454. package/dist/extensions/llama/client.d.ts +6 -0
  455. package/dist/extensions/llama/client.d.ts.map +1 -1
  456. package/dist/extensions/llama/client.js +7 -0
  457. package/dist/extensions/llama/client.js.map +1 -1
  458. package/dist/extensions/llama/index.d.ts.map +1 -1
  459. package/dist/extensions/llama/index.js +2 -0
  460. package/dist/extensions/llama/index.js.map +1 -1
  461. package/dist/extensions/llama/provider.d.ts +3 -1
  462. package/dist/extensions/llama/provider.d.ts.map +1 -1
  463. package/dist/extensions/llama/provider.js +29 -4
  464. package/dist/extensions/llama/provider.js.map +1 -1
  465. package/dist/index.d.ts +8 -5
  466. package/dist/index.d.ts.map +1 -1
  467. package/dist/index.js +7 -4
  468. package/dist/index.js.map +1 -1
  469. package/dist/main.d.ts +6 -0
  470. package/dist/main.d.ts.map +1 -1
  471. package/dist/main.js +58 -23
  472. package/dist/main.js.map +1 -1
  473. package/dist/migrations.d.ts.map +1 -1
  474. package/dist/migrations.js +4 -3
  475. package/dist/migrations.js.map +1 -1
  476. package/dist/modes/acp/server.d.ts +55 -0
  477. package/dist/modes/acp/server.d.ts.map +1 -0
  478. package/dist/modes/acp/server.js +244 -0
  479. package/dist/modes/acp/server.js.map +1 -0
  480. package/dist/modes/acp/translate.d.ts +37 -0
  481. package/dist/modes/acp/translate.d.ts.map +1 -0
  482. package/dist/modes/acp/translate.js +50 -0
  483. package/dist/modes/acp/translate.js.map +1 -0
  484. package/dist/modes/index.d.ts +1 -0
  485. package/dist/modes/index.d.ts.map +1 -1
  486. package/dist/modes/index.js +1 -0
  487. package/dist/modes/index.js.map +1 -1
  488. package/dist/modes/interactive/components/assistant-message.d.ts +29 -0
  489. package/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
  490. package/dist/modes/interactive/components/assistant-message.js +182 -23
  491. package/dist/modes/interactive/components/assistant-message.js.map +1 -1
  492. package/dist/modes/interactive/components/bash-execution.d.ts +7 -0
  493. package/dist/modes/interactive/components/bash-execution.d.ts.map +1 -1
  494. package/dist/modes/interactive/components/bash-execution.js +20 -0
  495. package/dist/modes/interactive/components/bash-execution.js.map +1 -1
  496. package/dist/modes/interactive/components/custom-editor.d.ts +9 -4
  497. package/dist/modes/interactive/components/custom-editor.d.ts.map +1 -1
  498. package/dist/modes/interactive/components/custom-editor.js +20 -9
  499. package/dist/modes/interactive/components/custom-editor.js.map +1 -1
  500. package/dist/modes/interactive/components/extension-selector.d.ts +7 -1
  501. package/dist/modes/interactive/components/extension-selector.d.ts.map +1 -1
  502. package/dist/modes/interactive/components/extension-selector.js +7 -3
  503. package/dist/modes/interactive/components/extension-selector.js.map +1 -1
  504. package/dist/modes/interactive/components/footer.d.ts +19 -0
  505. package/dist/modes/interactive/components/footer.d.ts.map +1 -1
  506. package/dist/modes/interactive/components/footer.js +70 -10
  507. package/dist/modes/interactive/components/footer.js.map +1 -1
  508. package/dist/modes/interactive/components/model-selector.d.ts +10 -3
  509. package/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
  510. package/dist/modes/interactive/components/model-selector.js +56 -14
  511. package/dist/modes/interactive/components/model-selector.js.map +1 -1
  512. package/dist/modes/interactive/components/permission-preview.d.ts +15 -0
  513. package/dist/modes/interactive/components/permission-preview.d.ts.map +1 -0
  514. package/dist/modes/interactive/components/permission-preview.js +33 -0
  515. package/dist/modes/interactive/components/permission-preview.js.map +1 -0
  516. package/dist/modes/interactive/components/session-selector.d.ts.map +1 -1
  517. package/dist/modes/interactive/components/session-selector.js +2 -2
  518. package/dist/modes/interactive/components/session-selector.js.map +1 -1
  519. package/dist/modes/interactive/components/settings-selector.d.ts +9 -2
  520. package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  521. package/dist/modes/interactive/components/settings-selector.js +128 -56
  522. package/dist/modes/interactive/components/settings-selector.js.map +1 -1
  523. package/dist/modes/interactive/components/settings-submenu.d.ts +72 -0
  524. package/dist/modes/interactive/components/settings-submenu.d.ts.map +1 -0
  525. package/dist/modes/interactive/components/settings-submenu.js +165 -0
  526. package/dist/modes/interactive/components/settings-submenu.js.map +1 -0
  527. package/dist/modes/interactive/components/thinking-selector.d.ts +15 -3
  528. package/dist/modes/interactive/components/thinking-selector.d.ts.map +1 -1
  529. package/dist/modes/interactive/components/thinking-selector.js +74 -17
  530. package/dist/modes/interactive/components/thinking-selector.js.map +1 -1
  531. package/dist/modes/interactive/components/tool-execution.d.ts +23 -1
  532. package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  533. package/dist/modes/interactive/components/tool-execution.js +64 -10
  534. package/dist/modes/interactive/components/tool-execution.js.map +1 -1
  535. package/dist/modes/interactive/external-editor.d.ts.map +1 -1
  536. package/dist/modes/interactive/external-editor.js +2 -1
  537. package/dist/modes/interactive/external-editor.js.map +1 -1
  538. package/dist/modes/interactive/interactive-mode.d.ts +45 -0
  539. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  540. package/dist/modes/interactive/interactive-mode.js +395 -43
  541. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  542. package/dist/modes/interactive/theme/theme.d.ts +12 -0
  543. package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
  544. package/dist/modes/interactive/theme/theme.js +20 -9
  545. package/dist/modes/interactive/theme/theme.js.map +1 -1
  546. package/dist/modes/json-event.d.ts +16 -13
  547. package/dist/modes/json-event.d.ts.map +1 -1
  548. package/dist/modes/json-event.js +20 -6
  549. package/dist/modes/json-event.js.map +1 -1
  550. package/dist/modes/print-mode.d.ts.map +1 -1
  551. package/dist/modes/print-mode.js +27 -12
  552. package/dist/modes/print-mode.js.map +1 -1
  553. package/dist/modes/rpc/rpc-client.d.ts +7 -0
  554. package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
  555. package/dist/modes/rpc/rpc-client.js +7 -0
  556. package/dist/modes/rpc/rpc-client.js.map +1 -1
  557. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  558. package/dist/modes/rpc/rpc-mode.js +3 -0
  559. package/dist/modes/rpc/rpc-mode.js.map +1 -1
  560. package/dist/modes/rpc/rpc-types.d.ts +12 -0
  561. package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
  562. package/dist/modes/rpc/rpc-types.js.map +1 -1
  563. package/dist/package-manager-cli.d.ts +1 -0
  564. package/dist/package-manager-cli.d.ts.map +1 -1
  565. package/dist/package-manager-cli.js +196 -10
  566. package/dist/package-manager-cli.js.map +1 -1
  567. package/dist/utils/frontmatter.d.ts.map +1 -1
  568. package/dist/utils/frontmatter.js +2 -1
  569. package/dist/utils/frontmatter.js.map +1 -1
  570. package/dist/utils/management-http.d.ts +5 -3
  571. package/dist/utils/management-http.d.ts.map +1 -1
  572. package/dist/utils/management-http.js +14 -10
  573. package/dist/utils/management-http.js.map +1 -1
  574. package/dist/utils/shell.d.ts +3 -0
  575. package/dist/utils/shell.d.ts.map +1 -1
  576. package/dist/utils/shell.js +23 -9
  577. package/dist/utils/shell.js.map +1 -1
  578. package/dist/utils/syntax-highlight.d.ts +1 -0
  579. package/dist/utils/syntax-highlight.d.ts.map +1 -1
  580. package/dist/utils/syntax-highlight.js +60 -1
  581. package/dist/utils/syntax-highlight.js.map +1 -1
  582. package/dist/utils/text.d.ts +8 -0
  583. package/dist/utils/text.d.ts.map +1 -0
  584. package/dist/utils/text.js +9 -0
  585. package/dist/utils/text.js.map +1 -0
  586. package/dist/utils/version-check.d.ts.map +1 -1
  587. package/dist/utils/version-check.js +6 -4
  588. package/dist/utils/version-check.js.map +1 -1
  589. package/docs/compaction.md +19 -0
  590. package/docs/custom-provider.md +12 -9
  591. package/docs/environment-variables.md +1 -1
  592. package/docs/extensions.md +35 -7
  593. package/docs/json.md +7 -2
  594. package/docs/keybindings.md +9 -9
  595. package/docs/llama-cpp.md +2 -0
  596. package/docs/models.md +8 -2
  597. package/docs/packages.md +2 -2
  598. package/docs/quickstart.md +1 -1
  599. package/docs/rpc.md +32 -3
  600. package/docs/sdk.md +32 -2
  601. package/docs/settings.md +23 -7
  602. package/docs/skills.md +4 -3
  603. package/docs/terminal-setup.md +23 -7
  604. package/docs/usage.md +10 -5
  605. package/docs/windows.md +24 -2
  606. package/examples/extensions/border-status-editor.ts +1 -1
  607. package/examples/extensions/built-in-tool-renderer.ts +8 -4
  608. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  609. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  610. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  611. package/examples/extensions/git-checkpoint.ts +24 -36
  612. package/examples/extensions/gondolin/package-lock.json +2 -2
  613. package/examples/extensions/gondolin/package.json +1 -1
  614. package/examples/extensions/minimal-mode.ts +2 -2
  615. package/examples/extensions/notify.ts +3 -1
  616. package/examples/extensions/permission-gate.ts +1 -1
  617. package/examples/extensions/plan-mode/index.ts +1 -1
  618. package/examples/extensions/rpc-demo.ts +1 -1
  619. package/examples/extensions/sandbox/package-lock.json +2 -2
  620. package/examples/extensions/sandbox/package.json +1 -1
  621. package/examples/extensions/subagent/README.md +1 -1
  622. package/examples/extensions/subagent/index.ts +6 -1
  623. package/examples/extensions/titlebar-spinner.ts +1 -1
  624. package/examples/extensions/with-deps/package-lock.json +2 -2
  625. package/examples/extensions/with-deps/package.json +1 -1
  626. package/examples/rpc-extension-ui.ts +1 -1
  627. package/examples/sdk/06-extensions.ts +1 -1
  628. package/examples/sdk/README.md +1 -1
  629. package/npm-shrinkwrap.json +439 -405
  630. package/package.json +17 -17
  631. package/dist/server/create-harness.d.ts +0 -26
  632. package/dist/server/create-harness.d.ts.map +0 -1
  633. package/dist/server/create-harness.js +0 -107
  634. package/dist/server/create-harness.js.map +0 -1
@@ -0,0 +1,99 @@
1
+ /**
2
+ * Persistent MCP tool metadata, so search and describe answer without a running
3
+ * server. Everything the cache holds is producible from a past connection, which is
4
+ * what makes "no server needed to search" a property rather than a convention.
5
+ *
6
+ * One file for every server, not one per server: the whole cache is read once at
7
+ * session start and written whole, and a handful of servers never makes that costly.
8
+ */
9
+ import { createHash } from "node:crypto";
10
+ import { existsSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
11
+ import { dirname, join } from "node:path";
12
+ import { getAgentDir } from "../../config.js";
13
+ const EMPTY = { version: 1, servers: {} };
14
+ const SEPARATOR = "|";
15
+ export function mcpCachePath() {
16
+ return join(getAgentDir(), "mcp", "metadata.json");
17
+ }
18
+ /**
19
+ * Everything that can change which tools a server publishes, and nothing that cannot.
20
+ * A version bump in `args` produces a new key and therefore reads no stale entries,
21
+ * while `capabilities` or `idleTimeoutMinutes` leave the key alone.
22
+ */
23
+ function transportIdentity(transport) {
24
+ if (transport.kind === "stdio") {
25
+ // Env values, not just keys: a server that switches which token or mode it runs
26
+ // under can publish a different tool list under the same command line.
27
+ const env = Object.entries(transport.env)
28
+ .sort(([a], [b]) => a.localeCompare(b))
29
+ .map(([key, value]) => `${key}=${value}`)
30
+ .join(",");
31
+ return ["stdio", transport.command, transport.args.join(" "), env, transport.cwd ?? ""].join(SEPARATOR);
32
+ }
33
+ const headers = Object.entries(transport.headers)
34
+ .sort(([a], [b]) => a.localeCompare(b))
35
+ .map(([key, value]) => `${key}=${value}`)
36
+ .join(",");
37
+ return ["http", transport.url, headers, transport.bearerTokenEnv ?? ""].join(SEPARATOR);
38
+ }
39
+ /**
40
+ * The name is part of the key, not only the launch spec. Two servers can share a
41
+ * command (the same binary run twice under different names) and would otherwise
42
+ * collide, the second silently overwriting the first's cached tools.
43
+ */
44
+ export function serverCacheKey(server) {
45
+ const identity = [server.name, transportIdentity(server.transport)].join(SEPARATOR);
46
+ return createHash("sha256").update(identity).digest("hex").slice(0, 32);
47
+ }
48
+ export class McpMetadataCache {
49
+ file;
50
+ path;
51
+ constructor(path = mcpCachePath()) {
52
+ this.path = path;
53
+ this.file = McpMetadataCache.read(path);
54
+ }
55
+ static read(path) {
56
+ if (!existsSync(path))
57
+ return structuredClone(EMPTY);
58
+ try {
59
+ const parsed = JSON.parse(readFileSync(path, "utf-8"));
60
+ if (parsed?.version !== 1 || typeof parsed.servers !== "object" || parsed.servers === null) {
61
+ return structuredClone(EMPTY);
62
+ }
63
+ return parsed;
64
+ }
65
+ catch {
66
+ return structuredClone(EMPTY);
67
+ }
68
+ }
69
+ get(server) {
70
+ return this.file.servers[serverCacheKey(server)]?.tools ?? [];
71
+ }
72
+ all() {
73
+ return Object.values(this.file.servers).flatMap((entry) => entry.tools);
74
+ }
75
+ set(server, tools) {
76
+ this.file.servers[serverCacheKey(server)] = { name: server.name, tools: [...tools] };
77
+ }
78
+ /** Forget every server absent from `configured`, so a removed server stops appearing in search. */
79
+ prune(configured) {
80
+ const live = new Set(configured.map(serverCacheKey));
81
+ for (const key of Object.keys(this.file.servers)) {
82
+ if (!live.has(key))
83
+ delete this.file.servers[key];
84
+ }
85
+ }
86
+ save() {
87
+ const directory = dirname(this.path);
88
+ mkdirSync(directory, { recursive: true });
89
+ const temporary = join(directory, `.metadata.${process.pid}.tmp`);
90
+ try {
91
+ writeFileSync(temporary, JSON.stringify(this.file));
92
+ renameSync(temporary, this.path);
93
+ }
94
+ catch {
95
+ rmSync(temporary, { force: true });
96
+ }
97
+ }
98
+ }
99
+ //# sourceMappingURL=metadata-cache.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"metadata-cache.js","sourceRoot":"","sources":["../../../src/core/mcp/metadata-cache.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Persistent MCP tool metadata, so search and describe answer without a running\n * server. Everything the cache holds is producible from a past connection, which is\n * what makes \"no server needed to search\" a property rather than a convention.\n *\n * One file for every server, not one per server: the whole cache is read once at\n * session start and written whole, and a handful of servers never makes that costly.\n */\n\nimport { createHash } from \"node:crypto\";\nimport { existsSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport { getAgentDir } from \"../../config.ts\";\nimport type { CachedTool, McpServerConfig, McpTransport } from \"./types.ts\";\n\ninterface CacheFile {\n\tversion: 1;\n\tservers: Record<string, { name: string; tools: CachedTool[] }>;\n}\n\nconst EMPTY: CacheFile = { version: 1, servers: {} };\n\nconst SEPARATOR = \"|\";\n\nexport function mcpCachePath(): string {\n\treturn join(getAgentDir(), \"mcp\", \"metadata.json\");\n}\n\n/**\n * Everything that can change which tools a server publishes, and nothing that cannot.\n * A version bump in `args` produces a new key and therefore reads no stale entries,\n * while `capabilities` or `idleTimeoutMinutes` leave the key alone.\n */\nfunction transportIdentity(transport: McpTransport): string {\n\tif (transport.kind === \"stdio\") {\n\t\t// Env values, not just keys: a server that switches which token or mode it runs\n\t\t// under can publish a different tool list under the same command line.\n\t\tconst env = Object.entries(transport.env)\n\t\t\t.sort(([a], [b]) => a.localeCompare(b))\n\t\t\t.map(([key, value]) => `${key}=${value}`)\n\t\t\t.join(\",\");\n\t\treturn [\"stdio\", transport.command, transport.args.join(\" \"), env, transport.cwd ?? \"\"].join(SEPARATOR);\n\t}\n\tconst headers = Object.entries(transport.headers)\n\t\t.sort(([a], [b]) => a.localeCompare(b))\n\t\t.map(([key, value]) => `${key}=${value}`)\n\t\t.join(\",\");\n\treturn [\"http\", transport.url, headers, transport.bearerTokenEnv ?? \"\"].join(SEPARATOR);\n}\n\n/**\n * The name is part of the key, not only the launch spec. Two servers can share a\n * command (the same binary run twice under different names) and would otherwise\n * collide, the second silently overwriting the first's cached tools.\n */\nexport function serverCacheKey(server: McpServerConfig): string {\n\tconst identity = [server.name, transportIdentity(server.transport)].join(SEPARATOR);\n\treturn createHash(\"sha256\").update(identity).digest(\"hex\").slice(0, 32);\n}\n\nexport class McpMetadataCache {\n\tprivate file: CacheFile;\n\tprivate readonly path: string;\n\n\tconstructor(path: string = mcpCachePath()) {\n\t\tthis.path = path;\n\t\tthis.file = McpMetadataCache.read(path);\n\t}\n\n\tprivate static read(path: string): CacheFile {\n\t\tif (!existsSync(path)) return structuredClone(EMPTY);\n\t\ttry {\n\t\t\tconst parsed = JSON.parse(readFileSync(path, \"utf-8\")) as CacheFile;\n\t\t\tif (parsed?.version !== 1 || typeof parsed.servers !== \"object\" || parsed.servers === null) {\n\t\t\t\treturn structuredClone(EMPTY);\n\t\t\t}\n\t\t\treturn parsed;\n\t\t} catch {\n\t\t\treturn structuredClone(EMPTY);\n\t\t}\n\t}\n\n\tget(server: McpServerConfig): CachedTool[] {\n\t\treturn this.file.servers[serverCacheKey(server)]?.tools ?? [];\n\t}\n\n\tall(): CachedTool[] {\n\t\treturn Object.values(this.file.servers).flatMap((entry) => entry.tools);\n\t}\n\n\tset(server: McpServerConfig, tools: readonly CachedTool[]): void {\n\t\tthis.file.servers[serverCacheKey(server)] = { name: server.name, tools: [...tools] };\n\t}\n\n\t/** Forget every server absent from `configured`, so a removed server stops appearing in search. */\n\tprune(configured: readonly McpServerConfig[]): void {\n\t\tconst live = new Set(configured.map(serverCacheKey));\n\t\tfor (const key of Object.keys(this.file.servers)) {\n\t\t\tif (!live.has(key)) delete this.file.servers[key];\n\t\t}\n\t}\n\n\tsave(): void {\n\t\tconst directory = dirname(this.path);\n\t\tmkdirSync(directory, { recursive: true });\n\t\tconst temporary = join(directory, `.metadata.${process.pid}.tmp`);\n\t\ttry {\n\t\t\twriteFileSync(temporary, JSON.stringify(this.file));\n\t\t\trenameSync(temporary, this.path);\n\t\t} catch {\n\t\t\trmSync(temporary, { force: true });\n\t\t}\n\t}\n}\n"]}
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Shared entry point for `apex-code mcp auth <server>` and the TUI `/mcp auth`
3
+ * command. Resolves the named server from the merged MCP config, checks it is an
4
+ * OAuth-configured HTTP server, and runs the flow. UI-agnostic: callers supply the
5
+ * line printer and the browser opener.
6
+ */
7
+ import type { CredentialStore } from "@earendil-works/pi-ai";
8
+ import { runMcpOAuthFlow } from "./flow.ts";
9
+ export interface AuthorizeServerOptions {
10
+ serverName: string;
11
+ /** Project root for `.mcp.json` discovery. Default: process.cwd(). */
12
+ cwd?: string;
13
+ /** Credential sink. Default: the host store (`AuthStorage.create()`). */
14
+ credentials?: CredentialStore;
15
+ openBrowser?: (url: string) => void;
16
+ /** Injectable flow, for tests. */
17
+ flow?: typeof runMcpOAuthFlow;
18
+ log: (line: string) => void;
19
+ }
20
+ export declare function authorizeConfiguredServer(options: AuthorizeServerOptions): Promise<void>;
21
+ //# sourceMappingURL=authorize.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"authorize.d.ts","sourceRoot":"","sources":["../../../../src/core/mcp/oauth/authorize.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAG7D,OAAO,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAE5C,MAAM,WAAW,sBAAsB;IACtC,UAAU,EAAE,MAAM,CAAC;IACnB,sEAAsE;IACtE,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,yEAAyE;IACzE,WAAW,CAAC,EAAE,eAAe,CAAC;IAC9B,WAAW,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IACpC,kCAAkC;IAClC,IAAI,CAAC,EAAE,OAAO,eAAe,CAAC;IAC9B,GAAG,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CAC5B;AAED,wBAAsB,yBAAyB,CAAC,OAAO,EAAE,sBAAsB,GAAG,OAAO,CAAC,IAAI,CAAC,CA0B9F","sourcesContent":["/**\n * Shared entry point for `apex-code mcp auth <server>` and the TUI `/mcp auth`\n * command. Resolves the named server from the merged MCP config, checks it is an\n * OAuth-configured HTTP server, and runs the flow. UI-agnostic: callers supply the\n * line printer and the browser opener.\n */\n\nimport { join } from \"node:path\";\nimport type { CredentialStore } from \"@earendil-works/pi-ai\";\nimport { AuthStorage } from \"../../auth-storage.ts\";\nimport { loadMcpConfig, PROJECT_CONFIG_FILENAME } from \"../config.ts\";\nimport { runMcpOAuthFlow } from \"./flow.ts\";\n\nexport interface AuthorizeServerOptions {\n\tserverName: string;\n\t/** Project root for `.mcp.json` discovery. Default: process.cwd(). */\n\tcwd?: string;\n\t/** Credential sink. Default: the host store (`AuthStorage.create()`). */\n\tcredentials?: CredentialStore;\n\topenBrowser?: (url: string) => void;\n\t/** Injectable flow, for tests. */\n\tflow?: typeof runMcpOAuthFlow;\n\tlog: (line: string) => void;\n}\n\nexport async function authorizeConfiguredServer(options: AuthorizeServerOptions): Promise<void> {\n\tconst { servers } = loadMcpConfig({ projectPath: join(options.cwd ?? process.cwd(), PROJECT_CONFIG_FILENAME) });\n\tconst server = servers.get(options.serverName);\n\tif (!server) {\n\t\tconst names = [...servers.keys()];\n\t\tthrow new Error(\n\t\t\t`MCP server \"${options.serverName}\" is not configured. Configured servers: ${names.join(\", \") || \"none\"}. Add it to .mcp.json first.`,\n\t\t);\n\t}\n\tif (server.transport.kind !== \"http\") {\n\t\tthrow new Error(`MCP server \"${options.serverName}\" is a stdio server; OAuth applies to HTTP servers only.`);\n\t}\n\tif (server.auth !== \"oauth\") {\n\t\tthrow new Error(\n\t\t\t`MCP server \"${options.serverName}\" is not configured for OAuth. Add \"auth\": \"oauth\" to its entry.`,\n\t\t);\n\t}\n\tconst result = await (options.flow ?? runMcpOAuthFlow)({\n\t\tserverName: options.serverName,\n\t\tserverUrl: server.transport.url,\n\t\tcredentials: options.credentials ?? AuthStorage.create(),\n\t\tdeps: { print: options.log, openBrowser: options.openBrowser ?? (() => {}) },\n\t});\n\toptions.log(\n\t\t`Authorized \"${options.serverName}\". Token stored; expires ${new Date(result.expiresAt).toISOString()}.`,\n\t);\n}\n"]}
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Shared entry point for `apex-code mcp auth <server>` and the TUI `/mcp auth`
3
+ * command. Resolves the named server from the merged MCP config, checks it is an
4
+ * OAuth-configured HTTP server, and runs the flow. UI-agnostic: callers supply the
5
+ * line printer and the browser opener.
6
+ */
7
+ import { join } from "node:path";
8
+ import { AuthStorage } from "../../auth-storage.js";
9
+ import { loadMcpConfig, PROJECT_CONFIG_FILENAME } from "../config.js";
10
+ import { runMcpOAuthFlow } from "./flow.js";
11
+ export async function authorizeConfiguredServer(options) {
12
+ const { servers } = loadMcpConfig({ projectPath: join(options.cwd ?? process.cwd(), PROJECT_CONFIG_FILENAME) });
13
+ const server = servers.get(options.serverName);
14
+ if (!server) {
15
+ const names = [...servers.keys()];
16
+ throw new Error(`MCP server "${options.serverName}" is not configured. Configured servers: ${names.join(", ") || "none"}. Add it to .mcp.json first.`);
17
+ }
18
+ if (server.transport.kind !== "http") {
19
+ throw new Error(`MCP server "${options.serverName}" is a stdio server; OAuth applies to HTTP servers only.`);
20
+ }
21
+ if (server.auth !== "oauth") {
22
+ throw new Error(`MCP server "${options.serverName}" is not configured for OAuth. Add "auth": "oauth" to its entry.`);
23
+ }
24
+ const result = await (options.flow ?? runMcpOAuthFlow)({
25
+ serverName: options.serverName,
26
+ serverUrl: server.transport.url,
27
+ credentials: options.credentials ?? AuthStorage.create(),
28
+ deps: { print: options.log, openBrowser: options.openBrowser ?? (() => { }) },
29
+ });
30
+ options.log(`Authorized "${options.serverName}". Token stored; expires ${new Date(result.expiresAt).toISOString()}.`);
31
+ }
32
+ //# sourceMappingURL=authorize.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"authorize.js","sourceRoot":"","sources":["../../../../src/core/mcp/oauth/authorize.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AACpD,OAAO,EAAE,aAAa,EAAE,uBAAuB,EAAE,MAAM,cAAc,CAAC;AACtE,OAAO,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAc5C,MAAM,CAAC,KAAK,UAAU,yBAAyB,CAAC,OAA+B,EAAiB;IAC/F,MAAM,EAAE,OAAO,EAAE,GAAG,aAAa,CAAC,EAAE,WAAW,EAAE,IAAI,CAAC,OAAO,CAAC,GAAG,IAAI,OAAO,CAAC,GAAG,EAAE,EAAE,uBAAuB,CAAC,EAAE,CAAC,CAAC;IAChH,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;IAC/C,IAAI,CAAC,MAAM,EAAE,CAAC;QACb,MAAM,KAAK,GAAG,CAAC,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC;QAClC,MAAM,IAAI,KAAK,CACd,eAAe,OAAO,CAAC,UAAU,4CAA4C,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,MAAM,8BAA8B,CACrI,CAAC;IACH,CAAC;IACD,IAAI,MAAM,CAAC,SAAS,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;QACtC,MAAM,IAAI,KAAK,CAAC,eAAe,OAAO,CAAC,UAAU,0DAA0D,CAAC,CAAC;IAC9G,CAAC;IACD,IAAI,MAAM,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;QAC7B,MAAM,IAAI,KAAK,CACd,eAAe,OAAO,CAAC,UAAU,kEAAkE,CACnG,CAAC;IACH,CAAC;IACD,MAAM,MAAM,GAAG,MAAM,CAAC,OAAO,CAAC,IAAI,IAAI,eAAe,CAAC,CAAC;QACtD,UAAU,EAAE,OAAO,CAAC,UAAU;QAC9B,SAAS,EAAE,MAAM,CAAC,SAAS,CAAC,GAAG;QAC/B,WAAW,EAAE,OAAO,CAAC,WAAW,IAAI,WAAW,CAAC,MAAM,EAAE;QACxD,IAAI,EAAE,EAAE,KAAK,EAAE,OAAO,CAAC,GAAG,EAAE,WAAW,EAAE,OAAO,CAAC,WAAW,IAAI,CAAC,GAAG,EAAE,CAAC,EAAC,CAAC,CAAC,EAAE;KAC5E,CAAC,CAAC;IACH,OAAO,CAAC,GAAG,CACV,eAAe,OAAO,CAAC,UAAU,4BAA4B,IAAI,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,WAAW,EAAE,GAAG,CACxG,CAAC;AAAA,CACF","sourcesContent":["/**\n * Shared entry point for `apex-code mcp auth <server>` and the TUI `/mcp auth`\n * command. Resolves the named server from the merged MCP config, checks it is an\n * OAuth-configured HTTP server, and runs the flow. UI-agnostic: callers supply the\n * line printer and the browser opener.\n */\n\nimport { join } from \"node:path\";\nimport type { CredentialStore } from \"@earendil-works/pi-ai\";\nimport { AuthStorage } from \"../../auth-storage.ts\";\nimport { loadMcpConfig, PROJECT_CONFIG_FILENAME } from \"../config.ts\";\nimport { runMcpOAuthFlow } from \"./flow.ts\";\n\nexport interface AuthorizeServerOptions {\n\tserverName: string;\n\t/** Project root for `.mcp.json` discovery. Default: process.cwd(). */\n\tcwd?: string;\n\t/** Credential sink. Default: the host store (`AuthStorage.create()`). */\n\tcredentials?: CredentialStore;\n\topenBrowser?: (url: string) => void;\n\t/** Injectable flow, for tests. */\n\tflow?: typeof runMcpOAuthFlow;\n\tlog: (line: string) => void;\n}\n\nexport async function authorizeConfiguredServer(options: AuthorizeServerOptions): Promise<void> {\n\tconst { servers } = loadMcpConfig({ projectPath: join(options.cwd ?? process.cwd(), PROJECT_CONFIG_FILENAME) });\n\tconst server = servers.get(options.serverName);\n\tif (!server) {\n\t\tconst names = [...servers.keys()];\n\t\tthrow new Error(\n\t\t\t`MCP server \"${options.serverName}\" is not configured. Configured servers: ${names.join(\", \") || \"none\"}. Add it to .mcp.json first.`,\n\t\t);\n\t}\n\tif (server.transport.kind !== \"http\") {\n\t\tthrow new Error(`MCP server \"${options.serverName}\" is a stdio server; OAuth applies to HTTP servers only.`);\n\t}\n\tif (server.auth !== \"oauth\") {\n\t\tthrow new Error(\n\t\t\t`MCP server \"${options.serverName}\" is not configured for OAuth. Add \"auth\": \"oauth\" to its entry.`,\n\t\t);\n\t}\n\tconst result = await (options.flow ?? runMcpOAuthFlow)({\n\t\tserverName: options.serverName,\n\t\tserverUrl: server.transport.url,\n\t\tcredentials: options.credentials ?? AuthStorage.create(),\n\t\tdeps: { print: options.log, openBrowser: options.openBrowser ?? (() => {}) },\n\t});\n\toptions.log(\n\t\t`Authorized \"${options.serverName}\". Token stored; expires ${new Date(result.expiresAt).toISOString()}.`,\n\t);\n}\n"]}
@@ -0,0 +1,27 @@
1
+ /**
2
+ * OAuth discovery for MCP servers: the `WWW-Authenticate` challenge, protected-resource
3
+ * metadata (RFC 9728), and authorization-server metadata (RFC 8414), in the order the
4
+ * MCP authorization spec expects.
5
+ *
6
+ * Metadata URLs must be https. The one exception is loopback, because the authorization
7
+ * code flow itself speaks loopback http and local development servers ride on it.
8
+ */
9
+ export type FetchLike = typeof fetch;
10
+ export declare class OAuthDiscoveryError extends Error {
11
+ }
12
+ export interface AuthorizationServerMetadata {
13
+ authorizationEndpoint: string;
14
+ tokenEndpoint: string;
15
+ registrationEndpoint: string | undefined;
16
+ }
17
+ export declare function oauthErrorMessage(error: unknown): string;
18
+ /** Pulls `resource_metadata` out of a `WWW-Authenticate: Bearer …` challenge, if present. */
19
+ export declare function parseWwwAuthenticate(value: string | undefined): {
20
+ resourceMetadata?: string;
21
+ };
22
+ export declare function discoverAuthorizationServer(options: {
23
+ serverUrl: string;
24
+ wwwAuthenticate?: string;
25
+ fetch?: FetchLike;
26
+ }): Promise<AuthorizationServerMetadata>;
27
+ //# sourceMappingURL=discover.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"discover.d.ts","sourceRoot":"","sources":["../../../../src/core/mcp/oauth/discover.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,MAAM,MAAM,SAAS,GAAG,OAAO,KAAK,CAAC;AAErC,qBAAa,mBAAoB,SAAQ,KAAK;CAAG;AAEjD,MAAM,WAAW,2BAA2B;IAC3C,qBAAqB,EAAE,MAAM,CAAC;IAC9B,aAAa,EAAE,MAAM,CAAC;IACtB,oBAAoB,EAAE,MAAM,GAAG,SAAS,CAAC;CACzC;AAKD,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAExD;AAMD,+FAA6F;AAC7F,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG;IAAE,gBAAgB,CAAC,EAAE,MAAM,CAAA;CAAE,CAM7F;AAkDD,wBAAsB,2BAA2B,CAAC,OAAO,EAAE;IAC1D,SAAS,EAAE,MAAM,CAAC;IAClB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,KAAK,CAAC,EAAE,SAAS,CAAC;CAClB,GAAG,OAAO,CAAC,2BAA2B,CAAC,CAsCvC","sourcesContent":["/**\n * OAuth discovery for MCP servers: the `WWW-Authenticate` challenge, protected-resource\n * metadata (RFC 9728), and authorization-server metadata (RFC 8414), in the order the\n * MCP authorization spec expects.\n *\n * Metadata URLs must be https. The one exception is loopback, because the authorization\n * code flow itself speaks loopback http and local development servers ride on it.\n */\n\nexport type FetchLike = typeof fetch;\n\nexport class OAuthDiscoveryError extends Error {}\n\nexport interface AuthorizationServerMetadata {\n\tauthorizationEndpoint: string;\n\ttokenEndpoint: string;\n\tregistrationEndpoint: string | undefined;\n}\n\nconst PRM_WELL_KNOWN = \".well-known/oauth-protected-resource\";\nconst AS_WELL_KNOWN = \".well-known/oauth-authorization-server\";\n\nexport function oauthErrorMessage(error: unknown): string {\n\treturn error instanceof Error ? error.message : String(error);\n}\n\nfunction isLoopback(url: URL): boolean {\n\treturn url.hostname === \"127.0.0.1\" || url.hostname === \"localhost\" || url.hostname === \"::1\";\n}\n\n/** Pulls `resource_metadata` out of a `WWW-Authenticate: Bearer …` challenge, if present. */\nexport function parseWwwAuthenticate(value: string | undefined): { resourceMetadata?: string } {\n\tif (!value) return {};\n\tconst challenge = /^Bearer\\s+(.*)$/i.exec(value.trim());\n\tif (!challenge) return {};\n\tconst resourceMetadata = /resource_metadata\\s*=\\s*\"([^\"]+)\"/i.exec(challenge[1])?.[1];\n\treturn resourceMetadata ? { resourceMetadata } : {};\n}\n\n/** RFC 9728: the well-known URI is inserted before the path, with the bare origin as fallback. */\nfunction wellKnownUrls(resource: URL, suffix: string): URL[] {\n\tconst pathAware = new URL(resource);\n\tpathAware.pathname = `/${suffix}${resource.pathname === \"/\" ? \"\" : resource.pathname}`;\n\tconst origin = new URL(resource);\n\torigin.pathname = `/${suffix}`;\n\treturn [pathAware, origin];\n}\n\nasync function fetchJson(url: URL, fetch: FetchLike): Promise<Record<string, unknown>> {\n\tif (url.protocol !== \"https:\" && !isLoopback(url)) {\n\t\tthrow new OAuthDiscoveryError(`OAuth metadata URLs must be https (loopback excepted): ${url}`);\n\t}\n\tlet response: Response;\n\ttry {\n\t\tresponse = await fetch(url, { redirect: \"error\" });\n\t} catch (error) {\n\t\tthrow new OAuthDiscoveryError(`could not fetch OAuth metadata from ${url}: ${oauthErrorMessage(error)}`);\n\t}\n\tif (!response.ok) throw new OAuthDiscoveryError(`OAuth metadata at ${url} answered HTTP ${response.status}`);\n\ttry {\n\t\tconst parsed: unknown = await response.json();\n\t\tif (typeof parsed !== \"object\" || parsed === null) throw new Error(\"not an object\");\n\t\treturn parsed as Record<string, unknown>;\n\t} catch {\n\t\tthrow new OAuthDiscoveryError(`OAuth metadata at ${url} is not a JSON object`);\n\t}\n}\n\nfunction requireEndpoint(metadata: Record<string, unknown>, key: string, label: string): string {\n\tconst value = metadata[key];\n\tif (typeof value !== \"string\" || value === \"\") {\n\t\tthrow new OAuthDiscoveryError(`authorization server metadata is missing ${label}`);\n\t}\n\treturn value;\n}\n\nfunction toAuthorizationServerMetadata(raw: Record<string, unknown>): AuthorizationServerMetadata {\n\treturn {\n\t\tauthorizationEndpoint: requireEndpoint(raw, \"authorization_endpoint\", \"an authorization endpoint\"),\n\t\ttokenEndpoint: requireEndpoint(raw, \"token_endpoint\", \"a token endpoint\"),\n\t\tregistrationEndpoint:\n\t\t\ttypeof raw.registration_endpoint === \"string\" && raw.registration_endpoint !== \"\"\n\t\t\t\t? raw.registration_endpoint\n\t\t\t\t: undefined,\n\t};\n}\n\nexport async function discoverAuthorizationServer(options: {\n\tserverUrl: string;\n\twwwAuthenticate?: string;\n\tfetch?: FetchLike;\n}): Promise<AuthorizationServerMetadata> {\n\tconst fetch = options.fetch ?? globalThis.fetch;\n\tconst serverUrl = new URL(options.serverUrl);\n\tif (serverUrl.protocol !== \"https:\" && !isLoopback(serverUrl)) {\n\t\tthrow new OAuthDiscoveryError(`an OAuth-protected MCP server must be https or loopback: ${options.serverUrl}`);\n\t}\n\n\tlet authorizationServers: URL[] = [];\n\tconst fromChallenge = parseWwwAuthenticate(options.wwwAuthenticate).resourceMetadata;\n\tconst prmCandidates = fromChallenge ? [new URL(fromChallenge)] : wellKnownUrls(serverUrl, PRM_WELL_KNOWN);\n\tfor (const candidate of prmCandidates) {\n\t\ttry {\n\t\t\tconst prm = await fetchJson(candidate, fetch);\n\t\t\tif (Array.isArray(prm.authorization_servers)) {\n\t\t\t\tauthorizationServers = prm.authorization_servers\n\t\t\t\t\t.filter((entry): entry is string => typeof entry === \"string\")\n\t\t\t\t\t.map((entry) => new URL(entry));\n\t\t\t\tbreak;\n\t\t\t}\n\t\t} catch (error) {\n\t\t\t// An explicit challenge URL is the server speaking; do not silently skip it.\n\t\t\tif (fromChallenge !== undefined && candidate.href === fromChallenge) throw error;\n\t\t}\n\t}\n\n\tconst asCandidates = authorizationServers.length > 0 ? authorizationServers : [serverUrl];\n\tlet lastError: unknown;\n\tfor (const candidate of asCandidates) {\n\t\ttry {\n\t\t\tconst wellKnown = new URL(`/${AS_WELL_KNOWN}`, candidate.origin);\n\t\t\treturn toAuthorizationServerMetadata(await fetchJson(wellKnown, fetch));\n\t\t} catch (error) {\n\t\t\tlastError = error;\n\t\t}\n\t}\n\tthrow lastError instanceof OAuthDiscoveryError\n\t\t? lastError\n\t\t: new OAuthDiscoveryError(`no authorization server metadata found for ${options.serverUrl}`);\n}\n"]}
@@ -0,0 +1,116 @@
1
+ /**
2
+ * OAuth discovery for MCP servers: the `WWW-Authenticate` challenge, protected-resource
3
+ * metadata (RFC 9728), and authorization-server metadata (RFC 8414), in the order the
4
+ * MCP authorization spec expects.
5
+ *
6
+ * Metadata URLs must be https. The one exception is loopback, because the authorization
7
+ * code flow itself speaks loopback http and local development servers ride on it.
8
+ */
9
+ export class OAuthDiscoveryError extends Error {
10
+ }
11
+ const PRM_WELL_KNOWN = ".well-known/oauth-protected-resource";
12
+ const AS_WELL_KNOWN = ".well-known/oauth-authorization-server";
13
+ export function oauthErrorMessage(error) {
14
+ return error instanceof Error ? error.message : String(error);
15
+ }
16
+ function isLoopback(url) {
17
+ return url.hostname === "127.0.0.1" || url.hostname === "localhost" || url.hostname === "::1";
18
+ }
19
+ /** Pulls `resource_metadata` out of a `WWW-Authenticate: Bearer …` challenge, if present. */
20
+ export function parseWwwAuthenticate(value) {
21
+ if (!value)
22
+ return {};
23
+ const challenge = /^Bearer\s+(.*)$/i.exec(value.trim());
24
+ if (!challenge)
25
+ return {};
26
+ const resourceMetadata = /resource_metadata\s*=\s*"([^"]+)"/i.exec(challenge[1])?.[1];
27
+ return resourceMetadata ? { resourceMetadata } : {};
28
+ }
29
+ /** RFC 9728: the well-known URI is inserted before the path, with the bare origin as fallback. */
30
+ function wellKnownUrls(resource, suffix) {
31
+ const pathAware = new URL(resource);
32
+ pathAware.pathname = `/${suffix}${resource.pathname === "/" ? "" : resource.pathname}`;
33
+ const origin = new URL(resource);
34
+ origin.pathname = `/${suffix}`;
35
+ return [pathAware, origin];
36
+ }
37
+ async function fetchJson(url, fetch) {
38
+ if (url.protocol !== "https:" && !isLoopback(url)) {
39
+ throw new OAuthDiscoveryError(`OAuth metadata URLs must be https (loopback excepted): ${url}`);
40
+ }
41
+ let response;
42
+ try {
43
+ response = await fetch(url, { redirect: "error" });
44
+ }
45
+ catch (error) {
46
+ throw new OAuthDiscoveryError(`could not fetch OAuth metadata from ${url}: ${oauthErrorMessage(error)}`);
47
+ }
48
+ if (!response.ok)
49
+ throw new OAuthDiscoveryError(`OAuth metadata at ${url} answered HTTP ${response.status}`);
50
+ try {
51
+ const parsed = await response.json();
52
+ if (typeof parsed !== "object" || parsed === null)
53
+ throw new Error("not an object");
54
+ return parsed;
55
+ }
56
+ catch {
57
+ throw new OAuthDiscoveryError(`OAuth metadata at ${url} is not a JSON object`);
58
+ }
59
+ }
60
+ function requireEndpoint(metadata, key, label) {
61
+ const value = metadata[key];
62
+ if (typeof value !== "string" || value === "") {
63
+ throw new OAuthDiscoveryError(`authorization server metadata is missing ${label}`);
64
+ }
65
+ return value;
66
+ }
67
+ function toAuthorizationServerMetadata(raw) {
68
+ return {
69
+ authorizationEndpoint: requireEndpoint(raw, "authorization_endpoint", "an authorization endpoint"),
70
+ tokenEndpoint: requireEndpoint(raw, "token_endpoint", "a token endpoint"),
71
+ registrationEndpoint: typeof raw.registration_endpoint === "string" && raw.registration_endpoint !== ""
72
+ ? raw.registration_endpoint
73
+ : undefined,
74
+ };
75
+ }
76
+ export async function discoverAuthorizationServer(options) {
77
+ const fetch = options.fetch ?? globalThis.fetch;
78
+ const serverUrl = new URL(options.serverUrl);
79
+ if (serverUrl.protocol !== "https:" && !isLoopback(serverUrl)) {
80
+ throw new OAuthDiscoveryError(`an OAuth-protected MCP server must be https or loopback: ${options.serverUrl}`);
81
+ }
82
+ let authorizationServers = [];
83
+ const fromChallenge = parseWwwAuthenticate(options.wwwAuthenticate).resourceMetadata;
84
+ const prmCandidates = fromChallenge ? [new URL(fromChallenge)] : wellKnownUrls(serverUrl, PRM_WELL_KNOWN);
85
+ for (const candidate of prmCandidates) {
86
+ try {
87
+ const prm = await fetchJson(candidate, fetch);
88
+ if (Array.isArray(prm.authorization_servers)) {
89
+ authorizationServers = prm.authorization_servers
90
+ .filter((entry) => typeof entry === "string")
91
+ .map((entry) => new URL(entry));
92
+ break;
93
+ }
94
+ }
95
+ catch (error) {
96
+ // An explicit challenge URL is the server speaking; do not silently skip it.
97
+ if (fromChallenge !== undefined && candidate.href === fromChallenge)
98
+ throw error;
99
+ }
100
+ }
101
+ const asCandidates = authorizationServers.length > 0 ? authorizationServers : [serverUrl];
102
+ let lastError;
103
+ for (const candidate of asCandidates) {
104
+ try {
105
+ const wellKnown = new URL(`/${AS_WELL_KNOWN}`, candidate.origin);
106
+ return toAuthorizationServerMetadata(await fetchJson(wellKnown, fetch));
107
+ }
108
+ catch (error) {
109
+ lastError = error;
110
+ }
111
+ }
112
+ throw lastError instanceof OAuthDiscoveryError
113
+ ? lastError
114
+ : new OAuthDiscoveryError(`no authorization server metadata found for ${options.serverUrl}`);
115
+ }
116
+ //# sourceMappingURL=discover.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"discover.js","sourceRoot":"","sources":["../../../../src/core/mcp/oauth/discover.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * OAuth discovery for MCP servers: the `WWW-Authenticate` challenge, protected-resource\n * metadata (RFC 9728), and authorization-server metadata (RFC 8414), in the order the\n * MCP authorization spec expects.\n *\n * Metadata URLs must be https. The one exception is loopback, because the authorization\n * code flow itself speaks loopback http and local development servers ride on it.\n */\n\nexport type FetchLike = typeof fetch;\n\nexport class OAuthDiscoveryError extends Error {}\n\nexport interface AuthorizationServerMetadata {\n\tauthorizationEndpoint: string;\n\ttokenEndpoint: string;\n\tregistrationEndpoint: string | undefined;\n}\n\nconst PRM_WELL_KNOWN = \".well-known/oauth-protected-resource\";\nconst AS_WELL_KNOWN = \".well-known/oauth-authorization-server\";\n\nexport function oauthErrorMessage(error: unknown): string {\n\treturn error instanceof Error ? error.message : String(error);\n}\n\nfunction isLoopback(url: URL): boolean {\n\treturn url.hostname === \"127.0.0.1\" || url.hostname === \"localhost\" || url.hostname === \"::1\";\n}\n\n/** Pulls `resource_metadata` out of a `WWW-Authenticate: Bearer …` challenge, if present. */\nexport function parseWwwAuthenticate(value: string | undefined): { resourceMetadata?: string } {\n\tif (!value) return {};\n\tconst challenge = /^Bearer\\s+(.*)$/i.exec(value.trim());\n\tif (!challenge) return {};\n\tconst resourceMetadata = /resource_metadata\\s*=\\s*\"([^\"]+)\"/i.exec(challenge[1])?.[1];\n\treturn resourceMetadata ? { resourceMetadata } : {};\n}\n\n/** RFC 9728: the well-known URI is inserted before the path, with the bare origin as fallback. */\nfunction wellKnownUrls(resource: URL, suffix: string): URL[] {\n\tconst pathAware = new URL(resource);\n\tpathAware.pathname = `/${suffix}${resource.pathname === \"/\" ? \"\" : resource.pathname}`;\n\tconst origin = new URL(resource);\n\torigin.pathname = `/${suffix}`;\n\treturn [pathAware, origin];\n}\n\nasync function fetchJson(url: URL, fetch: FetchLike): Promise<Record<string, unknown>> {\n\tif (url.protocol !== \"https:\" && !isLoopback(url)) {\n\t\tthrow new OAuthDiscoveryError(`OAuth metadata URLs must be https (loopback excepted): ${url}`);\n\t}\n\tlet response: Response;\n\ttry {\n\t\tresponse = await fetch(url, { redirect: \"error\" });\n\t} catch (error) {\n\t\tthrow new OAuthDiscoveryError(`could not fetch OAuth metadata from ${url}: ${oauthErrorMessage(error)}`);\n\t}\n\tif (!response.ok) throw new OAuthDiscoveryError(`OAuth metadata at ${url} answered HTTP ${response.status}`);\n\ttry {\n\t\tconst parsed: unknown = await response.json();\n\t\tif (typeof parsed !== \"object\" || parsed === null) throw new Error(\"not an object\");\n\t\treturn parsed as Record<string, unknown>;\n\t} catch {\n\t\tthrow new OAuthDiscoveryError(`OAuth metadata at ${url} is not a JSON object`);\n\t}\n}\n\nfunction requireEndpoint(metadata: Record<string, unknown>, key: string, label: string): string {\n\tconst value = metadata[key];\n\tif (typeof value !== \"string\" || value === \"\") {\n\t\tthrow new OAuthDiscoveryError(`authorization server metadata is missing ${label}`);\n\t}\n\treturn value;\n}\n\nfunction toAuthorizationServerMetadata(raw: Record<string, unknown>): AuthorizationServerMetadata {\n\treturn {\n\t\tauthorizationEndpoint: requireEndpoint(raw, \"authorization_endpoint\", \"an authorization endpoint\"),\n\t\ttokenEndpoint: requireEndpoint(raw, \"token_endpoint\", \"a token endpoint\"),\n\t\tregistrationEndpoint:\n\t\t\ttypeof raw.registration_endpoint === \"string\" && raw.registration_endpoint !== \"\"\n\t\t\t\t? raw.registration_endpoint\n\t\t\t\t: undefined,\n\t};\n}\n\nexport async function discoverAuthorizationServer(options: {\n\tserverUrl: string;\n\twwwAuthenticate?: string;\n\tfetch?: FetchLike;\n}): Promise<AuthorizationServerMetadata> {\n\tconst fetch = options.fetch ?? globalThis.fetch;\n\tconst serverUrl = new URL(options.serverUrl);\n\tif (serverUrl.protocol !== \"https:\" && !isLoopback(serverUrl)) {\n\t\tthrow new OAuthDiscoveryError(`an OAuth-protected MCP server must be https or loopback: ${options.serverUrl}`);\n\t}\n\n\tlet authorizationServers: URL[] = [];\n\tconst fromChallenge = parseWwwAuthenticate(options.wwwAuthenticate).resourceMetadata;\n\tconst prmCandidates = fromChallenge ? [new URL(fromChallenge)] : wellKnownUrls(serverUrl, PRM_WELL_KNOWN);\n\tfor (const candidate of prmCandidates) {\n\t\ttry {\n\t\t\tconst prm = await fetchJson(candidate, fetch);\n\t\t\tif (Array.isArray(prm.authorization_servers)) {\n\t\t\t\tauthorizationServers = prm.authorization_servers\n\t\t\t\t\t.filter((entry): entry is string => typeof entry === \"string\")\n\t\t\t\t\t.map((entry) => new URL(entry));\n\t\t\t\tbreak;\n\t\t\t}\n\t\t} catch (error) {\n\t\t\t// An explicit challenge URL is the server speaking; do not silently skip it.\n\t\t\tif (fromChallenge !== undefined && candidate.href === fromChallenge) throw error;\n\t\t}\n\t}\n\n\tconst asCandidates = authorizationServers.length > 0 ? authorizationServers : [serverUrl];\n\tlet lastError: unknown;\n\tfor (const candidate of asCandidates) {\n\t\ttry {\n\t\t\tconst wellKnown = new URL(`/${AS_WELL_KNOWN}`, candidate.origin);\n\t\t\treturn toAuthorizationServerMetadata(await fetchJson(wellKnown, fetch));\n\t\t} catch (error) {\n\t\t\tlastError = error;\n\t\t}\n\t}\n\tthrow lastError instanceof OAuthDiscoveryError\n\t\t? lastError\n\t\t: new OAuthDiscoveryError(`no authorization server metadata found for ${options.serverUrl}`);\n}\n"]}
@@ -0,0 +1,31 @@
1
+ /**
2
+ * The user-initiated OAuth authorization flow for one MCP server: authorization-code
3
+ * with PKCE against a loopback callback listener that exists only for the duration of
4
+ * the flow. Per ADR 0023's posture, nothing here is reachable from a tool call — the
5
+ * command is the only entry point.
6
+ */
7
+ import type { CredentialStore } from "@earendil-works/pi-ai";
8
+ import { type FetchLike } from "./discover.ts";
9
+ export declare class OAuthFlowError extends Error {
10
+ }
11
+ export interface McpOAuthFlowDeps {
12
+ print: (line: string) => void;
13
+ openBrowser: (url: string) => void;
14
+ fetch: FetchLike;
15
+ now: () => number;
16
+ timeoutMs: number;
17
+ }
18
+ export interface McpOAuthFlowOptions {
19
+ serverName: string;
20
+ serverUrl: string;
21
+ credentials: CredentialStore;
22
+ /** A manually registered client id; skips dynamic registration when present. */
23
+ clientId?: string;
24
+ clientSecret?: string;
25
+ deps?: Partial<McpOAuthFlowDeps>;
26
+ }
27
+ export interface McpOAuthFlowResult {
28
+ expiresAt: number;
29
+ }
30
+ export declare function runMcpOAuthFlow(options: McpOAuthFlowOptions): Promise<McpOAuthFlowResult>;
31
+ //# sourceMappingURL=flow.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"flow.d.ts","sourceRoot":"","sources":["../../../../src/core/mcp/oauth/flow.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,OAAO,KAAK,EAAc,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACzE,OAAO,EAA+B,KAAK,SAAS,EAA0C,MAAM,eAAe,CAAC;AAMpH,qBAAa,cAAe,SAAQ,KAAK;CAAG;AAE5C,MAAM,WAAW,gBAAgB;IAChC,KAAK,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC9B,WAAW,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IACnC,KAAK,EAAE,SAAS,CAAC;IACjB,GAAG,EAAE,MAAM,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CAClB;AAOD,MAAM,WAAW,mBAAmB;IACnC,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,eAAe,CAAC;IAC7B,gFAAgF;IAChF,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,IAAI,CAAC,EAAE,OAAO,CAAC,gBAAgB,CAAC,CAAC;CACjC;AAED,MAAM,WAAW,kBAAkB;IAClC,SAAS,EAAE,MAAM,CAAC;CAClB;AAuED,wBAAsB,eAAe,CAAC,OAAO,EAAE,mBAAmB,GAAG,OAAO,CAAC,kBAAkB,CAAC,CA+F/F","sourcesContent":["/**\n * The user-initiated OAuth authorization flow for one MCP server: authorization-code\n * with PKCE against a loopback callback listener that exists only for the duration of\n * the flow. Per ADR 0023's posture, nothing here is reachable from a tool call — the\n * command is the only entry point.\n */\n\nimport { randomBytes } from \"node:crypto\";\nimport { createServer, type IncomingMessage, type Server, type ServerResponse } from \"node:http\";\nimport type { Credential, CredentialStore } from \"@earendil-works/pi-ai\";\nimport { discoverAuthorizationServer, type FetchLike, OAuthDiscoveryError, oauthErrorMessage } from \"./discover.ts\";\nimport { mcpCredentialKey } from \"./mcp-token.ts\";\nimport { createPkcePair } from \"./pkce.ts\";\nimport { registerClient } from \"./register.ts\";\nimport { exchangeAuthorizationCode, TokenEndpointError } from \"./token-client.ts\";\n\nexport class OAuthFlowError extends Error {}\n\nexport interface McpOAuthFlowDeps {\n\tprint: (line: string) => void;\n\topenBrowser: (url: string) => void;\n\tfetch: FetchLike;\n\tnow: () => number;\n\ttimeoutMs: number;\n}\n\nconst DEFAULT_TIMEOUT_MS = 5 * 60_000;\n/** Store the token as expiring a minute early, so in-flight calls never ride it to death. */\nconst EXPIRY_SKEW_MS = 60_000;\nconst CALLBACK_PATH = \"/callback\";\n\nexport interface McpOAuthFlowOptions {\n\tserverName: string;\n\tserverUrl: string;\n\tcredentials: CredentialStore;\n\t/** A manually registered client id; skips dynamic registration when present. */\n\tclientId?: string;\n\tclientSecret?: string;\n\tdeps?: Partial<McpOAuthFlowDeps>;\n}\n\nexport interface McpOAuthFlowResult {\n\texpiresAt: number;\n}\n\n/**\n * Accepts exactly one callback carrying the issued `state`, then stops. A request with\n * the wrong state is answered 400 and the listener stays available for the real one;\n * after the right one, the socket is closed before the flow proceeds to the exchange.\n */\nclass LoopbackCallback {\n\treadonly state = randomBytes(16).toString(\"base64url\");\n\tredirectUri = \"\";\n\tprivate server: Server | undefined;\n\tprivate resolveCode: ((code: string) => void) | undefined;\n\n\tasync start(): Promise<void> {\n\t\tconst server = createServer((request, response) => this.handle(request, response));\n\t\tawait new Promise<void>((resolve) => server.listen(0, \"127.0.0.1\", resolve));\n\t\tconst address = server.address();\n\t\tif (address === null || typeof address === \"string\") {\n\t\t\tthrow new OAuthFlowError(\"could not bind the loopback callback listener\");\n\t\t}\n\t\tthis.server = server;\n\t\tthis.redirectUri = `http://127.0.0.1:${address.port}${CALLBACK_PATH}`;\n\t}\n\n\tprivate handle(request: IncomingMessage, response: ServerResponse): void {\n\t\tconst respond = (status: number, body: string) => {\n\t\t\tresponse.writeHead(status, { \"content-type\": \"text/plain\" });\n\t\t\tresponse.end(body);\n\t\t};\n\t\tconst port = this.server?.address();\n\t\tconst origin = `http://127.0.0.1:${port !== null && typeof port === \"object\" ? port.port : 0}`;\n\t\tconst url = new URL(String(request.url), origin);\n\t\tif (url.pathname !== CALLBACK_PATH) {\n\t\t\trespond(404, \"not found\");\n\t\t\treturn;\n\t\t}\n\t\tif (url.searchParams.get(\"state\") !== this.state) {\n\t\t\trespond(400, \"invalid state\");\n\t\t\treturn;\n\t\t}\n\t\tconst code = url.searchParams.get(\"code\");\n\t\tif (!code) {\n\t\t\trespond(400, \"missing code\");\n\t\t\treturn;\n\t\t}\n\t\trespond(200, \"Authorization complete. You can close this tab.\");\n\t\tconst resolve = this.resolveCode;\n\t\tthis.resolveCode = undefined;\n\t\tresolve?.(code);\n\t}\n\n\t/** Resolves with the authorization code, or rejects when the flow times out. */\n\tcode(timeoutMs: number): Promise<string> {\n\t\treturn new Promise<string>((resolve, reject) => {\n\t\t\tconst timer = setTimeout(() => {\n\t\t\t\treject(new OAuthFlowError(`timed out after ${timeoutMs} ms waiting for authorization to complete`));\n\t\t\t}, timeoutMs);\n\t\t\tthis.resolveCode = (code) => {\n\t\t\t\tclearTimeout(timer);\n\t\t\t\tresolve(code);\n\t\t\t};\n\t\t});\n\t}\n\n\tclose(): Promise<void> {\n\t\tconst server = this.server;\n\t\tif (!server) return Promise.resolve();\n\t\treturn new Promise((resolve) => server.close(() => resolve()));\n\t}\n}\n\nexport async function runMcpOAuthFlow(options: McpOAuthFlowOptions): Promise<McpOAuthFlowResult> {\n\tconst deps: McpOAuthFlowDeps = {\n\t\tprint: options.deps?.print ?? ((line: string) => console.log(line)),\n\t\topenBrowser: options.deps?.openBrowser ?? (() => {}),\n\t\tfetch: options.deps?.fetch ?? globalThis.fetch,\n\t\tnow: options.deps?.now ?? Date.now,\n\t\ttimeoutMs: options.deps?.timeoutMs ?? DEFAULT_TIMEOUT_MS,\n\t};\n\tconst callback = new LoopbackCallback();\n\ttry {\n\t\tawait callback.start();\n\n\t\tlet wwwAuthenticate: string | undefined;\n\t\ttry {\n\t\t\tconst probe = await deps.fetch(new URL(options.serverUrl), { redirect: \"error\" });\n\t\t\twwwAuthenticate = probe.headers.get(\"www-authenticate\") ?? undefined;\n\t\t} catch {\n\t\t\t// The probe is advisory; discovery falls back to well-known URIs.\n\t\t}\n\t\tconst metadata = await discoverAuthorizationServer({\n\t\t\tserverUrl: options.serverUrl,\n\t\t\twwwAuthenticate,\n\t\t\tfetch: deps.fetch,\n\t\t});\n\n\t\tlet clientId = options.clientId;\n\t\tif (!clientId) {\n\t\t\tif (!metadata.registrationEndpoint) {\n\t\t\t\tthrow new OAuthFlowError(\n\t\t\t\t\t`The authorization server for \"${options.serverName}\" supports no dynamic client registration, so this needs manual client registration: register a client with redirect URI ${callback.redirectUri} and pass its client id.`,\n\t\t\t\t);\n\t\t\t}\n\t\t\tconst registered = await registerClient({\n\t\t\t\tregistrationEndpoint: new URL(metadata.registrationEndpoint),\n\t\t\t\tclientName: `Apex Code — MCP server \"${options.serverName}\"`,\n\t\t\t\tredirectUris: [callback.redirectUri],\n\t\t\t\tfetch: deps.fetch,\n\t\t\t});\n\t\t\tclientId = registered.clientId;\n\t\t}\n\n\t\tconst pkce = createPkcePair();\n\t\tconst authorizeUrl = new URL(metadata.authorizationEndpoint);\n\t\tauthorizeUrl.searchParams.set(\"response_type\", \"code\");\n\t\tauthorizeUrl.searchParams.set(\"client_id\", clientId);\n\t\tauthorizeUrl.searchParams.set(\"redirect_uri\", callback.redirectUri);\n\t\tauthorizeUrl.searchParams.set(\"code_challenge\", pkce.challenge);\n\t\tauthorizeUrl.searchParams.set(\"code_challenge_method\", \"S256\");\n\t\tauthorizeUrl.searchParams.set(\"state\", callback.state);\n\t\tauthorizeUrl.searchParams.set(\"resource\", options.serverUrl);\n\n\t\tdeps.print(`Authorize \"${options.serverName}\" by opening: ${authorizeUrl}`);\n\t\ttry {\n\t\t\tdeps.openBrowser(authorizeUrl.toString());\n\t\t} catch {\n\t\t\t// The URL is printed either way; opening a browser is best-effort.\n\t\t}\n\n\t\tconst code = await callback.code(deps.timeoutMs);\n\t\tconst token = await exchangeAuthorizationCode({\n\t\t\ttokenEndpoint: new URL(metadata.tokenEndpoint),\n\t\t\tcode,\n\t\t\tverifier: pkce.verifier,\n\t\t\tredirectUri: callback.redirectUri,\n\t\t\tclientId,\n\t\t\tclientSecret: options.clientSecret,\n\t\t\tresource: options.serverUrl,\n\t\t\tfetch: deps.fetch,\n\t\t});\n\n\t\tconst expiresAt = deps.now() + token.expiresInSeconds * 1000 - EXPIRY_SKEW_MS;\n\t\tconst credential: Credential = {\n\t\t\ttype: \"oauth\",\n\t\t\taccess: token.accessToken,\n\t\t\t// An absent rotation keeps the refresh token the server still honors.\n\t\t\trefresh: token.refreshToken ?? \"\",\n\t\t\texpires: expiresAt,\n\t\t\ttokenEndpoint: metadata.tokenEndpoint,\n\t\t\tresource: options.serverUrl,\n\t\t\tclientId,\n\t\t};\n\t\tawait options.credentials.modify(mcpCredentialKey(options.serverName), async () => credential);\n\t\treturn { expiresAt };\n\t} catch (error) {\n\t\tif (\n\t\t\terror instanceof OAuthFlowError ||\n\t\t\terror instanceof OAuthDiscoveryError ||\n\t\t\terror instanceof TokenEndpointError\n\t\t) {\n\t\t\tthrow error;\n\t\t}\n\t\tthrow new OAuthFlowError(`OAuth flow for \"${options.serverName}\" failed: ${oauthErrorMessage(error)}`);\n\t} finally {\n\t\tawait callback.close();\n\t}\n}\n"]}
@@ -0,0 +1,175 @@
1
+ /**
2
+ * The user-initiated OAuth authorization flow for one MCP server: authorization-code
3
+ * with PKCE against a loopback callback listener that exists only for the duration of
4
+ * the flow. Per ADR 0023's posture, nothing here is reachable from a tool call — the
5
+ * command is the only entry point.
6
+ */
7
+ import { randomBytes } from "node:crypto";
8
+ import { createServer } from "node:http";
9
+ import { discoverAuthorizationServer, OAuthDiscoveryError, oauthErrorMessage } from "./discover.js";
10
+ import { mcpCredentialKey } from "./mcp-token.js";
11
+ import { createPkcePair } from "./pkce.js";
12
+ import { registerClient } from "./register.js";
13
+ import { exchangeAuthorizationCode, TokenEndpointError } from "./token-client.js";
14
+ export class OAuthFlowError extends Error {
15
+ }
16
+ const DEFAULT_TIMEOUT_MS = 5 * 60_000;
17
+ /** Store the token as expiring a minute early, so in-flight calls never ride it to death. */
18
+ const EXPIRY_SKEW_MS = 60_000;
19
+ const CALLBACK_PATH = "/callback";
20
+ /**
21
+ * Accepts exactly one callback carrying the issued `state`, then stops. A request with
22
+ * the wrong state is answered 400 and the listener stays available for the real one;
23
+ * after the right one, the socket is closed before the flow proceeds to the exchange.
24
+ */
25
+ class LoopbackCallback {
26
+ state = randomBytes(16).toString("base64url");
27
+ redirectUri = "";
28
+ server;
29
+ resolveCode;
30
+ async start() {
31
+ const server = createServer((request, response) => this.handle(request, response));
32
+ await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve));
33
+ const address = server.address();
34
+ if (address === null || typeof address === "string") {
35
+ throw new OAuthFlowError("could not bind the loopback callback listener");
36
+ }
37
+ this.server = server;
38
+ this.redirectUri = `http://127.0.0.1:${address.port}${CALLBACK_PATH}`;
39
+ }
40
+ handle(request, response) {
41
+ const respond = (status, body) => {
42
+ response.writeHead(status, { "content-type": "text/plain" });
43
+ response.end(body);
44
+ };
45
+ const port = this.server?.address();
46
+ const origin = `http://127.0.0.1:${port !== null && typeof port === "object" ? port.port : 0}`;
47
+ const url = new URL(String(request.url), origin);
48
+ if (url.pathname !== CALLBACK_PATH) {
49
+ respond(404, "not found");
50
+ return;
51
+ }
52
+ if (url.searchParams.get("state") !== this.state) {
53
+ respond(400, "invalid state");
54
+ return;
55
+ }
56
+ const code = url.searchParams.get("code");
57
+ if (!code) {
58
+ respond(400, "missing code");
59
+ return;
60
+ }
61
+ respond(200, "Authorization complete. You can close this tab.");
62
+ const resolve = this.resolveCode;
63
+ this.resolveCode = undefined;
64
+ resolve?.(code);
65
+ }
66
+ /** Resolves with the authorization code, or rejects when the flow times out. */
67
+ code(timeoutMs) {
68
+ return new Promise((resolve, reject) => {
69
+ const timer = setTimeout(() => {
70
+ reject(new OAuthFlowError(`timed out after ${timeoutMs} ms waiting for authorization to complete`));
71
+ }, timeoutMs);
72
+ this.resolveCode = (code) => {
73
+ clearTimeout(timer);
74
+ resolve(code);
75
+ };
76
+ });
77
+ }
78
+ close() {
79
+ const server = this.server;
80
+ if (!server)
81
+ return Promise.resolve();
82
+ return new Promise((resolve) => server.close(() => resolve()));
83
+ }
84
+ }
85
+ export async function runMcpOAuthFlow(options) {
86
+ const deps = {
87
+ print: options.deps?.print ?? ((line) => console.log(line)),
88
+ openBrowser: options.deps?.openBrowser ?? (() => { }),
89
+ fetch: options.deps?.fetch ?? globalThis.fetch,
90
+ now: options.deps?.now ?? Date.now,
91
+ timeoutMs: options.deps?.timeoutMs ?? DEFAULT_TIMEOUT_MS,
92
+ };
93
+ const callback = new LoopbackCallback();
94
+ try {
95
+ await callback.start();
96
+ let wwwAuthenticate;
97
+ try {
98
+ const probe = await deps.fetch(new URL(options.serverUrl), { redirect: "error" });
99
+ wwwAuthenticate = probe.headers.get("www-authenticate") ?? undefined;
100
+ }
101
+ catch {
102
+ // The probe is advisory; discovery falls back to well-known URIs.
103
+ }
104
+ const metadata = await discoverAuthorizationServer({
105
+ serverUrl: options.serverUrl,
106
+ wwwAuthenticate,
107
+ fetch: deps.fetch,
108
+ });
109
+ let clientId = options.clientId;
110
+ if (!clientId) {
111
+ if (!metadata.registrationEndpoint) {
112
+ throw new OAuthFlowError(`The authorization server for "${options.serverName}" supports no dynamic client registration, so this needs manual client registration: register a client with redirect URI ${callback.redirectUri} and pass its client id.`);
113
+ }
114
+ const registered = await registerClient({
115
+ registrationEndpoint: new URL(metadata.registrationEndpoint),
116
+ clientName: `Apex Code — MCP server "${options.serverName}"`,
117
+ redirectUris: [callback.redirectUri],
118
+ fetch: deps.fetch,
119
+ });
120
+ clientId = registered.clientId;
121
+ }
122
+ const pkce = createPkcePair();
123
+ const authorizeUrl = new URL(metadata.authorizationEndpoint);
124
+ authorizeUrl.searchParams.set("response_type", "code");
125
+ authorizeUrl.searchParams.set("client_id", clientId);
126
+ authorizeUrl.searchParams.set("redirect_uri", callback.redirectUri);
127
+ authorizeUrl.searchParams.set("code_challenge", pkce.challenge);
128
+ authorizeUrl.searchParams.set("code_challenge_method", "S256");
129
+ authorizeUrl.searchParams.set("state", callback.state);
130
+ authorizeUrl.searchParams.set("resource", options.serverUrl);
131
+ deps.print(`Authorize "${options.serverName}" by opening: ${authorizeUrl}`);
132
+ try {
133
+ deps.openBrowser(authorizeUrl.toString());
134
+ }
135
+ catch {
136
+ // The URL is printed either way; opening a browser is best-effort.
137
+ }
138
+ const code = await callback.code(deps.timeoutMs);
139
+ const token = await exchangeAuthorizationCode({
140
+ tokenEndpoint: new URL(metadata.tokenEndpoint),
141
+ code,
142
+ verifier: pkce.verifier,
143
+ redirectUri: callback.redirectUri,
144
+ clientId,
145
+ clientSecret: options.clientSecret,
146
+ resource: options.serverUrl,
147
+ fetch: deps.fetch,
148
+ });
149
+ const expiresAt = deps.now() + token.expiresInSeconds * 1000 - EXPIRY_SKEW_MS;
150
+ const credential = {
151
+ type: "oauth",
152
+ access: token.accessToken,
153
+ // An absent rotation keeps the refresh token the server still honors.
154
+ refresh: token.refreshToken ?? "",
155
+ expires: expiresAt,
156
+ tokenEndpoint: metadata.tokenEndpoint,
157
+ resource: options.serverUrl,
158
+ clientId,
159
+ };
160
+ await options.credentials.modify(mcpCredentialKey(options.serverName), async () => credential);
161
+ return { expiresAt };
162
+ }
163
+ catch (error) {
164
+ if (error instanceof OAuthFlowError ||
165
+ error instanceof OAuthDiscoveryError ||
166
+ error instanceof TokenEndpointError) {
167
+ throw error;
168
+ }
169
+ throw new OAuthFlowError(`OAuth flow for "${options.serverName}" failed: ${oauthErrorMessage(error)}`);
170
+ }
171
+ finally {
172
+ await callback.close();
173
+ }
174
+ }
175
+ //# sourceMappingURL=flow.js.map