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,171 @@
1
+ /**
2
+ * One tool for every MCP server, instead of one tool per server tool.
3
+ *
4
+ * Registering each server tool individually costs 150-300 prompt tokens apiece, and
5
+ * two ordinary servers would exceed the whole static-prefix budget before a
6
+ * conversation starts. The proxy costs one announced name and this description,
7
+ * because its schema defers.
8
+ *
9
+ * `search` and `describe` read the disk cache and reach no server. Only a `tool` call
10
+ * connects, which is what makes discovery free and is why the two have separate
11
+ * permission rules (ADR 0025).
12
+ */
13
+ import { wrapToolDefinition } from "../tools/tool-definition-wrapper.js";
14
+ import { createMcpToolContract } from "./contract.js";
15
+ import { McpOAuthRefreshError, McpOAuthRequiredError } from "./oauth/mcp-token.js";
16
+ import { mcpToolSchema } from "./schema.js";
17
+ export const MCP_TOOL_NAME = "mcp";
18
+ const MAX_RESULTS = 25;
19
+ const DESCRIPTION = "Use tools provided by configured MCP servers. " +
20
+ 'Search with {"search":"keyword"}, list one server with {"server":"name"}, ' +
21
+ 'see a tool\'s full schema with {"describe":"server:tool"}, ' +
22
+ 'and call one with {"tool":"server:tool","args":{...}}. ' +
23
+ "Search and describe read a local cache and never start a server, so discovery is free. " +
24
+ "Always describe a tool before calling it for the first time.";
25
+ function text(body) {
26
+ return [{ type: "text", text: body }];
27
+ }
28
+ function qualified(tool) {
29
+ return `${tool.server}:${tool.name}`;
30
+ }
31
+ /** Substring scoring, weighted to the name, which is what the model searched for. */
32
+ function score(tool, needle) {
33
+ const name = qualified(tool).toLowerCase();
34
+ const description = tool.description.toLowerCase();
35
+ if (name === needle)
36
+ return 100;
37
+ if (name.includes(needle))
38
+ return 50;
39
+ if (description.includes(needle))
40
+ return 10;
41
+ return 0;
42
+ }
43
+ function renderList(tools) {
44
+ return tools.map((tool) => `${qualified(tool)}: ${tool.description}`).join("\n");
45
+ }
46
+ function nearest(tools, target) {
47
+ const needle = target.toLowerCase();
48
+ const stem = needle.slice(0, Math.max(3, Math.floor(needle.length * 0.6)));
49
+ return tools.filter((tool) => qualified(tool).toLowerCase().includes(stem)).slice(0, 5);
50
+ }
51
+ export function createMcpToolDefinition(options) {
52
+ const { servers, cache, manager } = options;
53
+ return {
54
+ name: MCP_TOOL_NAME,
55
+ label: "mcp",
56
+ description: DESCRIPTION,
57
+ promptSnippet: "Call tools on configured MCP servers",
58
+ parameters: mcpToolSchema,
59
+ contract: createMcpToolContract(servers),
60
+ async execute(_id, params) {
61
+ if (params.tool)
62
+ return callTool(params.tool, params.args);
63
+ if (params.describe)
64
+ return describeTool(params.describe);
65
+ if (params.search !== undefined)
66
+ return searchTools(params.search, params.server);
67
+ if (params.server)
68
+ return listServer(params.server);
69
+ return {
70
+ content: text('Provide one of "search", "server", "describe", or "tool".'),
71
+ details: { action: "search", server: undefined, tool: undefined },
72
+ };
73
+ },
74
+ };
75
+ function searchTools(needle, serverFilter) {
76
+ const pool = serverFilter ? cache.all().filter((tool) => tool.server === serverFilter) : cache.all();
77
+ const lowered = needle.toLowerCase();
78
+ const ranked = pool
79
+ .map((tool) => ({ tool, rank: score(tool, lowered) }))
80
+ .filter((entry) => entry.rank > 0)
81
+ .sort((a, b) => b.rank - a.rank)
82
+ .slice(0, MAX_RESULTS)
83
+ .map((entry) => entry.tool);
84
+ return {
85
+ content: text(ranked.length > 0
86
+ ? renderList(ranked)
87
+ : `No cached MCP tool matches "${needle}". Configured servers: ${[...servers.keys()].join(", ") || "none"}.`),
88
+ details: { action: "search", server: serverFilter, tool: undefined },
89
+ };
90
+ }
91
+ function listServer(name) {
92
+ if (!servers.has(name)) {
93
+ return {
94
+ content: text(`MCP server "${name}" is not configured. Configured servers: ${[...servers.keys()].join(", ") || "none"}.`),
95
+ details: { action: "list", server: name, tool: undefined },
96
+ };
97
+ }
98
+ const tools = cache.all().filter((tool) => tool.server === name);
99
+ return {
100
+ content: text(tools.length > 0
101
+ ? renderList(tools)
102
+ : `No cached tools for "${name}" yet. Tools are cached the first time the server is called, so call a known tool on it, or set "lifecycle": "eager" for this server in .mcp.json to have them cached at startup.`),
103
+ details: { action: "list", server: name, tool: undefined },
104
+ };
105
+ }
106
+ function describeTool(target) {
107
+ const found = cache.all().find((tool) => qualified(tool) === target);
108
+ if (!found) {
109
+ const suggestions = nearest(cache.all(), target);
110
+ return {
111
+ content: text(suggestions.length > 0
112
+ ? `No cached tool named "${target}". Did you mean:\n${renderList(suggestions)}`
113
+ : `No cached tool named "${target}". Search with {"search":"keyword"}.`),
114
+ details: { action: "describe", server: undefined, tool: target },
115
+ };
116
+ }
117
+ return {
118
+ content: text(`${qualified(found)}\n${found.description}\n\nInput schema:\n${JSON.stringify(found.inputSchema, null, 2)}`),
119
+ details: { action: "describe", server: found.server, tool: target },
120
+ };
121
+ }
122
+ async function callTool(target, args) {
123
+ const separator = target.indexOf(":");
124
+ const details = { action: "call", server: undefined, tool: target };
125
+ if (separator <= 0) {
126
+ return { content: text(`Tool must be qualified as "server:tool". Received "${target}".`), details };
127
+ }
128
+ const serverName = target.slice(0, separator);
129
+ const toolName = target.slice(separator + 1);
130
+ details.server = serverName;
131
+ if (!servers.has(serverName)) {
132
+ return {
133
+ content: text(`MCP server "${serverName}" is not configured. Configured servers: ${[...servers.keys()].join(", ") || "none"}.`),
134
+ details,
135
+ };
136
+ }
137
+ try {
138
+ const result = await manager.withConnection(serverName, async (connection) => {
139
+ // An empty list is a fact about the server, not a failed read. Keeping the
140
+ // previous entry would leave search advertising tools that no longer exist.
141
+ const fresh = await connection.listTools();
142
+ cache.set(servers.get(serverName), fresh);
143
+ cache.save();
144
+ return connection.callTool(toolName, args);
145
+ });
146
+ // `AgentToolResult` has no error flag; a server-reported failure is marked in
147
+ // the text so the model can see it failed without the turn being aborted.
148
+ const content = result.isError
149
+ ? text(`Tool reported an error:\n${result.content.map((part) => part.text).join("\n")}`)
150
+ : result.content;
151
+ return { content, details };
152
+ }
153
+ catch (error) {
154
+ // OAuth failures already carry the one action that helps (`apex-code mcp auth
155
+ // <server>`); the generic "unavailable" wrapper would bury it.
156
+ if (error instanceof McpOAuthRequiredError || error instanceof McpOAuthRefreshError) {
157
+ return { content: text(error.message), details };
158
+ }
159
+ // A failed server is reported, not retried: the manager holds it in `failed`
160
+ // with a backoff window so one broken server cannot cost every call a spawn.
161
+ return {
162
+ content: text(`MCP server "${serverName}" is unavailable: ${error instanceof Error ? error.message : String(error)}`),
163
+ details,
164
+ };
165
+ }
166
+ }
167
+ }
168
+ export function createMcpTool(options) {
169
+ return wrapToolDefinition(createMcpToolDefinition(options));
170
+ }
171
+ //# sourceMappingURL=mcp-tool.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mcp-tool.js","sourceRoot":"","sources":["../../../src/core/mcp/mcp-tool.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * One tool for every MCP server, instead of one tool per server tool.\n *\n * Registering each server tool individually costs 150-300 prompt tokens apiece, and\n * two ordinary servers would exceed the whole static-prefix budget before a\n * conversation starts. The proxy costs one announced name and this description,\n * because its schema defers.\n *\n * `search` and `describe` read the disk cache and reach no server. Only a `tool` call\n * connects, which is what makes discovery free and is why the two have separate\n * permission rules (ADR 0025).\n */\n\nimport type { AgentToolResult } from \"apex-code-agent-core\";\nimport type { ApexToolDefinition } from \"../tools/contract.ts\";\nimport { wrapToolDefinition } from \"../tools/tool-definition-wrapper.ts\";\nimport { createMcpToolContract } from \"./contract.ts\";\nimport type { McpMetadataCache } from \"./metadata-cache.ts\";\nimport { McpOAuthRefreshError, McpOAuthRequiredError } from \"./oauth/mcp-token.ts\";\nimport { type McpToolDetails, type McpToolParams, mcpToolSchema } from \"./schema.ts\";\nimport type { McpServerManager } from \"./server-manager.ts\";\nimport type { CachedTool, McpServerConfig } from \"./types.ts\";\n\nexport const MCP_TOOL_NAME = \"mcp\";\n\nconst MAX_RESULTS = 25;\n\nconst DESCRIPTION =\n\t\"Use tools provided by configured MCP servers. \" +\n\t'Search with {\"search\":\"keyword\"}, list one server with {\"server\":\"name\"}, ' +\n\t'see a tool\\'s full schema with {\"describe\":\"server:tool\"}, ' +\n\t'and call one with {\"tool\":\"server:tool\",\"args\":{...}}. ' +\n\t\"Search and describe read a local cache and never start a server, so discovery is free. \" +\n\t\"Always describe a tool before calling it for the first time.\";\n\nfunction text(body: string): AgentToolResult<McpToolDetails>[\"content\"] {\n\treturn [{ type: \"text\", text: body }];\n}\n\nfunction qualified(tool: CachedTool): string {\n\treturn `${tool.server}:${tool.name}`;\n}\n\n/** Substring scoring, weighted to the name, which is what the model searched for. */\nfunction score(tool: CachedTool, needle: string): number {\n\tconst name = qualified(tool).toLowerCase();\n\tconst description = tool.description.toLowerCase();\n\tif (name === needle) return 100;\n\tif (name.includes(needle)) return 50;\n\tif (description.includes(needle)) return 10;\n\treturn 0;\n}\n\nfunction renderList(tools: readonly CachedTool[]): string {\n\treturn tools.map((tool) => `${qualified(tool)}: ${tool.description}`).join(\"\\n\");\n}\n\nfunction nearest(tools: readonly CachedTool[], target: string): CachedTool[] {\n\tconst needle = target.toLowerCase();\n\tconst stem = needle.slice(0, Math.max(3, Math.floor(needle.length * 0.6)));\n\treturn tools.filter((tool) => qualified(tool).toLowerCase().includes(stem)).slice(0, 5);\n}\n\nexport interface McpToolOptions {\n\tservers: ReadonlyMap<string, McpServerConfig>;\n\tcache: McpMetadataCache;\n\tmanager: McpServerManager;\n}\n\nexport function createMcpToolDefinition(\n\toptions: McpToolOptions,\n): ApexToolDefinition<typeof mcpToolSchema, McpToolDetails> {\n\tconst { servers, cache, manager } = options;\n\n\treturn {\n\t\tname: MCP_TOOL_NAME,\n\t\tlabel: \"mcp\",\n\t\tdescription: DESCRIPTION,\n\t\tpromptSnippet: \"Call tools on configured MCP servers\",\n\t\tparameters: mcpToolSchema,\n\t\tcontract: createMcpToolContract(servers),\n\t\tasync execute(_id, params: McpToolParams): Promise<AgentToolResult<McpToolDetails>> {\n\t\t\tif (params.tool) return callTool(params.tool, params.args);\n\t\t\tif (params.describe) return describeTool(params.describe);\n\t\t\tif (params.search !== undefined) return searchTools(params.search, params.server);\n\t\t\tif (params.server) return listServer(params.server);\n\n\t\t\treturn {\n\t\t\t\tcontent: text('Provide one of \"search\", \"server\", \"describe\", or \"tool\".'),\n\t\t\t\tdetails: { action: \"search\", server: undefined, tool: undefined },\n\t\t\t};\n\t\t},\n\t};\n\n\tfunction searchTools(needle: string, serverFilter?: string): AgentToolResult<McpToolDetails> {\n\t\tconst pool = serverFilter ? cache.all().filter((tool) => tool.server === serverFilter) : cache.all();\n\t\tconst lowered = needle.toLowerCase();\n\t\tconst ranked = pool\n\t\t\t.map((tool) => ({ tool, rank: score(tool, lowered) }))\n\t\t\t.filter((entry) => entry.rank > 0)\n\t\t\t.sort((a, b) => b.rank - a.rank)\n\t\t\t.slice(0, MAX_RESULTS)\n\t\t\t.map((entry) => entry.tool);\n\n\t\treturn {\n\t\t\tcontent: text(\n\t\t\t\tranked.length > 0\n\t\t\t\t\t? renderList(ranked)\n\t\t\t\t\t: `No cached MCP tool matches \"${needle}\". Configured servers: ${[...servers.keys()].join(\", \") || \"none\"}.`,\n\t\t\t),\n\t\t\tdetails: { action: \"search\", server: serverFilter, tool: undefined },\n\t\t};\n\t}\n\n\tfunction listServer(name: string): AgentToolResult<McpToolDetails> {\n\t\tif (!servers.has(name)) {\n\t\t\treturn {\n\t\t\t\tcontent: text(\n\t\t\t\t\t`MCP server \"${name}\" is not configured. Configured servers: ${[...servers.keys()].join(\", \") || \"none\"}.`,\n\t\t\t\t),\n\t\t\t\tdetails: { action: \"list\", server: name, tool: undefined },\n\t\t\t};\n\t\t}\n\n\t\tconst tools = cache.all().filter((tool) => tool.server === name);\n\t\treturn {\n\t\t\tcontent: text(\n\t\t\t\ttools.length > 0\n\t\t\t\t\t? renderList(tools)\n\t\t\t\t\t: `No cached tools for \"${name}\" yet. Tools are cached the first time the server is called, so call a known tool on it, or set \"lifecycle\": \"eager\" for this server in .mcp.json to have them cached at startup.`,\n\t\t\t),\n\t\t\tdetails: { action: \"list\", server: name, tool: undefined },\n\t\t};\n\t}\n\n\tfunction describeTool(target: string): AgentToolResult<McpToolDetails> {\n\t\tconst found = cache.all().find((tool) => qualified(tool) === target);\n\t\tif (!found) {\n\t\t\tconst suggestions = nearest(cache.all(), target);\n\t\t\treturn {\n\t\t\t\tcontent: text(\n\t\t\t\t\tsuggestions.length > 0\n\t\t\t\t\t\t? `No cached tool named \"${target}\". Did you mean:\\n${renderList(suggestions)}`\n\t\t\t\t\t\t: `No cached tool named \"${target}\". Search with {\"search\":\"keyword\"}.`,\n\t\t\t\t),\n\t\t\t\tdetails: { action: \"describe\", server: undefined, tool: target },\n\t\t\t};\n\t\t}\n\n\t\treturn {\n\t\t\tcontent: text(\n\t\t\t\t`${qualified(found)}\\n${found.description}\\n\\nInput schema:\\n${JSON.stringify(found.inputSchema, null, 2)}`,\n\t\t\t),\n\t\t\tdetails: { action: \"describe\", server: found.server, tool: target },\n\t\t};\n\t}\n\n\tasync function callTool(target: string, args: unknown): Promise<AgentToolResult<McpToolDetails>> {\n\t\tconst separator = target.indexOf(\":\");\n\t\tconst details: McpToolDetails = { action: \"call\", server: undefined, tool: target };\n\t\tif (separator <= 0) {\n\t\t\treturn { content: text(`Tool must be qualified as \"server:tool\". Received \"${target}\".`), details };\n\t\t}\n\n\t\tconst serverName = target.slice(0, separator);\n\t\tconst toolName = target.slice(separator + 1);\n\t\tdetails.server = serverName;\n\n\t\tif (!servers.has(serverName)) {\n\t\t\treturn {\n\t\t\t\tcontent: text(\n\t\t\t\t\t`MCP server \"${serverName}\" is not configured. Configured servers: ${[...servers.keys()].join(\", \") || \"none\"}.`,\n\t\t\t\t),\n\t\t\t\tdetails,\n\t\t\t};\n\t\t}\n\n\t\ttry {\n\t\t\tconst result = await manager.withConnection(serverName, async (connection) => {\n\t\t\t\t// An empty list is a fact about the server, not a failed read. Keeping the\n\t\t\t\t// previous entry would leave search advertising tools that no longer exist.\n\t\t\t\tconst fresh = await connection.listTools();\n\t\t\t\tcache.set(servers.get(serverName) as McpServerConfig, fresh);\n\t\t\t\tcache.save();\n\t\t\t\treturn connection.callTool(toolName, args);\n\t\t\t});\n\n\t\t\t// `AgentToolResult` has no error flag; a server-reported failure is marked in\n\t\t\t// the text so the model can see it failed without the turn being aborted.\n\t\t\tconst content = result.isError\n\t\t\t\t? text(`Tool reported an error:\\n${result.content.map((part) => part.text).join(\"\\n\")}`)\n\t\t\t\t: result.content;\n\t\t\treturn { content, details };\n\t\t} catch (error) {\n\t\t\t// OAuth failures already carry the one action that helps (`apex-code mcp auth\n\t\t\t// <server>`); the generic \"unavailable\" wrapper would bury it.\n\t\t\tif (error instanceof McpOAuthRequiredError || error instanceof McpOAuthRefreshError) {\n\t\t\t\treturn { content: text(error.message), details };\n\t\t\t}\n\t\t\t// A failed server is reported, not retried: the manager holds it in `failed`\n\t\t\t// with a backoff window so one broken server cannot cost every call a spawn.\n\t\t\treturn {\n\t\t\t\tcontent: text(\n\t\t\t\t\t`MCP server \"${serverName}\" is unavailable: ${error instanceof Error ? error.message : String(error)}`,\n\t\t\t\t),\n\t\t\t\tdetails,\n\t\t\t};\n\t\t}\n\t}\n}\n\nexport function createMcpTool(options: McpToolOptions) {\n\treturn wrapToolDefinition(createMcpToolDefinition(options));\n}\n"]}
@@ -0,0 +1,29 @@
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 type { CachedTool, McpServerConfig } from "./types.ts";
10
+ export declare function mcpCachePath(): string;
11
+ /**
12
+ * The name is part of the key, not only the launch spec. Two servers can share a
13
+ * command (the same binary run twice under different names) and would otherwise
14
+ * collide, the second silently overwriting the first's cached tools.
15
+ */
16
+ export declare function serverCacheKey(server: McpServerConfig): string;
17
+ export declare class McpMetadataCache {
18
+ private file;
19
+ private readonly path;
20
+ constructor(path?: string);
21
+ private static read;
22
+ get(server: McpServerConfig): CachedTool[];
23
+ all(): CachedTool[];
24
+ set(server: McpServerConfig, tools: readonly CachedTool[]): void;
25
+ /** Forget every server absent from `configured`, so a removed server stops appearing in search. */
26
+ prune(configured: readonly McpServerConfig[]): void;
27
+ save(): void;
28
+ }
29
+ //# sourceMappingURL=metadata-cache.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"metadata-cache.d.ts","sourceRoot":"","sources":["../../../src/core/mcp/metadata-cache.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAMH,OAAO,KAAK,EAAE,UAAU,EAAE,eAAe,EAAgB,MAAM,YAAY,CAAC;AAW5E,wBAAgB,YAAY,IAAI,MAAM,CAErC;AAwBD;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,eAAe,GAAG,MAAM,CAG9D;AAED,qBAAa,gBAAgB;IAC5B,OAAO,CAAC,IAAI,CAAY;IACxB,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAS;IAE9B,YAAY,IAAI,GAAE,MAAuB,EAGxC;IAED,OAAO,CAAC,MAAM,CAAC,IAAI;IAanB,GAAG,CAAC,MAAM,EAAE,eAAe,GAAG,UAAU,EAAE,CAEzC;IAED,GAAG,IAAI,UAAU,EAAE,CAElB;IAED,GAAG,CAAC,MAAM,EAAE,eAAe,EAAE,KAAK,EAAE,SAAS,UAAU,EAAE,GAAG,IAAI,CAE/D;IAED,mGAAmG;IACnG,KAAK,CAAC,UAAU,EAAE,SAAS,eAAe,EAAE,GAAG,IAAI,CAKlD;IAED,IAAI,IAAI,IAAI,CAUX;CACD","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,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"]}