@eventcatalog/core 5.0.0-beta.3 → 5.0.0-beta.30

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 (277) hide show
  1. package/LICENSE +234 -134
  2. package/README.md +5 -5
  3. package/dist/analytics/analytics.cjs +18 -3
  4. package/dist/analytics/analytics.d.cts +1 -1
  5. package/dist/analytics/analytics.d.ts +1 -1
  6. package/dist/analytics/analytics.js +3 -2
  7. package/dist/analytics/log-build.cjs +160 -77
  8. package/dist/analytics/log-build.d.cts +2 -2
  9. package/dist/analytics/log-build.d.ts +2 -2
  10. package/dist/analytics/log-build.js +7 -5
  11. package/dist/analytics/studio-usage.cjs +90 -0
  12. package/dist/analytics/studio-usage.d.cts +5 -0
  13. package/dist/analytics/studio-usage.d.ts +5 -0
  14. package/dist/analytics/studio-usage.js +10 -0
  15. package/dist/analytics/telemetry.cjs +42 -0
  16. package/dist/analytics/telemetry.d.cts +20 -0
  17. package/dist/analytics/telemetry.d.ts +20 -0
  18. package/dist/analytics/telemetry.js +8 -0
  19. package/dist/catalog-runtime.cjs +1 -1
  20. package/dist/{chunk-RFIXKATC.js → chunk-4G7FSRPN.js} +35 -42
  21. package/dist/{chunk-6OC46WQJ.js → chunk-55QETQZ3.js} +66 -35
  22. package/dist/{chunk-TQLRG35T.js → chunk-AD7CL772.js} +78 -45
  23. package/dist/chunk-CA6WCQ2T.js +54 -0
  24. package/dist/chunk-CPW6G62V.js +17 -0
  25. package/dist/{chunk-423S2KHN.js → chunk-FLAZ2JVN.js} +4 -2
  26. package/dist/{chunk-BH2F5WPN.js → chunk-GQI5UYXZ.js} +1 -1
  27. package/dist/{chunk-GYIVJXOJ.js → chunk-NA7OTBWW.js} +1 -1
  28. package/dist/{chunk-MDWB5HLO.js → chunk-NKNOXQ62.js} +1 -1
  29. package/dist/{chunk-3PKS2IIP.js → chunk-OX6TSVHY.js} +1 -1
  30. package/dist/{chunk-36RYTYT5.js → chunk-PD7KTSYN.js} +1 -1
  31. package/dist/chunk-S5GWAUG5.js +29 -0
  32. package/dist/{chunk-NDJOAITI.js → chunk-T3OROMAU.js} +9 -3
  33. package/dist/constants.cjs +1 -1
  34. package/dist/constants.js +1 -1
  35. package/dist/eventcatalog-config-file-utils.cjs +34 -41
  36. package/dist/eventcatalog-config-file-utils.d.cts +56 -58
  37. package/dist/eventcatalog-config-file-utils.d.ts +56 -58
  38. package/dist/eventcatalog-config-file-utils.js +1 -1
  39. package/dist/eventcatalog.cjs +531 -356
  40. package/dist/eventcatalog.config.d.cts +59 -1
  41. package/dist/eventcatalog.config.d.ts +59 -1
  42. package/dist/eventcatalog.js +63 -25
  43. package/dist/features.cjs +34 -41
  44. package/dist/features.js +2 -2
  45. package/dist/federation/federate.cjs +37 -44
  46. package/dist/federation/federate.js +2 -2
  47. package/dist/generate.cjs +37 -42
  48. package/dist/generate.js +4 -4
  49. package/dist/resolve-catalog-dependencies.cjs +34 -41
  50. package/dist/resolve-catalog-dependencies.js +2 -2
  51. package/dist/runtime-dependencies/@eventcatalog__visualiser__studio-fonts.css.mjs +3 -0
  52. package/dist/runtime-dependencies/@eventcatalog__visualiser__studio.css.mjs +3 -0
  53. package/dist/runtime-dependencies/@eventcatalog__visualiser__studio.mjs +3 -0
  54. package/dist/runtime-dependencies/@hocuspocus__provider.mjs +3 -0
  55. package/dist/runtime-dependencies/@hocuspocus__server.mjs +3 -0
  56. package/dist/runtime-dependencies/@mcp-b__webmcp-local-relay.mjs +3 -0
  57. package/dist/runtime-dependencies/@mcp-b__webmcp-polyfill.mjs +3 -0
  58. package/dist/runtime-dependencies/htmlparser2.mjs +3 -0
  59. package/dist/runtime-dependencies/manifest.json +13 -1
  60. package/dist/runtime-dependencies/react-hotkeys-hook.mjs +3 -0
  61. package/dist/runtime-dependencies/react-syntax-highlighter__dist__cjs__create-element.js.mjs +3 -0
  62. package/dist/runtime-dependencies/y-protocols.mjs +3 -0
  63. package/dist/runtime-dependencies/y-protocols__awareness.mjs +3 -0
  64. package/dist/runtime-dependencies/yjs.mjs +3 -0
  65. package/dist/utils/child-process-signals.cjs +53 -0
  66. package/dist/utils/child-process-signals.d.cts +19 -0
  67. package/dist/utils/child-process-signals.d.ts +19 -0
  68. package/dist/utils/child-process-signals.js +6 -0
  69. package/dist/utils/cli-logger.cjs +1 -1
  70. package/dist/utils/cli-logger.js +2 -2
  71. package/dist/utils/license-status.cjs +78 -45
  72. package/dist/utils/license-status.d.cts +15 -5
  73. package/dist/utils/license-status.d.ts +15 -5
  74. package/dist/utils/license-status.js +1 -1
  75. package/eventcatalog/astro.config.mjs +23 -1
  76. package/eventcatalog/integrations/config-loader.mjs +56 -58
  77. package/eventcatalog/integrations/skip-startup-reload.mjs +31 -0
  78. package/eventcatalog/integrations/stable-dependency-order.mjs +29 -0
  79. package/eventcatalog/integrations/storage.mjs +20 -0
  80. package/eventcatalog/integrations/studio-server.mjs +32 -0
  81. package/eventcatalog/src/components/CatalogGraph/CatalogForceGraph.tsx +39 -0
  82. package/eventcatalog/src/components/ChatPanel/ChatPanel.tsx +2 -0
  83. package/eventcatalog/src/components/Header.astro +3 -3
  84. package/eventcatalog/src/components/Homepage/CatalogDashboard.astro +342 -0
  85. package/eventcatalog/src/components/MDX/NodeGraph/AstroNodeGraph.tsx +60 -4
  86. package/eventcatalog/src/components/MDX/NodeGraph/NodeGraph.astro +5 -1
  87. package/eventcatalog/src/components/NavigationProgress.astro +7 -0
  88. package/eventcatalog/src/components/SchemaExplorer/ApiContentViewer.tsx +2 -0
  89. package/eventcatalog/src/components/SchemaExplorer/ExamplesViewer.tsx +2 -0
  90. package/eventcatalog/src/components/SchemaExplorer/SchemaCodeModal.tsx +2 -0
  91. package/eventcatalog/src/components/SchemaExplorer/SchemaContentViewer.tsx +2 -0
  92. package/eventcatalog/src/components/Search/SearchDataLoader.astro +16 -28
  93. package/eventcatalog/src/components/Search/SearchModal.tsx +2 -10
  94. package/eventcatalog/src/components/Search/search-utils.ts +26 -0
  95. package/eventcatalog/src/components/Seo.astro +5 -1
  96. package/eventcatalog/src/components/Settings/AssistantSettingsForm.tsx +53 -48
  97. package/eventcatalog/src/components/Settings/GeneralSettingsForm.tsx +183 -178
  98. package/eventcatalog/src/components/Settings/LicenseSettings.tsx +140 -31
  99. package/eventcatalog/src/components/Settings/LlmAccessSettingsForm.tsx +40 -35
  100. package/eventcatalog/src/components/Settings/LogoUpload.tsx +1 -1
  101. package/eventcatalog/src/components/Settings/McpSettingsForm.tsx +9 -8
  102. package/eventcatalog/src/components/Settings/Row.tsx +7 -4
  103. package/eventcatalog/src/components/Settings/SettingsShared.tsx +3 -3
  104. package/eventcatalog/src/components/SideNav/NestedSideBar/index.tsx +53 -83
  105. package/eventcatalog/src/components/SideNav/NestedSideBar/storage.ts +2 -0
  106. package/eventcatalog/src/components/SideNav/NestedSideBar/utils.ts +131 -0
  107. package/eventcatalog/src/components/SideNav/SideNav.astro +9 -1
  108. package/eventcatalog/src/components/TrialBadge.astro +20 -9
  109. package/eventcatalog/src/content.config.ts +19 -2
  110. package/eventcatalog/src/features/auth/error.astro +4 -2
  111. package/eventcatalog/src/features/auth/login.astro +2 -1
  112. package/eventcatalog/src/features/auth/middleware/middleware-auth.ts +10 -0
  113. package/eventcatalog/src/features/auth/unauthorized.astro +1 -1
  114. package/eventcatalog/src/features/fields/pages/fields.astro +1 -1
  115. package/eventcatalog/src/features/integrations/eventcatalog-features.ts +34 -1
  116. package/eventcatalog/src/features/mcp/apps/architecture-diagram/node-chat.tsx +6 -45
  117. package/eventcatalog/src/features/mcp/apps/canvas/shared.ts +47 -0
  118. package/eventcatalog/src/features/mcp/apps/canvas/styles.css +24 -0
  119. package/eventcatalog/src/features/mcp/apps/canvas/view.tsx +278 -0
  120. package/eventcatalog/src/features/mcp/apps/generated/canvas.html +272 -0
  121. package/eventcatalog/src/features/mcp/apps/generated/viewer.html +65 -65
  122. package/eventcatalog/src/features/mcp/apps/shared/app-view.tsx +2 -1
  123. package/eventcatalog/src/features/mcp/mcp-apps.ts +6 -3
  124. package/eventcatalog/src/features/mcp/mcp-server.ts +36 -5
  125. package/eventcatalog/src/features/server/start.ts +25 -0
  126. package/eventcatalog/src/features/storage/database.ts +88 -0
  127. package/eventcatalog/src/features/storage/migrations.ts +85 -0
  128. package/eventcatalog/src/features/storage/sql/database.sql +5 -0
  129. package/eventcatalog/src/features/storage/sql/migrations/0001-studio-canvases.sql +12 -0
  130. package/eventcatalog/src/features/storage/sql/migrations.sql +15 -0
  131. package/eventcatalog/src/features/storage/sql.ts +28 -0
  132. package/eventcatalog/src/features/studio/agent-choreography.ts +384 -0
  133. package/eventcatalog/src/features/studio/agents.ts +28 -0
  134. package/eventcatalog/src/features/studio/api/canvases-api.ts +219 -0
  135. package/eventcatalog/src/features/studio/api/canvases.ts +25 -0
  136. package/eventcatalog/src/features/studio/api/client.ts +40 -0
  137. package/eventcatalog/src/features/studio/canvas-actions.ts +540 -0
  138. package/eventcatalog/src/features/studio/canvas-preview.ts +52 -0
  139. package/eventcatalog/src/features/studio/catalog-resources.ts +155 -0
  140. package/eventcatalog/src/features/studio/catalog.ts +173 -0
  141. package/eventcatalog/src/features/studio/components/CanvasHeader.tsx +518 -0
  142. package/eventcatalog/src/features/studio/components/CanvasStatusMenu.tsx +118 -0
  143. package/eventcatalog/src/features/studio/components/ConnectAgentDialog.tsx +434 -0
  144. package/eventcatalog/src/features/studio/components/ConnectionsChooser.tsx +143 -0
  145. package/eventcatalog/src/features/studio/components/DeleteCanvasDialog.tsx +65 -0
  146. package/eventcatalog/src/features/studio/components/Dialog.tsx +60 -0
  147. package/eventcatalog/src/features/studio/components/JoinForm.tsx +109 -0
  148. package/eventcatalog/src/features/studio/components/LeftPanel.tsx +206 -0
  149. package/eventcatalog/src/features/studio/components/NodeDetails.tsx +183 -0
  150. package/eventcatalog/src/features/studio/components/OpenInStudioDialog.tsx +79 -0
  151. package/eventcatalog/src/features/studio/components/Presence.tsx +293 -0
  152. package/eventcatalog/src/features/studio/components/ShareDialog.tsx +396 -0
  153. package/eventcatalog/src/features/studio/components/ShortcutsDialog.tsx +41 -0
  154. package/eventcatalog/src/features/studio/components/StudioDesigner.tsx +2091 -0
  155. package/eventcatalog/src/features/studio/components/StudioNotice.astro +24 -0
  156. package/eventcatalog/src/features/studio/document-name.ts +7 -0
  157. package/eventcatalog/src/features/studio/from-visualiser.ts +138 -0
  158. package/eventcatalog/src/features/studio/hooks/presence-sender.ts +63 -0
  159. package/eventcatalog/src/features/studio/hooks/presence-store.ts +119 -0
  160. package/eventcatalog/src/features/studio/hooks/use-canvas-webmcp.ts +353 -0
  161. package/eventcatalog/src/features/studio/hooks/use-comments.ts +54 -0
  162. package/eventcatalog/src/features/studio/hooks/use-follow-camera.ts +109 -0
  163. package/eventcatalog/src/features/studio/hooks/use-studio-flow.ts +782 -0
  164. package/eventcatalog/src/features/studio/identity.ts +59 -0
  165. package/eventcatalog/src/features/studio/limits.ts +2 -0
  166. package/eventcatalog/src/features/studio/pages/[id].astro +69 -0
  167. package/eventcatalog/src/features/studio/pages/index.astro +784 -0
  168. package/eventcatalog/src/features/studio/pages/webmcp-relay.ts +27 -0
  169. package/eventcatalog/src/features/studio/server/canvas-mcp.ts +495 -0
  170. package/eventcatalog/src/features/studio/server/runtime.ts +498 -0
  171. package/eventcatalog/src/features/studio/server/sign-in.ts +18 -0
  172. package/eventcatalog/src/features/studio/server/sql/storage.sql +18 -0
  173. package/eventcatalog/src/features/studio/server/storage.ts +90 -0
  174. package/eventcatalog/src/features/studio/server/tool-icons.ts +30 -0
  175. package/eventcatalog/src/features/studio/tool-sync.ts +75 -0
  176. package/eventcatalog/src/layouts/BaseLayout.astro +4 -4
  177. package/eventcatalog/src/layouts/SettingsLayout.astro +2 -2
  178. package/eventcatalog/src/layouts/VerticalSideBarLayout.astro +13 -5
  179. package/eventcatalog/src/pages/_index.astro +5 -342
  180. package/eventcatalog/src/pages/architecture/[type]/[id]/[version]/_index.data.spec.ts +2 -3
  181. package/eventcatalog/src/pages/architecture/[type]/[id]/[version]/_index.data.ts +0 -7
  182. package/eventcatalog/src/pages/architecture/[type]/[id]/[version]/index.astro +3 -1
  183. package/eventcatalog/src/pages/diagrams/[id]/[version]/_index.data.ts +0 -7
  184. package/eventcatalog/src/pages/diagrams/[id]/[version]/embed.astro +1 -0
  185. package/eventcatalog/src/pages/diagrams/[id]/[version]/index.astro +1 -0
  186. package/eventcatalog/src/pages/directory/[type]/_index.data.ts +5 -7
  187. package/eventcatalog/src/pages/directory/[type]/index.astro +3 -1
  188. package/eventcatalog/src/pages/discover/[type]/_index.data.ts +5 -7
  189. package/eventcatalog/src/pages/discover/[type]/index.astro +3 -1
  190. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/[docType]/[docId]/[docVersion]/_index.data.ts +0 -7
  191. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/[docType]/[docId]/[docVersion]/index.astro +1 -0
  192. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/[docType]/[docId]/_index.data.ts +0 -7
  193. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/[docType]/[docId]/index.astro +1 -0
  194. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/_index.data.ts +0 -7
  195. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/asyncapi/[filename].astro +3 -1
  196. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/asyncapi/_[filename].data.ts +0 -7
  197. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/changelog/_index.data.ts +0 -7
  198. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/changelog/index.astro +1 -0
  199. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/examples/[...filename].astro +1 -0
  200. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/examples/_[...filename].data.ts +0 -4
  201. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/field-lineage/_index.data.ts +0 -7
  202. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/field-lineage/index.astro +1 -0
  203. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/graphql/[filename].astro +3 -1
  204. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/graphql/_[filename].data.ts +0 -7
  205. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/index.astro +1 -0
  206. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/resources/_index.data.spec.ts +2 -3
  207. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/resources/_index.data.ts +0 -7
  208. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/resources/index.astro +1 -0
  209. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/spec/[filename].astro +1 -0
  210. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/spec/_[filename].data.ts +0 -7
  211. package/eventcatalog/src/pages/docs/[type]/[id]/_index.data.ts +0 -7
  212. package/eventcatalog/src/pages/docs/[type]/[id]/index.astro +2 -1
  213. package/eventcatalog/src/pages/docs/[type]/[id]/language/[dictionaryId]/_index.data.ts +0 -7
  214. package/eventcatalog/src/pages/docs/[type]/[id]/language/[dictionaryId]/index.astro +1 -0
  215. package/eventcatalog/src/pages/docs/[type]/[id]/language/_index.data.ts +0 -7
  216. package/eventcatalog/src/pages/docs/[type]/[id]/language/index.astro +1 -0
  217. package/eventcatalog/src/pages/docs/custom/[...path]/_index.data.ts +0 -7
  218. package/eventcatalog/src/pages/docs/custom/[...path]/index.astro +1 -0
  219. package/eventcatalog/src/pages/docs/services/[id]/[docType]/[docId]/index.astro +1 -1
  220. package/eventcatalog/src/pages/docs/services/[id]/[version]/resources/index.astro +1 -0
  221. package/eventcatalog/src/pages/docs/services/[id]/asyncapi/[filename].astro +1 -1
  222. package/eventcatalog/src/pages/docs/services/[id]/changelog/index.astro +1 -1
  223. package/eventcatalog/src/pages/docs/services/[id]/graphql/[filename].astro +1 -1
  224. package/eventcatalog/src/pages/docs/services/[id]/spec/[filename].astro +1 -1
  225. package/eventcatalog/src/pages/docs/teams/[id]/_index.data.ts +0 -7
  226. package/eventcatalog/src/pages/docs/teams/[id]/index.astro +1 -0
  227. package/eventcatalog/src/pages/docs/users/[id]/_index.data.ts +0 -7
  228. package/eventcatalog/src/pages/docs/users/[id]/index.astro +1 -0
  229. package/eventcatalog/src/pages/index.astro +4 -2
  230. package/eventcatalog/src/pages/schemas/[type]/[id]/[version]/_index.data.ts +0 -7
  231. package/eventcatalog/src/pages/schemas/[type]/[id]/[version]/index.astro +1 -0
  232. package/eventcatalog/src/pages/schemas/explorer/_index.data.ts +0 -7
  233. package/eventcatalog/src/pages/schemas/explorer/index.astro +4 -2
  234. package/eventcatalog/src/pages/settings/license.astro +9 -1
  235. package/eventcatalog/src/pages/triggers/[type]/[id]/[version]/_index.data.ts +0 -7
  236. package/eventcatalog/src/pages/triggers/[type]/[id]/[version]/index.astro +1 -0
  237. package/eventcatalog/src/pages/visualiser/[type]/[id]/[version]/_index.data.ts +0 -7
  238. package/eventcatalog/src/pages/visualiser/[type]/[id]/[version]/data/_index.data.ts +0 -7
  239. package/eventcatalog/src/pages/visualiser/[type]/[id]/[version]/data/index.astro +1 -0
  240. package/eventcatalog/src/pages/visualiser/[type]/[id]/[version]/entity-map/_index.data.ts +0 -7
  241. package/eventcatalog/src/pages/visualiser/[type]/[id]/[version]/entity-map/index.astro +1 -0
  242. package/eventcatalog/src/pages/visualiser/[type]/[id]/[version]/index.astro +1 -0
  243. package/eventcatalog/src/pages/visualiser/[type]/[id]/_index.data.ts +38 -0
  244. package/eventcatalog/src/pages/visualiser/[type]/[id]/index.astro +18 -41
  245. package/eventcatalog/src/pages/visualiser/designs/[id]/_index.data.ts +0 -7
  246. package/eventcatalog/src/pages/visualiser/designs/[id]/index.astro +2 -1
  247. package/eventcatalog/src/pages/visualiser/domains/[id]/[version]/entity-map/_index.data.ts +0 -7
  248. package/eventcatalog/src/pages/visualiser/domains/[id]/[version]/entity-map/index.astro +1 -0
  249. package/eventcatalog/src/pages/visualiser/domains/[id]/[version]/index.astro +1 -0
  250. package/eventcatalog/src/pages/visualiser/domains/[id]/[version]/systems-context/_index.data.ts +0 -7
  251. package/eventcatalog/src/pages/visualiser/domains/[id]/[version]/systems-context/index.astro +1 -0
  252. package/eventcatalog/src/pages/visualiser/systems/[id]/[version]/context/_index.data.ts +0 -7
  253. package/eventcatalog/src/pages/visualiser/systems/[id]/[version]/context/index.astro +1 -0
  254. package/eventcatalog/src/pages/visualiser/systems/[id]/[version]/index.astro +1 -0
  255. package/eventcatalog/src/types/react-syntax-highlighter.d.ts +10 -0
  256. package/eventcatalog/src/utils/collections/util.ts +8 -4
  257. package/eventcatalog/src/utils/feature.ts +8 -0
  258. package/eventcatalog/src/utils/git.ts +22 -5
  259. package/eventcatalog/src/utils/homepage-dashboard.ts +143 -0
  260. package/eventcatalog/src/utils/license.ts +1 -1
  261. package/eventcatalog/src/utils/link-validation.ts +25 -19
  262. package/eventcatalog/src/utils/node-graphs/domain-entity-map.ts +3 -1
  263. package/eventcatalog/src/utils/node-graphs/domain-levels-node-graph.ts +3 -3
  264. package/eventcatalog/src/utils/node-graphs/export-mermaid.ts +0 -2
  265. package/eventcatalog/src/utils/node-graphs/field-node-graph.ts +4 -2
  266. package/eventcatalog/src/utils/node-graphs/layout-node-graph.ts +20 -1
  267. package/eventcatalog/src/utils/node-graphs/node-resource.ts +67 -0
  268. package/eventcatalog/src/utils/node-graphs/systems-node-graph.ts +1 -1
  269. package/eventcatalog/src/utils/page-loaders/hybrid-page.ts +4 -15
  270. package/eventcatalog/src/utils/page-title.ts +15 -0
  271. package/eventcatalog/src/utils/syntax-highlighter-renderer.ts +64 -0
  272. package/eventcatalog/src/utils/trial.ts +14 -1
  273. package/package.json +19 -13
  274. package/dist/runtime-dependencies/shelljs.mjs +0 -3
  275. package/eventcatalog/public/studio-bg.png +0 -0
  276. package/eventcatalog/src/components/Studio/StudioPageModal.tsx +0 -150
  277. package/eventcatalog/src/pages/studio.astro +0 -316
