@portel/photon 1.4.0 → 1.5.1

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 (379) hide show
  1. package/README.md +287 -1160
  2. package/dist/auto-ui/beam.d.ts +9 -0
  3. package/dist/auto-ui/beam.d.ts.map +1 -0
  4. package/dist/auto-ui/beam.js +2381 -0
  5. package/dist/auto-ui/beam.js.map +1 -0
  6. package/dist/auto-ui/components/card.d.ts +13 -0
  7. package/dist/auto-ui/components/card.d.ts.map +1 -0
  8. package/dist/auto-ui/components/card.js +64 -0
  9. package/dist/auto-ui/components/card.js.map +1 -0
  10. package/dist/auto-ui/components/form.d.ts +15 -0
  11. package/dist/auto-ui/components/form.d.ts.map +1 -0
  12. package/dist/auto-ui/components/form.js +72 -0
  13. package/dist/auto-ui/components/form.js.map +1 -0
  14. package/dist/auto-ui/components/list.d.ts +13 -0
  15. package/dist/auto-ui/components/list.d.ts.map +1 -0
  16. package/dist/auto-ui/components/list.js +58 -0
  17. package/dist/auto-ui/components/list.js.map +1 -0
  18. package/dist/auto-ui/components/progress.d.ts +18 -0
  19. package/dist/auto-ui/components/progress.d.ts.map +1 -0
  20. package/dist/auto-ui/components/progress.js +125 -0
  21. package/dist/auto-ui/components/progress.js.map +1 -0
  22. package/dist/auto-ui/components/table.d.ts +13 -0
  23. package/dist/auto-ui/components/table.d.ts.map +1 -0
  24. package/dist/auto-ui/components/table.js +82 -0
  25. package/dist/auto-ui/components/table.js.map +1 -0
  26. package/dist/auto-ui/components/tree.d.ts +13 -0
  27. package/dist/auto-ui/components/tree.d.ts.map +1 -0
  28. package/dist/auto-ui/components/tree.js +61 -0
  29. package/dist/auto-ui/components/tree.js.map +1 -0
  30. package/dist/auto-ui/daemon-tools.d.ts +45 -0
  31. package/dist/auto-ui/daemon-tools.d.ts.map +1 -0
  32. package/dist/auto-ui/daemon-tools.js +580 -0
  33. package/dist/auto-ui/daemon-tools.js.map +1 -0
  34. package/dist/auto-ui/design-system/index.d.ts +21 -0
  35. package/dist/auto-ui/design-system/index.d.ts.map +1 -0
  36. package/dist/auto-ui/design-system/index.js +27 -0
  37. package/dist/auto-ui/design-system/index.js.map +1 -0
  38. package/dist/auto-ui/design-system/tokens.d.ts +9 -0
  39. package/dist/auto-ui/design-system/tokens.d.ts.map +1 -0
  40. package/dist/auto-ui/design-system/tokens.js +27 -0
  41. package/dist/auto-ui/design-system/tokens.js.map +1 -0
  42. package/dist/auto-ui/design-system/transaction-ui.d.ts +70 -0
  43. package/dist/auto-ui/design-system/transaction-ui.d.ts.map +1 -0
  44. package/dist/auto-ui/design-system/transaction-ui.js +982 -0
  45. package/dist/auto-ui/design-system/transaction-ui.js.map +1 -0
  46. package/dist/auto-ui/frontend/index.html +84 -0
  47. package/dist/auto-ui/index.d.ts +21 -0
  48. package/dist/auto-ui/index.d.ts.map +1 -0
  49. package/dist/auto-ui/index.js +25 -0
  50. package/dist/auto-ui/index.js.map +1 -0
  51. package/dist/auto-ui/openapi-generator.d.ts +71 -0
  52. package/dist/auto-ui/openapi-generator.d.ts.map +1 -0
  53. package/dist/auto-ui/openapi-generator.js +223 -0
  54. package/dist/auto-ui/openapi-generator.js.map +1 -0
  55. package/dist/auto-ui/photon-bridge.d.ts +159 -0
  56. package/dist/auto-ui/photon-bridge.d.ts.map +1 -0
  57. package/dist/auto-ui/photon-bridge.js +262 -0
  58. package/dist/auto-ui/photon-bridge.js.map +1 -0
  59. package/dist/auto-ui/photon-host.d.ts +113 -0
  60. package/dist/auto-ui/photon-host.d.ts.map +1 -0
  61. package/dist/auto-ui/photon-host.js +284 -0
  62. package/dist/auto-ui/photon-host.js.map +1 -0
  63. package/dist/auto-ui/platform-compat.d.ts +71 -0
  64. package/dist/auto-ui/platform-compat.d.ts.map +1 -0
  65. package/dist/auto-ui/platform-compat.js +574 -0
  66. package/dist/auto-ui/platform-compat.js.map +1 -0
  67. package/dist/auto-ui/playground-html.d.ts +15 -0
  68. package/dist/auto-ui/playground-html.d.ts.map +1 -0
  69. package/dist/auto-ui/playground-html.js +1113 -0
  70. package/dist/auto-ui/playground-html.js.map +1 -0
  71. package/dist/auto-ui/playground-server.d.ts +7 -0
  72. package/dist/auto-ui/playground-server.d.ts.map +1 -0
  73. package/dist/auto-ui/playground-server.js +840 -0
  74. package/dist/auto-ui/playground-server.js.map +1 -0
  75. package/dist/auto-ui/registry.d.ts +13 -0
  76. package/dist/auto-ui/registry.d.ts.map +1 -0
  77. package/dist/auto-ui/registry.js +62 -0
  78. package/dist/auto-ui/registry.js.map +1 -0
  79. package/dist/auto-ui/renderer.d.ts +14 -0
  80. package/dist/auto-ui/renderer.d.ts.map +1 -0
  81. package/dist/auto-ui/renderer.js +88 -0
  82. package/dist/auto-ui/renderer.js.map +1 -0
  83. package/dist/auto-ui/rendering/components.d.ts +29 -0
  84. package/dist/auto-ui/rendering/components.d.ts.map +1 -0
  85. package/dist/auto-ui/rendering/components.js +773 -0
  86. package/dist/auto-ui/rendering/components.js.map +1 -0
  87. package/dist/auto-ui/rendering/field-analyzer.d.ts +48 -0
  88. package/dist/auto-ui/rendering/field-analyzer.d.ts.map +1 -0
  89. package/dist/auto-ui/rendering/field-analyzer.js +270 -0
  90. package/dist/auto-ui/rendering/field-analyzer.js.map +1 -0
  91. package/dist/auto-ui/rendering/field-renderers.d.ts +64 -0
  92. package/dist/auto-ui/rendering/field-renderers.d.ts.map +1 -0
  93. package/dist/auto-ui/rendering/field-renderers.js +317 -0
  94. package/dist/auto-ui/rendering/field-renderers.js.map +1 -0
  95. package/dist/auto-ui/rendering/index.d.ts +28 -0
  96. package/dist/auto-ui/rendering/index.d.ts.map +1 -0
  97. package/dist/auto-ui/rendering/index.js +60 -0
  98. package/dist/auto-ui/rendering/index.js.map +1 -0
  99. package/dist/auto-ui/rendering/layout-selector.d.ts +48 -0
  100. package/dist/auto-ui/rendering/layout-selector.d.ts.map +1 -0
  101. package/dist/auto-ui/rendering/layout-selector.js +352 -0
  102. package/dist/auto-ui/rendering/layout-selector.js.map +1 -0
  103. package/dist/auto-ui/rendering/template-engine.d.ts +41 -0
  104. package/dist/auto-ui/rendering/template-engine.d.ts.map +1 -0
  105. package/dist/auto-ui/rendering/template-engine.js +238 -0
  106. package/dist/auto-ui/rendering/template-engine.js.map +1 -0
  107. package/dist/auto-ui/streamable-http-transport.d.ts +79 -0
  108. package/dist/auto-ui/streamable-http-transport.d.ts.map +1 -0
  109. package/dist/auto-ui/streamable-http-transport.js +1314 -0
  110. package/dist/auto-ui/streamable-http-transport.js.map +1 -0
  111. package/dist/auto-ui/types.d.ts +310 -0
  112. package/dist/auto-ui/types.d.ts.map +1 -0
  113. package/dist/auto-ui/types.js +71 -0
  114. package/dist/auto-ui/types.js.map +1 -0
  115. package/dist/beam.bundle.js +13506 -0
  116. package/dist/beam.bundle.js.map +7 -0
  117. package/dist/claude-code-plugin.d.ts.map +1 -1
  118. package/dist/claude-code-plugin.js +30 -30
  119. package/dist/claude-code-plugin.js.map +1 -1
  120. package/dist/cli/commands/info.d.ts +11 -0
  121. package/dist/cli/commands/info.d.ts.map +1 -0
  122. package/dist/cli/commands/info.js +313 -0
  123. package/dist/cli/commands/info.js.map +1 -0
  124. package/dist/cli/commands/marketplace.d.ts +11 -0
  125. package/dist/cli/commands/marketplace.d.ts.map +1 -0
  126. package/dist/cli/commands/marketplace.js +198 -0
  127. package/dist/cli/commands/marketplace.js.map +1 -0
  128. package/dist/cli/commands/package-app.d.ts +9 -0
  129. package/dist/cli/commands/package-app.d.ts.map +1 -0
  130. package/dist/cli/commands/package-app.js +191 -0
  131. package/dist/cli/commands/package-app.js.map +1 -0
  132. package/dist/cli/commands/package.d.ts +11 -0
  133. package/dist/cli/commands/package.d.ts.map +1 -0
  134. package/dist/cli/commands/package.js +573 -0
  135. package/dist/cli/commands/package.js.map +1 -0
  136. package/dist/cli-alias.d.ts.map +1 -1
  137. package/dist/cli-alias.js +30 -28
  138. package/dist/cli-alias.js.map +1 -1
  139. package/dist/cli-formatter.d.ts +8 -24
  140. package/dist/cli-formatter.d.ts.map +1 -1
  141. package/dist/cli-formatter.js +8 -325
  142. package/dist/cli-formatter.js.map +1 -1
  143. package/dist/cli.d.ts +15 -1
  144. package/dist/cli.d.ts.map +1 -1
  145. package/dist/cli.js +1157 -1132
  146. package/dist/cli.js.map +1 -1
  147. package/dist/daemon/client.d.ts +81 -0
  148. package/dist/daemon/client.d.ts.map +1 -1
  149. package/dist/daemon/client.js +583 -13
  150. package/dist/daemon/client.js.map +1 -1
  151. package/dist/daemon/manager.d.ts +46 -12
  152. package/dist/daemon/manager.d.ts.map +1 -1
  153. package/dist/daemon/manager.js +102 -61
  154. package/dist/daemon/manager.js.map +1 -1
  155. package/dist/daemon/protocol.d.ts +74 -6
  156. package/dist/daemon/protocol.d.ts.map +1 -1
  157. package/dist/daemon/protocol.js +76 -1
  158. package/dist/daemon/protocol.js.map +1 -1
  159. package/dist/daemon/server.d.ts +6 -6
  160. package/dist/daemon/server.js +778 -117
  161. package/dist/daemon/server.js.map +1 -1
  162. package/dist/daemon/session-manager.d.ts +8 -1
  163. package/dist/daemon/session-manager.d.ts.map +1 -1
  164. package/dist/daemon/session-manager.js +32 -9
  165. package/dist/daemon/session-manager.js.map +1 -1
  166. package/dist/deploy/cloudflare.d.ts +12 -0
  167. package/dist/deploy/cloudflare.d.ts.map +1 -0
  168. package/dist/deploy/cloudflare.js +216 -0
  169. package/dist/deploy/cloudflare.js.map +1 -0
  170. package/dist/index.d.ts +1 -0
  171. package/dist/index.d.ts.map +1 -1
  172. package/dist/index.js +3 -0
  173. package/dist/index.js.map +1 -1
  174. package/dist/loader.d.ts +172 -15
  175. package/dist/loader.d.ts.map +1 -1
  176. package/dist/loader.js +1132 -267
  177. package/dist/loader.js.map +1 -1
  178. package/dist/markdown-utils.d.ts +8 -0
  179. package/dist/markdown-utils.d.ts.map +1 -0
  180. package/dist/markdown-utils.js +63 -0
  181. package/dist/markdown-utils.js.map +1 -0
  182. package/dist/marketplace-manager.d.ts +10 -0
  183. package/dist/marketplace-manager.d.ts.map +1 -1
  184. package/dist/marketplace-manager.js +112 -28
  185. package/dist/marketplace-manager.js.map +1 -1
  186. package/dist/mcp-client.d.ts +9 -0
  187. package/dist/mcp-client.d.ts.map +1 -0
  188. package/dist/mcp-client.js +11 -0
  189. package/dist/mcp-client.js.map +1 -0
  190. package/dist/mcp-elicitation.d.ts +32 -0
  191. package/dist/mcp-elicitation.d.ts.map +1 -0
  192. package/dist/mcp-elicitation.js +26 -0
  193. package/dist/mcp-elicitation.js.map +1 -0
  194. package/dist/path-resolver.d.ts +9 -12
  195. package/dist/path-resolver.d.ts.map +1 -1
  196. package/dist/path-resolver.js +13 -43
  197. package/dist/path-resolver.js.map +1 -1
  198. package/dist/photon-cli-runner.d.ts.map +1 -1
  199. package/dist/photon-cli-runner.js +216 -73
  200. package/dist/photon-cli-runner.js.map +1 -1
  201. package/dist/photon-doc-extractor.d.ts +88 -0
  202. package/dist/photon-doc-extractor.d.ts.map +1 -1
  203. package/dist/photon-doc-extractor.js +536 -27
  204. package/dist/photon-doc-extractor.js.map +1 -1
  205. package/dist/photons/maker.photon.d.ts +182 -0
  206. package/dist/photons/maker.photon.d.ts.map +1 -0
  207. package/dist/photons/maker.photon.js +504 -0
  208. package/dist/photons/maker.photon.js.map +1 -0
  209. package/dist/photons/maker.photon.ts +626 -0
  210. package/dist/photons/marketplace.photon.d.ts +110 -0
  211. package/dist/photons/marketplace.photon.d.ts.map +1 -0
  212. package/dist/photons/marketplace.photon.js +260 -0
  213. package/dist/photons/marketplace.photon.js.map +1 -0
  214. package/dist/photons/marketplace.photon.ts +378 -0
  215. package/dist/photons/tunnel.photon.d.ts +80 -0
  216. package/dist/photons/tunnel.photon.d.ts.map +1 -0
  217. package/dist/photons/tunnel.photon.js +269 -0
  218. package/dist/photons/tunnel.photon.js.map +1 -0
  219. package/dist/photons/tunnel.photon.ts +345 -0
  220. package/dist/security-scanner.d.ts.map +1 -1
  221. package/dist/security-scanner.js +18 -15
  222. package/dist/security-scanner.js.map +1 -1
  223. package/dist/serv/auth/jwt.d.ts +89 -0
  224. package/dist/serv/auth/jwt.d.ts.map +1 -0
  225. package/dist/serv/auth/jwt.js +239 -0
  226. package/dist/serv/auth/jwt.js.map +1 -0
  227. package/dist/serv/auth/oauth.d.ts +117 -0
  228. package/dist/serv/auth/oauth.d.ts.map +1 -0
  229. package/dist/serv/auth/oauth.js +395 -0
  230. package/dist/serv/auth/oauth.js.map +1 -0
  231. package/dist/serv/auth/well-known.d.ts +60 -0
  232. package/dist/serv/auth/well-known.d.ts.map +1 -0
  233. package/dist/serv/auth/well-known.js +154 -0
  234. package/dist/serv/auth/well-known.js.map +1 -0
  235. package/dist/serv/db/d1-client.d.ts +65 -0
  236. package/dist/serv/db/d1-client.d.ts.map +1 -0
  237. package/dist/serv/db/d1-client.js +137 -0
  238. package/dist/serv/db/d1-client.js.map +1 -0
  239. package/dist/serv/db/d1-stores.d.ts +62 -0
  240. package/dist/serv/db/d1-stores.d.ts.map +1 -0
  241. package/dist/serv/db/d1-stores.js +307 -0
  242. package/dist/serv/db/d1-stores.js.map +1 -0
  243. package/dist/serv/index.d.ts +114 -0
  244. package/dist/serv/index.d.ts.map +1 -0
  245. package/dist/serv/index.js +172 -0
  246. package/dist/serv/index.js.map +1 -0
  247. package/dist/serv/local.d.ts +118 -0
  248. package/dist/serv/local.d.ts.map +1 -0
  249. package/dist/serv/local.js +392 -0
  250. package/dist/serv/local.js.map +1 -0
  251. package/dist/serv/middleware/auth.d.ts +66 -0
  252. package/dist/serv/middleware/auth.d.ts.map +1 -0
  253. package/dist/serv/middleware/auth.js +178 -0
  254. package/dist/serv/middleware/auth.js.map +1 -0
  255. package/dist/serv/middleware/tenant.d.ts +94 -0
  256. package/dist/serv/middleware/tenant.d.ts.map +1 -0
  257. package/dist/serv/middleware/tenant.js +152 -0
  258. package/dist/serv/middleware/tenant.js.map +1 -0
  259. package/dist/serv/runtime/executor.d.ts +76 -0
  260. package/dist/serv/runtime/executor.d.ts.map +1 -0
  261. package/dist/serv/runtime/executor.js +105 -0
  262. package/dist/serv/runtime/executor.js.map +1 -0
  263. package/dist/serv/runtime/index.d.ts +8 -0
  264. package/dist/serv/runtime/index.d.ts.map +1 -0
  265. package/dist/serv/runtime/index.js +10 -0
  266. package/dist/serv/runtime/index.js.map +1 -0
  267. package/dist/serv/runtime/oauth-context.d.ts +121 -0
  268. package/dist/serv/runtime/oauth-context.d.ts.map +1 -0
  269. package/dist/serv/runtime/oauth-context.js +153 -0
  270. package/dist/serv/runtime/oauth-context.js.map +1 -0
  271. package/dist/serv/session/kv-store.d.ts +54 -0
  272. package/dist/serv/session/kv-store.d.ts.map +1 -0
  273. package/dist/serv/session/kv-store.js +149 -0
  274. package/dist/serv/session/kv-store.js.map +1 -0
  275. package/dist/serv/session/store.d.ts +113 -0
  276. package/dist/serv/session/store.d.ts.map +1 -0
  277. package/dist/serv/session/store.js +284 -0
  278. package/dist/serv/session/store.js.map +1 -0
  279. package/dist/serv/types/index.d.ts +147 -0
  280. package/dist/serv/types/index.d.ts.map +1 -0
  281. package/dist/serv/types/index.js +8 -0
  282. package/dist/serv/types/index.js.map +1 -0
  283. package/dist/serv/vault/token-vault.d.ts +102 -0
  284. package/dist/serv/vault/token-vault.d.ts.map +1 -0
  285. package/dist/serv/vault/token-vault.js +177 -0
  286. package/dist/serv/vault/token-vault.js.map +1 -0
  287. package/dist/server.d.ts +173 -0
  288. package/dist/server.d.ts.map +1 -1
  289. package/dist/server.js +1622 -86
  290. package/dist/server.js.map +1 -1
  291. package/dist/shared/cli-sections.d.ts +6 -0
  292. package/dist/shared/cli-sections.d.ts.map +1 -0
  293. package/dist/shared/cli-sections.js +16 -0
  294. package/dist/shared/cli-sections.js.map +1 -0
  295. package/dist/shared/cli-utils.d.ts +81 -0
  296. package/dist/shared/cli-utils.d.ts.map +1 -0
  297. package/dist/shared/cli-utils.js +174 -0
  298. package/dist/shared/cli-utils.js.map +1 -0
  299. package/dist/shared/config-docs.d.ts +6 -0
  300. package/dist/shared/config-docs.d.ts.map +1 -0
  301. package/dist/shared/config-docs.js +6 -0
  302. package/dist/shared/config-docs.js.map +1 -0
  303. package/dist/shared/error-handler.d.ts +128 -0
  304. package/dist/shared/error-handler.d.ts.map +1 -0
  305. package/dist/shared/error-handler.js +342 -0
  306. package/dist/shared/error-handler.js.map +1 -0
  307. package/dist/shared/logger.d.ts +42 -0
  308. package/dist/shared/logger.d.ts.map +1 -0
  309. package/dist/shared/logger.js +123 -0
  310. package/dist/shared/logger.js.map +1 -0
  311. package/dist/shared/performance.d.ts +65 -0
  312. package/dist/shared/performance.d.ts.map +1 -0
  313. package/dist/shared/performance.js +136 -0
  314. package/dist/shared/performance.js.map +1 -0
  315. package/dist/shared/task-runner.d.ts +2 -0
  316. package/dist/shared/task-runner.d.ts.map +1 -0
  317. package/dist/shared/task-runner.js +16 -0
  318. package/dist/shared/task-runner.js.map +1 -0
  319. package/dist/shared/validation.d.ts +6 -0
  320. package/dist/shared/validation.d.ts.map +1 -0
  321. package/dist/shared/validation.js +6 -0
  322. package/dist/shared/validation.js.map +1 -0
  323. package/dist/shared-utils.d.ts +63 -0
  324. package/dist/shared-utils.d.ts.map +1 -0
  325. package/dist/shared-utils.js +123 -0
  326. package/dist/shared-utils.js.map +1 -0
  327. package/dist/template-manager.d.ts +23 -2
  328. package/dist/template-manager.d.ts.map +1 -1
  329. package/dist/template-manager.js +177 -88
  330. package/dist/template-manager.js.map +1 -1
  331. package/dist/test-client.d.ts.map +1 -1
  332. package/dist/test-client.js +10 -8
  333. package/dist/test-client.js.map +1 -1
  334. package/dist/test-runner.d.ts +52 -0
  335. package/dist/test-runner.d.ts.map +1 -0
  336. package/dist/test-runner.js +785 -0
  337. package/dist/test-runner.js.map +1 -0
  338. package/dist/testing.d.ts +103 -0
  339. package/dist/testing.d.ts.map +1 -0
  340. package/dist/testing.js +163 -0
  341. package/dist/testing.js.map +1 -0
  342. package/dist/version-checker.d.ts.map +1 -1
  343. package/dist/version-checker.js +2 -2
  344. package/dist/version-checker.js.map +1 -1
  345. package/dist/version.d.ts +2 -0
  346. package/dist/version.d.ts.map +1 -0
  347. package/dist/version.js +5 -0
  348. package/dist/version.js.map +1 -0
  349. package/dist/watcher.d.ts +6 -3
  350. package/dist/watcher.d.ts.map +1 -1
  351. package/dist/watcher.js +49 -10
  352. package/dist/watcher.js.map +1 -1
  353. package/package.json +47 -7
  354. package/templates/cloudflare/worker.ts.template +381 -0
  355. package/templates/cloudflare/wrangler.toml.template +9 -0
  356. package/dist/base.d.ts +0 -58
  357. package/dist/base.d.ts.map +0 -1
  358. package/dist/base.js +0 -92
  359. package/dist/base.js.map +0 -1
  360. package/dist/dependency-manager.d.ts +0 -49
  361. package/dist/dependency-manager.d.ts.map +0 -1
  362. package/dist/dependency-manager.js +0 -165
  363. package/dist/dependency-manager.js.map +0 -1
  364. package/dist/registry-manager.d.ts +0 -76
  365. package/dist/registry-manager.d.ts.map +0 -1
  366. package/dist/registry-manager.js +0 -220
  367. package/dist/registry-manager.js.map +0 -1
  368. package/dist/schema-extractor.d.ts +0 -110
  369. package/dist/schema-extractor.d.ts.map +0 -1
  370. package/dist/schema-extractor.js +0 -727
  371. package/dist/schema-extractor.js.map +0 -1
  372. package/dist/test-marketplace-sources.d.ts +0 -5
  373. package/dist/test-marketplace-sources.d.ts.map +0 -1
  374. package/dist/test-marketplace-sources.js +0 -53
  375. package/dist/test-marketplace-sources.js.map +0 -1
  376. package/dist/types.d.ts +0 -109
  377. package/dist/types.d.ts.map +0 -1
  378. package/dist/types.js +0 -12
  379. package/dist/types.js.map +0 -1