@@ -0,0 +1,27 @@
1
+ import type { APIRoute } from 'astro';
2
+ import fs from 'node:fs/promises';
3
+ import path from 'node:path';
4
+ import { createRequire } from 'node:module';
5
+
6
+ /**
7
+ * Serves the WebMCP local relay's browser files (embed.js loads widget.html next to it), so the canvas page
8
+ * can hand its WebMCP tools to desktop MCP clients (Claude Code, Cursor...) running
9
+ * `npx -y @mcp-b/webmcp-local-relay@5.1.0`. Served from the installed package, so the version is pinned.
10
+ */
11
+ const FILES: Record<string, string> = {
12
+ 'embed.js': 'text/javascript',
13
+ 'widget.html': 'text/html',
14
+ 'widget.js': 'text/javascript',
15
+ };
16
+
17
+ const browserDirectory = path.join(path.dirname(createRequire(import.meta.url).resolve('@mcp-b/webmcp-local-relay')), 'browser');
18
+
19
+ export const GET: APIRoute = async ({ params }) => {
20
+ const file = params.file ?? '';
21
+ const contentType = FILES[file];
22
+ if (!contentType) return new Response('Not found', { status: 404 });
23
+ const body = await fs.readFile(path.join(browserDirectory, file), 'utf-8');
24
+ return new Response(body, { headers: { 'Content-Type': contentType, 'Cache-Control': 'no-cache' } });
25
+ };
26
+
27
+ export const prerender = false;
@@ -0,0 +1,495 @@
1
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import type * as Y from 'yjs';
3
+ import { z } from 'zod';
4
+ import { buildUrl } from '@utils/url-builder';
5
+ import { appViewMeta, getMcpAppViewLoader, registerMcpApp } from '@features/mcp/mcp-apps';
6
+ import {
7
+ CANVAS_CATALOG_TOOL,
8
+ CANVAS_SYNC_TOOL,
9
+ CANVAS_META_KEY,
10
+ CANVAS_RESOURCE_URI,
11
+ CANVAS_VIEW_TOOL,
12
+ type CanvasPayload,
13
+ } from '@features/mcp/apps/canvas/shared';
14
+ import {
15
+ CANVAS_STATUSES,
16
+ getCanvasMaps,
17
+ readCanvas,
18
+ readMeta,
19
+ reopeningIfEdited,
20
+ replyToThread,
21
+ setCanvasStatus,
22
+ setThreadResolved,
23
+ type Author,
24
+ getLayoutPositions,
25
+ } from '@eventcatalog/visualiser/studio';
26
+ import { ADD_TO_CANVAS_DESCRIPTION, describeCanvas, edgeSpecsSchema, indexCatalog, nodeSpecsSchema } from '../canvas-actions';
27
+ import { getCatalogResources } from '../catalog-resources';
28
+ import {
29
+ playAddToCanvas,
30
+ playComment,
31
+ playConnectAll,
32
+ playLayout,
33
+ playOnNode,
34
+ playRemove,
35
+ playUpdateNode,
36
+ type AgentStage,
37
+ } from '../agent-choreography';
38
+ import { STUDIO_SOCKET_PATH, findStudioRuntime } from './runtime';
39
+ import { getAgent } from '../agents';
40
+ import { addToolIcons } from './tool-icons';
41
+
42
+ /**
43
+ * MCP tools for the collaborative canvas: agents create canvases, join the ones people are on, and
44
+ * add, connect, move and comment on things there, live, like anyone else on the canvas. openCanvas
45
+ * also shows the canvas in the chat (MCP App), where the user works on it with everyone else.
46
+ */
47
+
48
+ const loadCanvasView = getMcpAppViewLoader('canvas');
49
+
50
+ // The canvases entrypoint's icon (OpenAI MCP extensions: a 20x20, monochrome SVG in currentColor, 1.33px strokes)
51
+ const CANVAS_ICON_SVG =
52
+ '<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="1.33" stroke-linecap="round" stroke-linejoin="round"><rect x="2.5" y="3.5" width="5.5" height="4.5" rx="1"/><rect x="12" y="3.5" width="5.5" height="4.5" rx="1"/><rect x="12" y="12" width="5.5" height="4.5" rx="1"/><path d="M8 5.75h4M14.75 8v4"/></svg>';
53
+ const CANVAS_ICON = { src: `data:image/svg+xml;base64,${btoa(CANVAS_ICON_SVG)}`, mimeType: 'image/svg+xml', sizes: ['20x20'] };
54
+
55
+ const text = (value: unknown, isError = false) => ({
56
+ content: [{ type: 'text' as const, text: typeof value === 'string' ? value : JSON.stringify(value, null, 2) }],
57
+ ...(isError && { isError }),
58
+ });
59
+
60
+ const canvasId = z.string().describe('The id of the canvas (from openCanvas, createCanvas, listCanvases or a /studio/<id> link)');
61
+ const agentName = z
62
+ .string()
63
+ .optional()
64
+ .describe('Your name, as people on the canvas see it (e.g. "Claude"). Defaults to the name of your client.');
65
+
66
+ const readOnly = { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false } as const;
67
+ const writes = { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false } as const;
68
+
69
+ /** Names of the canvas tools, for the MCP server's tool list */
70
+ export const CANVAS_TOOL_NAMES = [
71
+ 'openCanvas',
72
+ 'listCanvases',
73
+ 'createCanvas',
74
+ 'getCanvas',
75
+ 'addToCanvas',
76
+ 'updateCanvasNode',
77
+ 'connectCanvasNodes',
78
+ 'removeFromCanvas',
79
+ 'layoutCanvas',
80
+ 'setCanvasStatus',
81
+ 'commentOnCanvas',
82
+ 'replyToCanvasComment',
83
+ 'resolveCanvasComment',
84
+ ];
85
+
86
+ type ToolContext = { catalogUrl: string; userAgent?: string };
87
+
88
+ export function registerCanvasTools(server: McpServer, { catalogUrl, userAgent }: ToolContext) {
89
+ const runtime = findStudioRuntime();
90
+ if (!runtime) return;
91
+
92
+ const canvasUrl = (id: string) => `${catalogUrl}${buildUrl(`/studio/${id}`)}`;
93
+ const socketUrl = `${catalogUrl.replace(/^http/, 'ws')}${buildUrl(STUDIO_SOCKET_PATH, true)}`;
94
+ const missing = (id: string) =>
95
+ text({ error: `No canvas "${id}". Use listCanvases to find one, or openCanvas to start one.` }, true);
96
+
97
+ const createCanvas = (title: string, name?: string) =>
98
+ runtime.createCanvas({ title, createdBy: getAgent(name, userAgent).name });
99
+
100
+ /**
101
+ * Work on a canvas as an agent: changes are played out like a person would make them (see agent-choreography),
102
+ * with the agent's pointer, selection and what it's doing shown to everyone on the canvas.
103
+ */
104
+ const asAgent = (id: string, name: string | undefined) => {
105
+ const agent = getAgent(name, userAgent);
106
+ const author = { ...agent, agent: true } as Author;
107
+ const stage: AgentStage = {
108
+ // An agent changing an accepted (or rejected) canvas makes it a draft again, like a person doing it
109
+ change: (fn) => runtime.withCanvas(id, (doc) => reopeningIfEdited(doc, author, () => fn(doc))),
110
+ present: (presence) => runtime.setAgentPresence(id, agent, presence),
111
+ lastPointer: runtime.getAgentPointer(id, agent),
112
+ sharesMoves: true,
113
+ };
114
+ return { stage, author };
115
+ };
116
+
117
+ // ---- The canvas in the chat (MCP App) ----
118
+
119
+ registerMcpApp(server, {
120
+ loadView: loadCanvasView,
121
+ resource: {
122
+ name: 'EventCatalog canvas',
123
+ uri: CANVAS_RESOURCE_URI,
124
+ description: 'A live, collaborative EventCatalog canvas, shown by MCP hosts that support MCP Apps',
125
+ // The view joins the canvas over the collaboration WebSocket, and shows icons served by this EventCatalog
126
+ csp: { connectDomains: [new URL(socketUrl).origin, catalogUrl], resourceDomains: [catalogUrl] },
127
+ displayModes: { preferred: 'fullscreen', available: ['inline', 'fullscreen'] },
128
+ },
129
+ metaKey: CANVAS_META_KEY,
130
+ tool: {
131
+ name: 'openCanvas',
132
+ // Also the name of the entrypoints in ChatGPT's sidebar and thread tabs (it differs from the plugin's name)
133
+ title: 'Canvases',
134
+ description: [
135
+ 'Shows the user a collaborative canvas right here in the chat, live: they can drag in catalog resources, connect them and comment, together with anyone else on it (in EventCatalog or another chat) and with you through the canvas tools.',
136
+ 'Pass canvasId to open an existing canvas (e.g. one the user shared a /studio/<id> link to), or a title to start a new one. With neither, it shows the user the canvases to pick one or start one.',
137
+ 'Use it when the user wants to design, sketch, map out or brainstorm architecture with you, or to see a canvas.',
138
+ ].join(' '),
139
+ // ChatGPT entrypoints (OpenAI MCP extensions): people can open canvases from the sidebar or as a tab in a
140
+ // thread, which calls the tool with {} (the list of canvases)
141
+ meta: { 'openai/ui': { entrypoints: [{ type: 'global' }, { type: 'thread' }] } },
142
+ input: {
143
+ canvasId: canvasId.optional(),
144
+ title: z.string().optional().describe('For a new canvas: what it is for'),
145
+ agentName,
146
+ },
147
+ },
148
+ viewTool: {
149
+ name: CANVAS_VIEW_TOOL,
150
+ title: 'Load a canvas for the canvas view',
151
+ description:
152
+ 'Loads what the canvas view needs to join a canvas (or, without a canvasId, the list of canvases). To show the user a canvas, use openCanvas instead.',
153
+ input: { canvasId: canvasId.optional() },
154
+ },
155
+ annotations: writes,
156
+ errorMessage: 'Failed to open the canvas',
157
+ load: async (params) => {
158
+ const { title: newTitle, agentName: name } = params as { title?: string; agentName?: string };
159
+
160
+ // Opened without a canvas (e.g. from an entrypoint): the canvases to pick from, or start one
161
+ if (!params.canvasId && !newTitle) {
162
+ const canvases = runtime
163
+ .listCanvases()
164
+ .sort((a, b) => (b.createdAt ?? 0) - (a.createdAt ?? 0))
165
+ .map(({ canvasId: id, title, status, people, nodeCount, openComments }) => ({
166
+ canvasId: id,
167
+ title,
168
+ status,
169
+ people,
170
+ nodeCount,
171
+ openComments,
172
+ }));
173
+ const home: CanvasPayload = { view: 'home', canvases };
174
+ return {
175
+ forModel: {
176
+ canvases: canvases.map((canvas) => ({ ...canvas, url: canvasUrl(canvas.canvasId) })),
177
+ note: 'Clients that support MCP Apps are showing the user these canvases to pick one or start one. Otherwise, list them for the user.',
178
+ },
179
+ label: 'the canvases',
180
+ getPayload: async (): Promise<CanvasPayload> => home,
181
+ };
182
+ }
183
+
184
+ const id = params.canvasId ?? (await createCanvas(newTitle ?? 'Untitled canvas', name));
185
+ if (!runtime.exists(id))
186
+ return { error: `No canvas "${id}". Use listCanvases to find one, or open a new one without a canvasId.` };
187
+ const { title } = await runtime.withCanvas(id, readMeta);
188
+ const payload: CanvasPayload = {
189
+ view: 'canvas',
190
+ canvasId: id,
191
+ title,
192
+ socketUrl,
193
+ canvasUrl: canvasUrl(id),
194
+ mcpUrl: `${catalogUrl}${buildUrl('/docs/mcp', true)}`,
195
+ agent: getAgent(name, userAgent),
196
+ };
197
+ return {
198
+ forModel: {
199
+ canvasId: id,
200
+ title,
201
+ url: canvasUrl(id),
202
+ people: runtime.peopleOn(id),
203
+ note: 'Clients that support MCP Apps are showing the user this canvas, live. Work on it with them using the canvas tools (getCanvas, addToCanvas...): they see your changes as you make them. Talk with them in the conversation, not in canvas comments (only comment when they ask you to). Otherwise, share the url so they can open it.',
204
+ },
205
+ label: `the canvas ${title ?? id}`,
206
+ getPayload: async (): Promise<CanvasPayload> => payload,
207
+ };
208
+ },
209
+ });
210
+
211
+ // The view loads the catalog separately, so the canvas opens fast and tool results stay small
212
+ server.registerTool(
213
+ CANVAS_CATALOG_TOOL,
214
+ {
215
+ title: 'Load the catalog for the canvas view',
216
+ description: 'Loads the catalog resources the canvas view lets people drag onto a canvas.',
217
+ inputSchema: z.object({}),
218
+ annotations: readOnly,
219
+ _meta: appViewMeta(CANVAS_RESOURCE_URI, ['app']),
220
+ },
221
+ async () => {
222
+ const catalog = await getCatalogResources();
223
+ return {
224
+ content: [{ type: 'text' as const, text: `Loaded ${catalog.resources.length} catalog resources` }],
225
+ structuredContent: catalog,
226
+ };
227
+ }
228
+ );
229
+
230
+ // The view syncs through this when the chat's sandbox won't let it open the collaboration WebSocket
231
+ server.registerTool(
232
+ CANVAS_SYNC_TOOL,
233
+ {
234
+ title: 'Sync the canvas view',
235
+ description:
236
+ 'Exchanges changes and presence between the canvas view and the canvas, when the view cannot use the WebSocket.',
237
+ inputSchema: z.object({
238
+ canvasId,
239
+ clientId: z.number(),
240
+ stateVector: z.string(),
241
+ update: z.string(),
242
+ awareness: z.string(),
243
+ }),
244
+ annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false },
245
+ _meta: appViewMeta(CANVAS_RESOURCE_URI, ['app']),
246
+ },
247
+ async (request) => {
248
+ if (!runtime.exists(request.canvasId)) return missing(request.canvasId);
249
+ const response = await runtime.syncCanvas(request);
250
+ return { content: [{ type: 'text' as const, text: 'Synced' }], structuredContent: response };
251
+ }
252
+ );
253
+
254
+ // ---- Working on canvases ----
255
+
256
+ server.registerTool(
257
+ 'listCanvases',
258
+ {
259
+ title: 'List canvases',
260
+ description:
261
+ 'Lists the collaborative canvases on this EventCatalog: shared, live design boards where people and agents map out architecture and ideas together. Shows who is on each one right now.',
262
+ inputSchema: z.object({}),
263
+ annotations: readOnly,
264
+ },
265
+ async () => text({ canvases: runtime.listCanvases().map((canvas) => ({ ...canvas, url: canvasUrl(canvas.canvasId) })) })
266
+ );
267
+
268
+ server.registerTool(
269
+ 'createCanvas',
270
+ {
271
+ title: 'Create a canvas',
272
+ description:
273
+ 'Creates a new collaborative canvas and returns its link, without showing it. Prefer openCanvas, which also shows it to the user in the chat. Share the link so people can join you on it.',
274
+ inputSchema: z.object({ title: z.string().describe('What the canvas is for, e.g. "Payments redesign"'), agentName }),
275
+ annotations: writes,
276
+ },
277
+ async ({ title, agentName: name }) => {
278
+ const id = await createCanvas(title, name);
279
+ return text({ canvasId: id, url: canvasUrl(id), title });
280
+ }
281
+ );
282
+
283
+ server.registerTool(
284
+ 'getCanvas',
285
+ {
286
+ title: 'Read a canvas',
287
+ description:
288
+ 'Reads everything on a canvas: its nodes (with ids, names, positions (their centres), sizes and the catalog resource they stand for), the connections between them, the comment threads, and who is on it right now. Read it before changing a canvas, and again to see what people have changed.',
289
+ inputSchema: z.object({ canvasId }),
290
+ annotations: readOnly,
291
+ },
292
+ async ({ canvasId: id }) => {
293
+ if (!runtime.exists(id)) return missing(id);
294
+ const canvas = await runtime.withCanvas(id, describeCanvas);
295
+ return text({ canvasId: id, url: canvasUrl(id), people: runtime.peopleOn(id), ...canvas });
296
+ }
297
+ );
298
+
299
+ server.registerTool(
300
+ 'setCanvasStatus',
301
+ {
302
+ title: 'Set a canvas status',
303
+ description: [
304
+ "Changes a canvas's status: draft (being worked on, where every canvas starts), proposed (ready for review), accepted (the agreed design) or rejected (decided against).",
305
+ 'Only change it when the user asks you to. Changing the nodes or connections on an accepted or rejected canvas makes it a draft again.',
306
+ ].join(' '),
307
+ inputSchema: z.object({
308
+ canvasId,
309
+ status: z.enum(CANVAS_STATUSES),
310
+ note: z.string().optional().describe('Why, e.g. "Agreed in the architecture review"'),
311
+ agentName,
312
+ }),
313
+ annotations: writes,
314
+ },
315
+ async ({ canvasId: id, status, note, agentName: name }) => {
316
+ if (!runtime.exists(id)) return missing(id);
317
+ const { author } = asAgent(id, name);
318
+ const changed = await runtime.withCanvas(id, (doc) => setCanvasStatus(doc, status, author, note));
319
+ return text({ canvasId: id, status, ...(!changed && { note: `The canvas was already ${status}` }) });
320
+ }
321
+ );
322
+
323
+ server.registerTool(
324
+ 'addToCanvas',
325
+ {
326
+ title: 'Add to a canvas',
327
+ description: `${ADD_TO_CANVAS_DESCRIPTION} People on the canvas watch you add them one by one, like a person would. Use getResources to find catalog resources first.`,
328
+ inputSchema: z.object({ canvasId, nodes: nodeSpecsSchema, edges: edgeSpecsSchema.optional(), agentName }),
329
+ annotations: writes,
330
+ },
331
+ async ({ canvasId: id, nodes, edges, agentName: name }) => {
332
+ if (!runtime.exists(id)) return missing(id);
333
+ const { resources, relations } = await getCatalogResources();
334
+ const result = await playAddToCanvas(asAgent(id, name).stage, { nodes, edges }, indexCatalog(resources, relations));
335
+ return text(result, result.created.length === 0 && result.errors.length > 0);
336
+ }
337
+ );
338
+
339
+ server.registerTool(
340
+ 'updateCanvasNode',
341
+ {
342
+ title: 'Update a node on a canvas',
343
+ description:
344
+ 'Renames, re-describes, re-versions or moves a node on a canvas (x/y: its new centre). Catalog resources keep their catalog name, summary and version; change those in the catalog. For notes, summary is the note text.',
345
+ inputSchema: z.object({
346
+ canvasId,
347
+ nodeId: z.string(),
348
+ name: z.string().optional(),
349
+ summary: z.string().optional(),
350
+ version: z.string().optional().describe('e.g. 1.0.0'),
351
+ x: z.number().optional(),
352
+ y: z.number().optional(),
353
+ agentName,
354
+ }),
355
+ annotations: writes,
356
+ },
357
+ async ({ canvasId: id, agentName: name, ...update }) => {
358
+ if (!runtime.exists(id)) return missing(id);
359
+ const result = await playUpdateNode(asAgent(id, name).stage, update);
360
+ return text(result, 'error' in result);
361
+ }
362
+ );
363
+
364
+ server.registerTool(
365
+ 'connectCanvasNodes',
366
+ {
367
+ title: 'Connect nodes on a canvas',
368
+ description:
369
+ 'Draws connections between nodes on a canvas, one at a time like a person dragging them. Labels default to EventCatalog wording (e.g. "publishes event", "sent to").',
370
+ inputSchema: z.object({ canvasId, edges: edgeSpecsSchema.min(1), agentName }),
371
+ annotations: writes,
372
+ },
373
+ async ({ canvasId: id, edges, agentName: name }) => {
374
+ if (!runtime.exists(id)) return missing(id);
375
+ const result = await playConnectAll(asAgent(id, name).stage, edges);
376
+ return text(result, result.connections.length === 0);
377
+ }
378
+ );
379
+
380
+ server.registerTool(
381
+ 'removeFromCanvas',
382
+ {
383
+ title: 'Remove from a canvas',
384
+ description:
385
+ 'Removes nodes (and their connections) or connections from a canvas. Comments on removed nodes stay on the canvas. Only remove what the user asked for or what you added.',
386
+ inputSchema: z.object({
387
+ canvasId,
388
+ nodeIds: z.array(z.string()).optional(),
389
+ connectionIds: z.array(z.string()).optional(),
390
+ agentName,
391
+ }),
392
+ annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: false },
393
+ },
394
+ async ({ canvasId: id, nodeIds = [], connectionIds = [], agentName: name }) => {
395
+ if (!runtime.exists(id)) return missing(id);
396
+ await playRemove(asAgent(id, name).stage, nodeIds, connectionIds);
397
+ return text({ removedNodes: nodeIds, removedConnections: connectionIds });
398
+ }
399
+ );
400
+
401
+ server.registerTool(
402
+ 'layoutCanvas',
403
+ {
404
+ title: 'Tidy a canvas',
405
+ description:
406
+ "Lays the whole canvas out again with EventCatalog's visualiser layout (left to right, connections routed around nodes); everyone on it sees the nodes glide into place. Call it once when you finish a round of changes, before you hand back to the user, so the canvas is tidy for everyone, and whenever the user asks to tidy up. Don't call it after every change (addToCanvas already places what you add next to what it connects to while you work), and skip it if the user asked to keep the layout as it is: it moves everything, including what people placed themselves.",
407
+ inputSchema: z.object({ canvasId, agentName }),
408
+ annotations: writes,
409
+ },
410
+ async ({ canvasId: id, agentName: name }) => {
411
+ if (!runtime.exists(id)) return missing(id);
412
+ const { nodes, edges } = await runtime.withCanvas(id, readCanvas);
413
+ const positions = await getLayoutPositions(nodes, edges);
414
+ await playLayout(asAgent(id, name).stage, positions);
415
+ return text({ laidOut: positions.nodes.size });
416
+ }
417
+ );
418
+
419
+ server.registerTool(
420
+ 'commentOnCanvas',
421
+ {
422
+ title: 'Comment on a canvas',
423
+ description:
424
+ "Starts a comment thread on a canvas, pinned to a node (nodeId) or a spot (x, y). Only use it when the user asks you to comment (e.g. to review the canvas or leave notes for others on it). Don't use comments to greet people, introduce yourself or ask the user questions: talk to them in the conversation.",
425
+ inputSchema: z.object({
426
+ canvasId,
427
+ text: z.string(),
428
+ nodeId: z.string().optional(),
429
+ x: z.number().optional(),
430
+ y: z.number().optional(),
431
+ agentName,
432
+ }),
433
+ annotations: writes,
434
+ },
435
+ async ({ canvasId: id, agentName: name, ...comment }) => {
436
+ if (!runtime.exists(id)) return missing(id);
437
+ const { stage, author } = asAgent(id, name);
438
+ const result = await playComment(stage, comment, author);
439
+ return 'error' in result ? text(result, true) : text(result);
440
+ }
441
+ );
442
+
443
+ /** For replies and resolving: point at the thread's node (if it's on one) while doing it */
444
+ const onThread = async <T>(
445
+ id: string,
446
+ name: string | undefined,
447
+ threadId: string,
448
+ activity: string,
449
+ change: (doc: Y.Doc, author: Author) => T
450
+ ) => {
451
+ const { stage, author } = asAgent(id, name);
452
+ const nodeId = await runtime.withCanvas(
453
+ id,
454
+ (doc) => getCanvasMaps(doc).threads.get(threadId)?.get('nodeId') as string | undefined
455
+ );
456
+ return nodeId
457
+ ? playOnNode(stage, nodeId, activity, (doc) => change(doc, author))
458
+ : stage.change((doc) => change(doc, author));
459
+ };
460
+
461
+ server.registerTool(
462
+ 'replyToCanvasComment',
463
+ {
464
+ title: 'Reply to a comment',
465
+ description: 'Replies to a comment thread on a canvas (threadId from getCanvas).',
466
+ inputSchema: z.object({ canvasId, threadId: z.string(), text: z.string(), agentName }),
467
+ annotations: writes,
468
+ },
469
+ async ({ canvasId: id, threadId, text: message, agentName: name }) => {
470
+ if (!runtime.exists(id)) return missing(id);
471
+ const replied = await onThread(id, name, threadId, 'Replying to a comment', (doc, author) =>
472
+ replyToThread(doc, threadId, message, author)
473
+ );
474
+ return replied ? text({ threadId }) : text({ error: `No comment thread "${threadId}"` }, true);
475
+ }
476
+ );
477
+
478
+ server.registerTool(
479
+ 'resolveCanvasComment',
480
+ {
481
+ title: 'Resolve a comment',
482
+ description: 'Resolves (or reopens) a comment thread on a canvas once it has been dealt with.',
483
+ inputSchema: z.object({ canvasId, threadId: z.string(), resolved: z.boolean().default(true), agentName }),
484
+ annotations: writes,
485
+ },
486
+ async ({ canvasId: id, threadId, resolved, agentName: name }) => {
487
+ if (!runtime.exists(id)) return missing(id);
488
+ const activity = resolved ? 'Resolving a comment' : 'Reopening a comment';
489
+ const found = await onThread(id, name, threadId, activity, (doc) => setThreadResolved(doc, threadId, resolved));
490
+ return found ? text({ threadId, resolved }) : text({ error: `No comment thread "${threadId}"` }, true);
491
+ }
492
+ );
493
+
494
+ addToolIcons(server, { openCanvas: [CANVAS_ICON] });
495
+ }