@@ -0,0 +1,1314 @@
1
+ /**
2
+ * Streamable HTTP Transport for MCP
3
+ *
4
+ * Implements the MCP Streamable HTTP transport specification (2025-03-26).
5
+ * This allows standard MCP clients (like Claude Desktop) to connect to Beam.
6
+ *
7
+ * Endpoint: /mcp
8
+ * - POST: Client sends JSON-RPC requests, server responds with JSON or SSE
9
+ * - GET: Opens SSE stream for server-initiated messages
10
+ *
11
+ * Configuration Schema (SEP-1596 inspired):
12
+ * - Returns configurationSchema in initialize response
13
+ * - Uses JSON Schema for rich UI generation (dropdowns, file pickers, etc.)
14
+ * - beam/configure tool for submitting configuration
15
+ * - beam/browse tool for server filesystem browsing
16
+ *
17
+ * @see https://modelcontextprotocol.io/specification/2025-03-26/basic/transports
18
+ */
19
+ import { randomUUID } from 'crypto';
20
+ import { readdir, stat } from 'fs/promises';
21
+ import { join, dirname } from 'path';
22
+ import { homedir } from 'os';
23
+ import { PHOTON_VERSION } from '../version.js';
24
+ import { buildToolMetadataExtensions } from './types.js';
25
+ // ════════════════════════════════════════════════════════════════════════════════
26
+ // SESSION MANAGEMENT
27
+ // ════════════════════════════════════════════════════════════════════════════════
28
+ const sessions = new Map();
29
+ const pendingElicitations = new Map();
30
+ // Clean up old sessions periodically (30 min timeout)
31
+ const SESSION_TIMEOUT_MS = 30 * 60 * 1000;
32
+ setInterval(() => {
33
+ const now = Date.now();
34
+ for (const [id, session] of sessions) {
35
+ if (now - session.lastActivity.getTime() > SESSION_TIMEOUT_MS) {
36
+ sessions.delete(id);
37
+ }
38
+ }
39
+ }, 60 * 1000);
40
+ function getOrCreateSession(sessionId) {
41
+ if (sessionId && sessions.has(sessionId)) {
42
+ const session = sessions.get(sessionId);
43
+ session.lastActivity = new Date();
44
+ return session;
45
+ }
46
+ const newSession = {
47
+ id: randomUUID(),
48
+ initialized: false,
49
+ createdAt: new Date(),
50
+ lastActivity: new Date(),
51
+ };
52
+ sessions.set(newSession.id, newSession);
53
+ return newSession;
54
+ }
55
+ // ════════════════════════════════════════════════════════════════════════════════
56
+ // CONFIGURATION SCHEMA GENERATION
57
+ // ════════════════════════════════════════════════════════════════════════════════
58
+ /**
59
+ * Convert ConfigParam to JSON Schema property
60
+ */
61
+ function configParamToJsonSchema(param) {
62
+ const schema = {
63
+ description: `Environment variable: ${param.envVar}`,
64
+ 'x-env-var': param.envVar,
65
+ };
66
+ // Map TypeScript types to JSON Schema types
67
+ switch (param.type.toLowerCase()) {
68
+ case 'number':
69
+ schema.type = 'number';
70
+ break;
71
+ case 'boolean':
72
+ schema.type = 'boolean';
73
+ break;
74
+ case 'string':
75
+ default:
76
+ schema.type = 'string';
77
+ // Check for common sensitive parameter names - use OpenAPI standard
78
+ if (/password|secret|token|key|credential/i.test(param.name)) {
79
+ schema.format = 'password';
80
+ schema.writeOnly = true;
81
+ }
82
+ // Check for path-like parameter names
83
+ else if (/path|file|dir|directory|folder/i.test(param.name)) {
84
+ schema.format = 'path';
85
+ }
86
+ break;
87
+ }
88
+ // Add default value if present
89
+ if (param.hasDefault && param.defaultValue !== undefined) {
90
+ schema.default = param.defaultValue;
91
+ }
92
+ return schema;
93
+ }
94
+ /**
95
+ * Generate configurationSchema for all unconfigured photons
96
+ * Uses JSON Schema format for rich UI generation
97
+ */
98
+ function generateConfigurationSchema(photons) {
99
+ const schema = {};
100
+ for (const photon of photons) {
101
+ // Only include unconfigured photons with params
102
+ if (photon.configured)
103
+ continue;
104
+ const unconfigured = photon;
105
+ if (!unconfigured.requiredParams || unconfigured.requiredParams.length === 0)
106
+ continue;
107
+ const properties = {};
108
+ const required = [];
109
+ for (const param of unconfigured.requiredParams) {
110
+ properties[param.name] = configParamToJsonSchema(param);
111
+ // Mark as required if not optional and no default
112
+ if (!param.isOptional && !param.hasDefault) {
113
+ required.push(param.name);
114
+ }
115
+ }
116
+ schema[photon.name] = {
117
+ type: 'object',
118
+ properties,
119
+ required: required.length > 0 ? required : undefined,
120
+ 'x-error-message': unconfigured.errorMessage,
121
+ 'x-internal': unconfigured.internal,
122
+ };
123
+ }
124
+ return schema;
125
+ }
126
+ const handlers = {
127
+ // ─────────────────────────────────────────────────────────────────────────────
128
+ // Lifecycle
129
+ // ─────────────────────────────────────────────────────────────────────────────
130
+ initialize: async (req, session, ctx) => {
131
+ session.initialized = true;
132
+ // Capture client info and detect Beam clients
133
+ const clientInfo = req.params?.clientInfo;
134
+ if (clientInfo) {
135
+ session.clientInfo = clientInfo;
136
+ session.isBeam = clientInfo.name === 'beam';
137
+ }
138
+ // Generate configuration schema for unconfigured photons
139
+ const configurationSchema = generateConfigurationSchema(ctx.photons);
140
+ return {
141
+ jsonrpc: '2.0',
142
+ id: req.id,
143
+ result: {
144
+ protocolVersion: '2025-03-26',
145
+ serverInfo: {
146
+ name: 'beam-mcp',
147
+ version: PHOTON_VERSION,
148
+ },
149
+ capabilities: {
150
+ tools: { listChanged: true },
151
+ resources: { listChanged: true },
152
+ },
153
+ // SEP-1596 inspired: configuration schema for unconfigured photons
154
+ // Uses JSON Schema for rich UI generation
155
+ configurationSchema: Object.keys(configurationSchema).length > 0 ? configurationSchema : undefined,
156
+ },
157
+ };
158
+ },
159
+ 'notifications/initialized': async (req, session) => {
160
+ // Notification - no response needed
161
+ return { jsonrpc: '2.0' };
162
+ },
163
+ // Handle elicitation response from frontend
164
+ 'beam/elicitation-response': async (req, session) => {
165
+ const params = req.params;
166
+ const elicitationId = params?.elicitationId;
167
+ if (!elicitationId) {
168
+ return {
169
+ jsonrpc: '2.0',
170
+ id: req.id,
171
+ error: { code: -32602, message: 'Missing elicitationId' },
172
+ };
173
+ }
174
+ const pending = pendingElicitations.get(elicitationId);
175
+ if (!pending) {
176
+ return {
177
+ jsonrpc: '2.0',
178
+ id: req.id,
179
+ error: { code: -32602, message: 'Unknown elicitationId' },
180
+ };
181
+ }
182
+ pendingElicitations.delete(elicitationId);
183
+ if (params?.cancelled) {
184
+ pending.reject(new Error('Elicitation cancelled by user'));
185
+ }
186
+ else {
187
+ pending.resolve(params?.value);
188
+ }
189
+ return { jsonrpc: '2.0', id: req.id, result: { success: true } };
190
+ },
191
+ // Client notifies what resource they're viewing (for on-demand subscriptions)
192
+ // photonId: hash of photon path (unique across servers)
193
+ // itemId: whatever the photon uses to identify the item (e.g., board name)
194
+ // lastEventId: optional - for replay of missed events on reconnect
195
+ 'beam/viewing': async (req, session, ctx) => {
196
+ const params = req.params;
197
+ const photonId = params?.photonId;
198
+ const itemId = params?.itemId;
199
+ const lastEventId = params?.lastEventId;
200
+ if (photonId && itemId && ctx.subscriptionManager) {
201
+ ctx.subscriptionManager.onClientViewingBoard(session.id, photonId, itemId, lastEventId);
202
+ }
203
+ // Notification - no response needed
204
+ return { jsonrpc: '2.0' };
205
+ },
206
+ ping: async (req) => {
207
+ return { jsonrpc: '2.0', id: req.id, result: {} };
208
+ },
209
+ // ─────────────────────────────────────────────────────────────────────────────
210
+ // Tools
211
+ // ─────────────────────────────────────────────────────────────────────────────
212
+ 'tools/list': async (req, session, ctx) => {
213
+ const tools = [];
214
+ // Add configured photon methods as tools
215
+ for (const photon of ctx.photons) {
216
+ if (!photon.configured || !photon.methods)
217
+ continue;
218
+ for (const method of photon.methods) {
219
+ tools.push({
220
+ name: `${photon.name}/${method.name}`,
221
+ description: method.description || `Execute ${method.name}`,
222
+ inputSchema: method.params || { type: 'object', properties: {} },
223
+ 'x-photon-id': photon.id, // Unique ID (hash of path) for subscriptions
224
+ 'x-photon-path': photon.path, // File path for View Source
225
+ 'x-photon-description': photon.description,
226
+ 'x-photon-icon': photon.icon,
227
+ 'x-photon-internal': photon.internal,
228
+ 'x-photon-prompt-count': photon.promptCount ?? 0,
229
+ 'x-photon-resource-count': photon.resourceCount ?? 0,
230
+ ...buildToolMetadataExtensions(method),
231
+ // MCP Apps standard: _meta.ui for linked UI resources and visibility
232
+ ...(method.linkedUi || method.visibility
233
+ ? {
234
+ _meta: {
235
+ ui: {
236
+ ...(method.linkedUi
237
+ ? { resourceUri: `ui://${photon.name}/${method.linkedUi}` }
238
+ : {}),
239
+ ...(method.visibility ? { visibility: method.visibility } : {}),
240
+ },
241
+ },
242
+ }
243
+ : {}),
244
+ });
245
+ }
246
+ }
247
+ // Add beam system tools (internal — hidden from sidebar)
248
+ tools.push({
249
+ name: 'beam/configure',
250
+ 'x-photon-internal': true,
251
+ description: 'Configure a photon with required parameters. Use initialize response configurationSchema to get required fields.',
252
+ inputSchema: {
253
+ type: 'object',
254
+ properties: {
255
+ photon: {
256
+ type: 'string',
257
+ description: 'Name of the photon to configure',
258
+ },
259
+ config: {
260
+ type: 'object',
261
+ description: 'Configuration values (key-value pairs matching the configurationSchema)',
262
+ additionalProperties: true,
263
+ },
264
+ },
265
+ required: ['photon', 'config'],
266
+ },
267
+ });
268
+ tools.push({
269
+ name: 'beam/browse',
270
+ description: 'Browse server filesystem for file/directory selection',
271
+ inputSchema: {
272
+ type: 'object',
273
+ properties: {
274
+ path: {
275
+ type: 'string',
276
+ description: 'Directory path to list (defaults to home directory)',
277
+ },
278
+ filter: {
279
+ type: 'string',
280
+ description: 'File extension filter (e.g., ".pem,.crt" or "*.photon.ts")',
281
+ },
282
+ },
283
+ },
284
+ });
285
+ tools.push({
286
+ name: 'beam/reload',
287
+ description: 'Reload a photon to pick up file changes',
288
+ inputSchema: {
289
+ type: 'object',
290
+ properties: {
291
+ photon: {
292
+ type: 'string',
293
+ description: 'Name of the photon to reload',
294
+ },
295
+ },
296
+ required: ['photon'],
297
+ },
298
+ });
299
+ tools.push({
300
+ name: 'beam/remove',
301
+ description: 'Remove a photon from the workspace',
302
+ inputSchema: {
303
+ type: 'object',
304
+ properties: {
305
+ photon: {
306
+ type: 'string',
307
+ description: 'Name of the photon to remove',
308
+ },
309
+ },
310
+ required: ['photon'],
311
+ },
312
+ });
313
+ tools.push({
314
+ name: 'beam/photon-help',
315
+ description: 'Get rich documentation for a photon',
316
+ inputSchema: {
317
+ type: 'object',
318
+ properties: {
319
+ photon: {
320
+ type: 'string',
321
+ description: 'Name of the photon to get help for',
322
+ },
323
+ },
324
+ required: ['photon'],
325
+ },
326
+ });
327
+ tools.push({
328
+ name: 'beam/update-metadata',
329
+ description: 'Update photon or method metadata (icon, description)',
330
+ inputSchema: {
331
+ type: 'object',
332
+ properties: {
333
+ photon: {
334
+ type: 'string',
335
+ description: 'Name of the photon',
336
+ },
337
+ method: {
338
+ type: 'string',
339
+ description: 'Name of the method (optional, for method metadata)',
340
+ },
341
+ metadata: {
342
+ type: 'object',
343
+ description: 'Metadata to update (icon, description)',
344
+ properties: {
345
+ icon: { type: 'string' },
346
+ description: { type: 'string' },
347
+ },
348
+ },
349
+ },
350
+ required: ['photon', 'metadata'],
351
+ },
352
+ });
353
+ // Filter out app-only tools for external (non-Beam) MCP clients
354
+ const visibleTools = session.isBeam
355
+ ? tools
356
+ : tools.filter((t) => {
357
+ const vis = t._meta?.ui?.visibility;
358
+ if (vis && Array.isArray(vis) && vis.includes('app') && !vis.includes('model')) {
359
+ return false;
360
+ }
361
+ return true;
362
+ });
363
+ return { jsonrpc: '2.0', id: req.id, result: { tools: visibleTools } };
364
+ },
365
+ 'tools/call': async (req, session, ctx) => {
366
+ const { name, arguments: args } = req.params;
367
+ // Handle beam system tools
368
+ if (name === 'beam/configure') {
369
+ return handleBeamConfigure(req, ctx, args || {});
370
+ }
371
+ if (name === 'beam/browse') {
372
+ return handleBeamBrowse(req, args || {});
373
+ }
374
+ if (name === 'beam/reload') {
375
+ return handleBeamReload(req, ctx, args || {});
376
+ }
377
+ if (name === 'beam/remove') {
378
+ return handleBeamRemove(req, ctx, args || {});
379
+ }
380
+ if (name === 'beam/update-metadata') {
381
+ return handleBeamUpdateMetadata(req, ctx, args || {});
382
+ }
383
+ if (name === 'beam/photon-help') {
384
+ return handleBeamPhotonHelp(req, ctx, args || {});
385
+ }
386
+ // Parse tool name: photon-name/method-name
387
+ const slashIndex = name.indexOf('/');
388
+ if (slashIndex === -1) {
389
+ return {
390
+ jsonrpc: '2.0',
391
+ id: req.id,
392
+ result: {
393
+ content: [{ type: 'text', text: `Invalid tool name: ${name}` }],
394
+ isError: true,
395
+ },
396
+ };
397
+ }
398
+ const photonName = name.slice(0, slashIndex);
399
+ const methodName = name.slice(slashIndex + 1);
400
+ // Find photon info for UI metadata
401
+ const photonInfo = ctx.photons.find((p) => p.name === photonName);
402
+ const methodInfo = photonInfo?.configured
403
+ ? photonInfo.methods?.find((m) => m.name === methodName)
404
+ : undefined;
405
+ // Build UI metadata
406
+ const uiMetadata = {};
407
+ if (methodInfo?.outputFormat) {
408
+ uiMetadata['x-output-format'] = methodInfo.outputFormat;
409
+ }
410
+ const mcp = ctx.photonMCPs.get(photonName);
411
+ if (!mcp?.instance) {
412
+ return {
413
+ jsonrpc: '2.0',
414
+ id: req.id,
415
+ result: {
416
+ content: [{ type: 'text', text: `Photon not found: ${photonName}` }],
417
+ isError: true,
418
+ },
419
+ };
420
+ }
421
+ // Check instance first, then prototype, then static methods on class
422
+ let method = mcp.instance[methodName];
423
+ let isStatic = false;
424
+ if (typeof method !== 'function') {
425
+ method = Object.getPrototypeOf(mcp.instance)?.[methodName];
426
+ }
427
+ // Check for static method on class constructor
428
+ if (typeof method !== 'function' && mcp.classConstructor) {
429
+ method = mcp.classConstructor[methodName];
430
+ isStatic = true;
431
+ }
432
+ if (typeof method !== 'function') {
433
+ return {
434
+ jsonrpc: '2.0',
435
+ id: req.id,
436
+ result: {
437
+ content: [{ type: 'text', text: `Method not found: ${methodName}` }],
438
+ isError: true,
439
+ },
440
+ };
441
+ }
442
+ try {
443
+ // Create outputHandler to capture emits for real-time UI updates
444
+ const outputHandler = (yieldValue) => {
445
+ if (!ctx.broadcast)
446
+ return;
447
+ // Forward progress events as MCP notifications
448
+ if (yieldValue?.emit === 'progress') {
449
+ const rawValue = typeof yieldValue.value === 'number' ? yieldValue.value : 0;
450
+ const progress = rawValue <= 1 ? rawValue * 100 : rawValue;
451
+ ctx.broadcast({
452
+ jsonrpc: '2.0',
453
+ method: 'notifications/progress',
454
+ params: {
455
+ progressToken: `progress_${photonName}_${methodName}`,
456
+ progress,
457
+ total: 100,
458
+ message: yieldValue.message || null,
459
+ },
460
+ });
461
+ return;
462
+ }
463
+ // Forward status events as MCP notifications
464
+ if (yieldValue?.emit === 'status') {
465
+ ctx.broadcast({
466
+ jsonrpc: '2.0',
467
+ method: 'notifications/progress',
468
+ params: {
469
+ progressToken: `progress_${photonName}_${methodName}`,
470
+ progress: 0,
471
+ total: 100,
472
+ message: yieldValue.message || '',
473
+ },
474
+ });
475
+ return;
476
+ }
477
+ // Forward toast events as beam notifications
478
+ if (yieldValue?.emit === 'toast') {
479
+ ctx.broadcast({
480
+ jsonrpc: '2.0',
481
+ method: 'beam/toast',
482
+ params: {
483
+ message: yieldValue.message || '',
484
+ type: yieldValue.type || 'info',
485
+ duration: yieldValue.duration,
486
+ },
487
+ });
488
+ return;
489
+ }
490
+ // Forward thinking events as beam notifications
491
+ if (yieldValue?.emit === 'thinking') {
492
+ ctx.broadcast({
493
+ jsonrpc: '2.0',
494
+ method: 'beam/thinking',
495
+ params: {
496
+ active: yieldValue.active ?? true,
497
+ },
498
+ });
499
+ return;
500
+ }
501
+ // Forward log events as beam notifications
502
+ if (yieldValue?.emit === 'log') {
503
+ ctx.broadcast({
504
+ jsonrpc: '2.0',
505
+ method: 'beam/log',
506
+ params: {
507
+ message: yieldValue.message || '',
508
+ level: yieldValue.level || 'info',
509
+ data: yieldValue.data,
510
+ },
511
+ });
512
+ return;
513
+ }
514
+ // Forward channel events (task-moved, task-updated, etc.) with full delta
515
+ // These contain specific event type + data for efficient UI updates
516
+ if (yieldValue?.channel && yieldValue?.event) {
517
+ ctx.broadcast({
518
+ type: 'channel-event',
519
+ photon: photonName,
520
+ channel: yieldValue.channel,
521
+ event: yieldValue.event,
522
+ data: yieldValue.data,
523
+ });
524
+ }
525
+ // Note: board-update emits are intentionally not forwarded here
526
+ // Channel events provide more specific info for real-time updates
527
+ };
528
+ // Create inputProvider to handle ask yields (elicitation)
529
+ const inputProvider = async (ask) => {
530
+ if (!ctx.broadcast) {
531
+ throw new Error('No broadcast connection for elicitation');
532
+ }
533
+ // Generate unique elicitation ID
534
+ const elicitationId = randomUUID();
535
+ return new Promise((resolve, reject) => {
536
+ // Store pending elicitation
537
+ pendingElicitations.set(elicitationId, {
538
+ resolve,
539
+ reject,
540
+ sessionId: session?.id || '',
541
+ });
542
+ // Broadcast elicitation request to frontend
543
+ ctx.broadcast({
544
+ jsonrpc: '2.0',
545
+ method: 'beam/elicitation',
546
+ params: {
547
+ elicitationId,
548
+ ...ask,
549
+ },
550
+ });
551
+ // Timeout after 5 minutes
552
+ setTimeout(() => {
553
+ if (pendingElicitations.has(elicitationId)) {
554
+ pendingElicitations.delete(elicitationId);
555
+ reject(new Error('Elicitation timeout - no response received'));
556
+ }
557
+ }, 300000);
558
+ });
559
+ };
560
+ // Use loader.executeTool if available (sets up execution context for this.emit())
561
+ // Fall back to direct method call for backward compatibility
562
+ let result;
563
+ if (ctx.loader) {
564
+ result = await ctx.loader.executeTool(mcp, methodName, args || {}, {
565
+ outputHandler,
566
+ inputProvider,
567
+ });
568
+ }
569
+ else {
570
+ // For static methods, don't bind to instance
571
+ result = isStatic ? await method(args || {}) : await method.call(mcp.instance, args || {});
572
+ }
573
+ // Handle async generators (when not using loader)
574
+ if (result && typeof result[Symbol.asyncIterator] === 'function') {
575
+ const chunks = [];
576
+ for await (const chunk of result) {
577
+ if (chunk.emit === 'result') {
578
+ chunks.push(chunk.data);
579
+ }
580
+ else if (chunk.emit === 'board-update' && ctx.broadcast) {
581
+ // Forward board-update from generator
582
+ ctx.broadcast({
583
+ type: 'board-update',
584
+ photon: photonName,
585
+ board: chunk.board,
586
+ });
587
+ }
588
+ else if (chunk.emit !== 'progress') {
589
+ chunks.push(chunk);
590
+ }
591
+ }
592
+ const finalResult = chunks.length === 1 ? chunks[0] : chunks;
593
+ const genResponse = {
594
+ jsonrpc: '2.0',
595
+ id: req.id,
596
+ result: {
597
+ content: [{ type: 'text', text: JSON.stringify(finalResult, null, 2) }],
598
+ isError: false,
599
+ ...uiMetadata,
600
+ },
601
+ };
602
+ // Broadcast tool result as MCP Apps notification for linked-UI methods
603
+ if (ctx.broadcast && methodInfo?.linkedUi) {
604
+ ctx.broadcast({
605
+ jsonrpc: '2.0',
606
+ method: 'ui/notifications/tool-result',
607
+ params: {
608
+ toolName: `${photonName}/${methodName}`,
609
+ result: genResponse.result,
610
+ isError: false,
611
+ },
612
+ });
613
+ }
614
+ return genResponse;
615
+ }
616
+ const toolResponse = {
617
+ jsonrpc: '2.0',
618
+ id: req.id,
619
+ result: {
620
+ content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
621
+ isError: false,
622
+ ...uiMetadata,
623
+ },
624
+ };
625
+ // Broadcast tool result as MCP Apps notification for linked-UI methods
626
+ if (ctx.broadcast && methodInfo?.linkedUi) {
627
+ ctx.broadcast({
628
+ jsonrpc: '2.0',
629
+ method: 'ui/notifications/tool-result',
630
+ params: {
631
+ toolName: `${photonName}/${methodName}`,
632
+ result: toolResponse.result,
633
+ isError: false,
634
+ },
635
+ });
636
+ }
637
+ return toolResponse;
638
+ }
639
+ catch (error) {
640
+ const message = error instanceof Error ? error.message : String(error);
641
+ return {
642
+ jsonrpc: '2.0',
643
+ id: req.id,
644
+ result: {
645
+ content: [{ type: 'text', text: `Error: ${message}` }],
646
+ isError: true,
647
+ },
648
+ };
649
+ }
650
+ },
651
+ // ─────────────────────────────────────────────────────────────────────────────
652
+ // Resources (MCP Apps ui:// scheme)
653
+ // ─────────────────────────────────────────────────────────────────────────────
654
+ 'resources/list': async (req, session, ctx) => {
655
+ const resources = [];
656
+ for (const photon of ctx.photons) {
657
+ if (!photon.configured || !photon.assets?.ui)
658
+ continue;
659
+ for (const uiAsset of photon.assets.ui) {
660
+ const uri = uiAsset.uri || `ui://${photon.name}/${uiAsset.id}`;
661
+ resources.push({
662
+ uri,
663
+ name: uiAsset.id,
664
+ mimeType: uiAsset.mimeType || 'text/html;profile=mcp-app',
665
+ description: uiAsset.linkedTool
666
+ ? `UI template for ${photon.name}/${uiAsset.linkedTool}`
667
+ : `UI template: ${uiAsset.id}`,
668
+ });
669
+ }
670
+ }
671
+ return { jsonrpc: '2.0', id: req.id, result: { resources } };
672
+ },
673
+ 'resources/read': async (req, session, ctx) => {
674
+ const { uri } = req.params;
675
+ // Parse ui:// URI
676
+ const match = uri.match(/^ui:\/\/([^/]+)\/(.+)$/);
677
+ if (!match) {
678
+ return {
679
+ jsonrpc: '2.0',
680
+ id: req.id,
681
+ error: { code: -32602, message: `Invalid URI: ${uri}` },
682
+ };
683
+ }
684
+ const [, photonName, uiId] = match;
685
+ const content = await ctx.loadUIAsset(photonName, uiId);
686
+ if (!content) {
687
+ return {
688
+ jsonrpc: '2.0',
689
+ id: req.id,
690
+ error: { code: -32602, message: `Resource not found: ${uri}` },
691
+ };
692
+ }
693
+ return {
694
+ jsonrpc: '2.0',
695
+ id: req.id,
696
+ result: {
697
+ contents: [{ uri, mimeType: 'text/html;profile=mcp-app', text: content }],
698
+ },
699
+ };
700
+ },
701
+ };
702
+ // ════════════════════════════════════════════════════════════════════════════════
703
+ // BEAM SYSTEM TOOLS
704
+ // ════════════════════════════════════════════════════════════════════════════════
705
+ /**
706
+ * Handle beam/configure tool - configure a photon with provided values
707
+ */
708
+ async function handleBeamConfigure(req, ctx, args) {
709
+ const { photon: photonName, config } = args;
710
+ if (!photonName) {
711
+ return {
712
+ jsonrpc: '2.0',
713
+ id: req.id,
714
+ result: {
715
+ content: [{ type: 'text', text: 'Error: photon name is required' }],
716
+ isError: true,
717
+ },
718
+ };
719
+ }
720
+ if (!config || typeof config !== 'object') {
721
+ return {
722
+ jsonrpc: '2.0',
723
+ id: req.id,
724
+ result: {
725
+ content: [{ type: 'text', text: 'Error: config object is required' }],
726
+ isError: true,
727
+ },
728
+ };
729
+ }
730
+ // Check if configurePhoton callback is available
731
+ if (!ctx.configurePhoton) {
732
+ return {
733
+ jsonrpc: '2.0',
734
+ id: req.id,
735
+ result: {
736
+ content: [{ type: 'text', text: 'Error: Configuration not supported in this context' }],
737
+ isError: true,
738
+ },
739
+ };
740
+ }
741
+ try {
742
+ const result = await ctx.configurePhoton(photonName, config);
743
+ if (result.success) {
744
+ return {
745
+ jsonrpc: '2.0',
746
+ id: req.id,
747
+ result: {
748
+ content: [
749
+ {
750
+ type: 'text',
751
+ text: `Successfully configured ${photonName}. Tools list will be updated.`,
752
+ },
753
+ ],
754
+ isError: false,
755
+ },
756
+ };
757
+ }
758
+ else {
759
+ return {
760
+ jsonrpc: '2.0',
761
+ id: req.id,
762
+ result: {
763
+ content: [{ type: 'text', text: `Failed to configure ${photonName}: ${result.error}` }],
764
+ isError: true,
765
+ },
766
+ };
767
+ }
768
+ }
769
+ catch (error) {
770
+ const message = error instanceof Error ? error.message : String(error);
771
+ return {
772
+ jsonrpc: '2.0',
773
+ id: req.id,
774
+ result: {
775
+ content: [{ type: 'text', text: `Error configuring ${photonName}: ${message}` }],
776
+ isError: true,
777
+ },
778
+ };
779
+ }
780
+ }
781
+ /**
782
+ * Handle beam/browse tool - browse server filesystem
783
+ */
784
+ async function handleBeamBrowse(req, args) {
785
+ const { path: requestedPath, filter } = args;
786
+ // Default to home directory
787
+ let targetPath = requestedPath || homedir();
788
+ // Handle relative navigation (.. for parent)
789
+ if (targetPath.endsWith('/..') || targetPath === '..') {
790
+ targetPath = dirname(targetPath.replace(/\/?\.\.$/, ''));
791
+ }
792
+ try {
793
+ const stats = await stat(targetPath);
794
+ if (!stats.isDirectory()) {
795
+ targetPath = dirname(targetPath);
796
+ }
797
+ const entries = await readdir(targetPath, { withFileTypes: true });
798
+ // Parse filter
799
+ const filters = filter ? filter.split(',').map((f) => f.trim().toLowerCase()) : [];
800
+ const items = entries
801
+ .filter((entry) => {
802
+ // Always show directories
803
+ if (entry.isDirectory())
804
+ return true;
805
+ // No filter = show all
806
+ if (filters.length === 0)
807
+ return true;
808
+ const fileName = entry.name.toLowerCase();
809
+ return filters.some((f) => {
810
+ // Handle glob patterns like "*.photon.ts"
811
+ if (f.startsWith('*.')) {
812
+ const suffix = f.slice(1);
813
+ return fileName.endsWith(suffix);
814
+ }
815
+ // Handle extension patterns like ".ts" or "ts"
816
+ const ext = f.startsWith('.') ? f : `.${f}`;
817
+ return fileName.endsWith(ext);
818
+ });
819
+ })
820
+ .map((entry) => ({
821
+ name: entry.name,
822
+ path: join(targetPath, entry.name),
823
+ isDirectory: entry.isDirectory(),
824
+ }))
825
+ .sort((a, b) => {
826
+ // Directories first, then alphabetical
827
+ if (a.isDirectory !== b.isDirectory) {
828
+ return a.isDirectory ? -1 : 1;
829
+ }
830
+ return a.name.localeCompare(b.name);
831
+ });
832
+ // Calculate parent path
833
+ const parent = dirname(targetPath);
834
+ return {
835
+ jsonrpc: '2.0',
836
+ id: req.id,
837
+ result: {
838
+ content: [
839
+ {
840
+ type: 'text',
841
+ text: JSON.stringify({
842
+ path: targetPath,
843
+ parent: parent !== targetPath ? parent : null,
844
+ items,
845
+ }, null, 2),
846
+ },
847
+ ],
848
+ isError: false,
849
+ },
850
+ };
851
+ }
852
+ catch (error) {
853
+ const message = error instanceof Error ? error.message : String(error);
854
+ return {
855
+ jsonrpc: '2.0',
856
+ id: req.id,
857
+ result: {
858
+ content: [{ type: 'text', text: `Error browsing ${targetPath}: ${message}` }],
859
+ isError: true,
860
+ },
861
+ };
862
+ }
863
+ }
864
+ /**
865
+ * Handle beam/reload tool - reload a photon
866
+ */
867
+ async function handleBeamReload(req, ctx, args) {
868
+ const { photon: photonName } = args;
869
+ if (!photonName) {
870
+ return {
871
+ jsonrpc: '2.0',
872
+ id: req.id,
873
+ result: {
874
+ content: [{ type: 'text', text: 'Error: photon name is required' }],
875
+ isError: true,
876
+ },
877
+ };
878
+ }
879
+ if (!ctx.reloadPhoton) {
880
+ return {
881
+ jsonrpc: '2.0',
882
+ id: req.id,
883
+ result: {
884
+ content: [{ type: 'text', text: 'Error: Reload not supported in this context' }],
885
+ isError: true,
886
+ },
887
+ };
888
+ }
889
+ try {
890
+ const result = await ctx.reloadPhoton(photonName);
891
+ if (result.success) {
892
+ // Notify Beam clients about the reload
893
+ broadcastToBeam('beam/hot-reload', { photon: result.photon });
894
+ return {
895
+ jsonrpc: '2.0',
896
+ id: req.id,
897
+ result: {
898
+ content: [{ type: 'text', text: `Successfully reloaded ${photonName}` }],
899
+ isError: false,
900
+ },
901
+ };
902
+ }
903
+ else {
904
+ return {
905
+ jsonrpc: '2.0',
906
+ id: req.id,
907
+ result: {
908
+ content: [{ type: 'text', text: `Failed to reload ${photonName}: ${result.error}` }],
909
+ isError: true,
910
+ },
911
+ };
912
+ }
913
+ }
914
+ catch (error) {
915
+ const message = error instanceof Error ? error.message : String(error);
916
+ return {
917
+ jsonrpc: '2.0',
918
+ id: req.id,
919
+ result: {
920
+ content: [{ type: 'text', text: `Error reloading ${photonName}: ${message}` }],
921
+ isError: true,
922
+ },
923
+ };
924
+ }
925
+ }
926
+ /**
927
+ * Handle beam/remove tool - remove a photon from the workspace
928
+ */
929
+ async function handleBeamRemove(req, ctx, args) {
930
+ const { photon: photonName } = args;
931
+ if (!photonName) {
932
+ return {
933
+ jsonrpc: '2.0',
934
+ id: req.id,
935
+ result: {
936
+ content: [{ type: 'text', text: 'Error: photon name is required' }],
937
+ isError: true,
938
+ },
939
+ };
940
+ }
941
+ if (!ctx.removePhoton) {
942
+ return {
943
+ jsonrpc: '2.0',
944
+ id: req.id,
945
+ result: {
946
+ content: [{ type: 'text', text: 'Error: Remove not supported in this context' }],
947
+ isError: true,
948
+ },
949
+ };
950
+ }
951
+ try {
952
+ const result = await ctx.removePhoton(photonName);
953
+ if (result.success) {
954
+ return {
955
+ jsonrpc: '2.0',
956
+ id: req.id,
957
+ result: {
958
+ content: [{ type: 'text', text: `Successfully removed ${photonName}` }],
959
+ isError: false,
960
+ },
961
+ };
962
+ }
963
+ else {
964
+ return {
965
+ jsonrpc: '2.0',
966
+ id: req.id,
967
+ result: {
968
+ content: [{ type: 'text', text: `Failed to remove ${photonName}: ${result.error}` }],
969
+ isError: true,
970
+ },
971
+ };
972
+ }
973
+ }
974
+ catch (error) {
975
+ const message = error instanceof Error ? error.message : String(error);
976
+ return {
977
+ jsonrpc: '2.0',
978
+ id: req.id,
979
+ result: {
980
+ content: [{ type: 'text', text: `Error removing ${photonName}: ${message}` }],
981
+ isError: true,
982
+ },
983
+ };
984
+ }
985
+ }
986
+ /**
987
+ * Handle beam/update-metadata tool - update photon or method metadata
988
+ */
989
+ async function handleBeamUpdateMetadata(req, ctx, args) {
990
+ const { photon: photonName, method: methodName, metadata, } = args;
991
+ if (!photonName) {
992
+ return {
993
+ jsonrpc: '2.0',
994
+ id: req.id,
995
+ result: {
996
+ content: [{ type: 'text', text: 'Error: photon name is required' }],
997
+ isError: true,
998
+ },
999
+ };
1000
+ }
1001
+ if (!metadata || typeof metadata !== 'object') {
1002
+ return {
1003
+ jsonrpc: '2.0',
1004
+ id: req.id,
1005
+ result: {
1006
+ content: [{ type: 'text', text: 'Error: metadata object is required' }],
1007
+ isError: true,
1008
+ },
1009
+ };
1010
+ }
1011
+ if (!ctx.updateMetadata) {
1012
+ return {
1013
+ jsonrpc: '2.0',
1014
+ id: req.id,
1015
+ result: {
1016
+ content: [{ type: 'text', text: 'Error: Update metadata not supported in this context' }],
1017
+ isError: true,
1018
+ },
1019
+ };
1020
+ }
1021
+ try {
1022
+ const result = await ctx.updateMetadata(photonName, methodName || null, metadata);
1023
+ if (result.success) {
1024
+ return {
1025
+ jsonrpc: '2.0',
1026
+ id: req.id,
1027
+ result: {
1028
+ content: [
1029
+ {
1030
+ type: 'text',
1031
+ text: `Successfully updated metadata for ${methodName ? `${photonName}/${methodName}` : photonName}`,
1032
+ },
1033
+ ],
1034
+ isError: false,
1035
+ },
1036
+ };
1037
+ }
1038
+ else {
1039
+ return {
1040
+ jsonrpc: '2.0',
1041
+ id: req.id,
1042
+ result: {
1043
+ content: [{ type: 'text', text: `Failed to update metadata: ${result.error}` }],
1044
+ isError: true,
1045
+ },
1046
+ };
1047
+ }
1048
+ }
1049
+ catch (error) {
1050
+ const message = error instanceof Error ? error.message : String(error);
1051
+ return {
1052
+ jsonrpc: '2.0',
1053
+ id: req.id,
1054
+ result: {
1055
+ content: [{ type: 'text', text: `Error updating metadata: ${message}` }],
1056
+ isError: true,
1057
+ },
1058
+ };
1059
+ }
1060
+ }
1061
+ /**
1062
+ * Handle beam/photon-help tool - get rich documentation for a photon
1063
+ */
1064
+ async function handleBeamPhotonHelp(req, ctx, args) {
1065
+ const { photon: photonName } = args;
1066
+ if (!photonName) {
1067
+ return {
1068
+ jsonrpc: '2.0',
1069
+ id: req.id,
1070
+ result: {
1071
+ content: [{ type: 'text', text: 'Error: photon name is required' }],
1072
+ isError: true,
1073
+ },
1074
+ };
1075
+ }
1076
+ if (!ctx.generatePhotonHelp) {
1077
+ return {
1078
+ jsonrpc: '2.0',
1079
+ id: req.id,
1080
+ result: {
1081
+ content: [{ type: 'text', text: 'Error: Help generation not supported in this context' }],
1082
+ isError: true,
1083
+ },
1084
+ };
1085
+ }
1086
+ try {
1087
+ const markdown = await ctx.generatePhotonHelp(photonName);
1088
+ return {
1089
+ jsonrpc: '2.0',
1090
+ id: req.id,
1091
+ result: {
1092
+ content: [{ type: 'text', text: markdown }],
1093
+ isError: false,
1094
+ },
1095
+ };
1096
+ }
1097
+ catch (error) {
1098
+ const message = error instanceof Error ? error.message : String(error);
1099
+ return {
1100
+ jsonrpc: '2.0',
1101
+ id: req.id,
1102
+ result: {
1103
+ content: [{ type: 'text', text: `Error generating help: ${message}` }],
1104
+ isError: true,
1105
+ },
1106
+ };
1107
+ }
1108
+ }
1109
+ /**
1110
+ * Handle MCP Streamable HTTP requests
1111
+ */
1112
+ export async function handleStreamableHTTP(req, res, options) {
1113
+ const url = new URL(req.url || '/', `http://${req.headers.host}`);
1114
+ // Only handle /mcp endpoint
1115
+ if (url.pathname !== '/mcp') {
1116
+ return false;
1117
+ }
1118
+ // CORS headers
1119
+ res.setHeader('Access-Control-Allow-Origin', '*');
1120
+ res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
1121
+ res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Accept, Mcp-Session-Id');
1122
+ res.setHeader('Access-Control-Expose-Headers', 'Mcp-Session-Id');
1123
+ // Handle preflight
1124
+ if (req.method === 'OPTIONS') {
1125
+ res.writeHead(204);
1126
+ res.end();
1127
+ return true;
1128
+ }
1129
+ // Get or create session
1130
+ // Check header first, then query parameter (for SSE which can't set headers)
1131
+ let sessionId = req.headers['mcp-session-id'];
1132
+ if (!sessionId) {
1133
+ sessionId = url.searchParams.get('sessionId') || undefined;
1134
+ }
1135
+ const session = getOrCreateSession(sessionId);
1136
+ // GET - Open SSE stream for server notifications
1137
+ if (req.method === 'GET') {
1138
+ const accept = req.headers.accept || '';
1139
+ if (!accept.includes('text/event-stream')) {
1140
+ res.writeHead(406);
1141
+ res.end('Accept header must include text/event-stream');
1142
+ return true;
1143
+ }
1144
+ res.writeHead(200, {
1145
+ 'Content-Type': 'text/event-stream',
1146
+ 'Cache-Control': 'no-cache',
1147
+ Connection: 'keep-alive',
1148
+ 'X-Accel-Buffering': 'no', // Disable nginx buffering
1149
+ 'Mcp-Session-Id': session.id,
1150
+ });
1151
+ // Disable Nagle's algorithm for immediate writes
1152
+ res.socket?.setNoDelay(true);
1153
+ // Store SSE response for server-initiated messages
1154
+ session.sseResponse = res;
1155
+ // Keep connection alive
1156
+ const keepAlive = setInterval(() => {
1157
+ res.write(': keepalive\n\n');
1158
+ }, 30000);
1159
+ req.on('close', () => {
1160
+ clearInterval(keepAlive);
1161
+ session.sseResponse = undefined;
1162
+ // Clean up subscriptions when client disconnects
1163
+ if (options.subscriptionManager) {
1164
+ options.subscriptionManager.onClientDisconnect(session.id);
1165
+ }
1166
+ });
1167
+ return true;
1168
+ }
1169
+ // POST - Handle JSON-RPC requests
1170
+ if (req.method === 'POST') {
1171
+ const accept = req.headers.accept || '';
1172
+ const wantsSSE = accept.includes('text/event-stream');
1173
+ // Read body
1174
+ let body = '';
1175
+ for await (const chunk of req) {
1176
+ body += chunk;
1177
+ }
1178
+ let requests;
1179
+ try {
1180
+ const parsed = JSON.parse(body);
1181
+ requests = Array.isArray(parsed) ? parsed : [parsed];
1182
+ }
1183
+ catch {
1184
+ res.writeHead(400);
1185
+ res.end(JSON.stringify({ error: 'Invalid JSON' }));
1186
+ return true;
1187
+ }
1188
+ const context = {
1189
+ photons: options.photons,
1190
+ photonMCPs: options.photonMCPs,
1191
+ loadUIAsset: options.loadUIAsset,
1192
+ configurePhoton: options.configurePhoton,
1193
+ reloadPhoton: options.reloadPhoton,
1194
+ removePhoton: options.removePhoton,
1195
+ updateMetadata: options.updateMetadata,
1196
+ generatePhotonHelp: options.generatePhotonHelp,
1197
+ loader: options.loader,
1198
+ broadcast: options.broadcast,
1199
+ subscriptionManager: options.subscriptionManager,
1200
+ };
1201
+ // Process requests
1202
+ const responses = [];
1203
+ for (const request of requests) {
1204
+ const handler = handlers[request.method];
1205
+ if (!handler) {
1206
+ if (request.id !== undefined) {
1207
+ responses.push({
1208
+ jsonrpc: '2.0',
1209
+ id: request.id,
1210
+ error: { code: -32601, message: `Method not found: ${request.method}` },
1211
+ });
1212
+ }
1213
+ continue;
1214
+ }
1215
+ const response = await handler(request, session, context);
1216
+ // Only include responses for requests (not notifications)
1217
+ if (request.id !== undefined && response.id !== undefined) {
1218
+ responses.push(response);
1219
+ }
1220
+ }
1221
+ // Send response
1222
+ if (responses.length === 0) {
1223
+ // All were notifications
1224
+ res.writeHead(202);
1225
+ res.end();
1226
+ }
1227
+ else if (wantsSSE) {
1228
+ // SSE response
1229
+ res.writeHead(200, {
1230
+ 'Content-Type': 'text/event-stream',
1231
+ 'Cache-Control': 'no-cache',
1232
+ 'Mcp-Session-Id': session.id,
1233
+ });
1234
+ for (const response of responses) {
1235
+ res.write(`data: ${JSON.stringify(response)}\n\n`);
1236
+ }
1237
+ res.end();
1238
+ }
1239
+ else {
1240
+ // JSON response
1241
+ res.writeHead(200, {
1242
+ 'Content-Type': 'application/json',
1243
+ 'Mcp-Session-Id': session.id,
1244
+ });
1245
+ const result = responses.length === 1 ? responses[0] : responses;
1246
+ res.end(JSON.stringify(result));
1247
+ }
1248
+ return true;
1249
+ }
1250
+ // Method not allowed
1251
+ res.writeHead(405);
1252
+ res.end('Method not allowed');
1253
+ return true;
1254
+ }
1255
+ /**
1256
+ * Send a notification to all connected SSE clients
1257
+ * @param method - The notification method name
1258
+ * @param params - Optional parameters for the notification
1259
+ * @param beamOnly - If true, only send to Beam clients (clientInfo.name === "beam")
1260
+ */
1261
+ export function broadcastNotification(method, params, beamOnly = false) {
1262
+ const notification = {
1263
+ jsonrpc: '2.0',
1264
+ method,
1265
+ params,
1266
+ };
1267
+ for (const session of sessions.values()) {
1268
+ if (session.sseResponse && !session.sseResponse.writableEnded) {
1269
+ // Skip non-Beam clients if beamOnly is true
1270
+ if (beamOnly && !session.isBeam)
1271
+ continue;
1272
+ session.sseResponse.write(`data: ${JSON.stringify(notification)}\n\n`);
1273
+ }
1274
+ }
1275
+ }
1276
+ /**
1277
+ * Send a notification to Beam clients only
1278
+ */
1279
+ export function broadcastToBeam(method, params) {
1280
+ broadcastNotification(method, params, true);
1281
+ }
1282
+ /**
1283
+ * Get count of active sessions (for debugging)
1284
+ */
1285
+ export function getActiveSessionCount() {
1286
+ let total = 0;
1287
+ let beam = 0;
1288
+ for (const session of sessions.values()) {
1289
+ if (session.sseResponse && !session.sseResponse.writableEnded) {
1290
+ total++;
1291
+ if (session.isBeam)
1292
+ beam++;
1293
+ }
1294
+ }
1295
+ return { total, beam };
1296
+ }
1297
+ /**
1298
+ * Send a notification to a specific session by ID
1299
+ * Used for replaying missed events on reconnect
1300
+ */
1301
+ export function sendToSession(sessionId, method, params) {
1302
+ const session = sessions.get(sessionId);
1303
+ if (!session?.sseResponse || session.sseResponse.writableEnded) {
1304
+ return false;
1305
+ }
1306
+ const notification = {
1307
+ jsonrpc: '2.0',
1308
+ method,
1309
+ params,
1310
+ };
1311
+ session.sseResponse.write(`data: ${JSON.stringify(notification)}\n\n`);
1312
+ return true;
1313
+ }
1314
+ //# sourceMappingURL=streamable-http-transport.js.map