@eventcatalog/core 4.12.2 → 5.0.0-beta.0

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 (268) hide show
  1. package/LICENSE +226 -95
  2. package/NOTICE +30 -0
  3. package/README.md +3 -2
  4. package/dist/analytics/analytics.cjs +1 -1
  5. package/dist/analytics/analytics.js +2 -2
  6. package/dist/analytics/log-build.cjs +43 -16
  7. package/dist/analytics/log-build.d.cts +6 -5
  8. package/dist/analytics/log-build.d.ts +6 -5
  9. package/dist/analytics/log-build.js +4 -3
  10. package/dist/{chunk-U4YJSTGV.js → chunk-AYNZXUB5.js} +2 -10
  11. package/dist/{chunk-IVVM67LN.js → chunk-CR6AK5LR.js} +1 -1
  12. package/dist/{chunk-GTIC4R74.js → chunk-LR7AGRHN.js} +13 -16
  13. package/dist/chunk-TQLRG35T.js +203 -0
  14. package/dist/{chunk-WK4J6FP3.js → chunk-TWN43V4T.js} +1 -1
  15. package/dist/{chunk-ZW3MYAWF.js → chunk-WAIADKQE.js} +1 -1
  16. package/dist/{chunk-RPVA2SZY.js → chunk-ZHKEARLJ.js} +1 -1
  17. package/dist/constants.cjs +1 -1
  18. package/dist/constants.js +1 -1
  19. package/dist/eventcatalog.cjs +407 -291
  20. package/dist/eventcatalog.config.d.cts +2 -3
  21. package/dist/eventcatalog.config.d.ts +2 -3
  22. package/dist/eventcatalog.js +30 -93
  23. package/dist/federation/content-cache.d.cts +0 -5
  24. package/dist/federation/content-cache.d.ts +0 -5
  25. package/dist/federation/diagnostics.d.cts +0 -5
  26. package/dist/federation/diagnostics.d.ts +0 -5
  27. package/dist/federation/federate.cjs +0 -9
  28. package/dist/federation/federate.d.cts +0 -5
  29. package/dist/federation/federate.d.ts +0 -5
  30. package/dist/federation/federate.js +2 -3
  31. package/dist/federation/filesystem-source-provider.d.cts +0 -5
  32. package/dist/federation/filesystem-source-provider.d.ts +0 -5
  33. package/dist/federation/github-source-provider.d.cts +0 -5
  34. package/dist/federation/github-source-provider.d.ts +0 -5
  35. package/dist/federation/output-transaction.d.cts +0 -4
  36. package/dist/federation/output-transaction.d.ts +0 -4
  37. package/dist/federation/public-assets.d.cts +0 -5
  38. package/dist/federation/public-assets.d.ts +0 -5
  39. package/dist/federation/source-provider.d.cts +0 -5
  40. package/dist/federation/source-provider.d.ts +0 -5
  41. package/dist/federation/types.d.cts +0 -5
  42. package/dist/federation/types.d.ts +0 -5
  43. package/dist/generate.cjs +1 -1
  44. package/dist/generate.js +3 -3
  45. package/dist/license-C1y-56bb.d.cts +23 -0
  46. package/dist/license-C1y-56bb.d.ts +23 -0
  47. package/dist/runtime-dependencies/@eventcatalog__visualiser__layout.mjs +3 -0
  48. package/dist/runtime-dependencies/@modelcontextprotocol__ext-apps.mjs +3 -0
  49. package/dist/runtime-dependencies/@modelcontextprotocol__ext-apps__react.mjs +3 -0
  50. package/dist/runtime-dependencies/@modelcontextprotocol__ext-apps__server.mjs +3 -0
  51. package/dist/runtime-dependencies/@modelcontextprotocol__sdk__types.js.mjs +3 -0
  52. package/dist/runtime-dependencies/manifest.json +12 -1
  53. package/dist/runtime-dependencies/react-syntax-highlighter__dist__esm__languages__prism__graphql.mjs +3 -0
  54. package/dist/runtime-dependencies/react-syntax-highlighter__dist__esm__languages__prism__json.mjs +3 -0
  55. package/dist/runtime-dependencies/react-syntax-highlighter__dist__esm__languages__prism__markup.mjs +3 -0
  56. package/dist/runtime-dependencies/react-syntax-highlighter__dist__esm__languages__prism__protobuf.mjs +3 -0
  57. package/dist/runtime-dependencies/react-syntax-highlighter__dist__esm__languages__prism__yaml.mjs +3 -0
  58. package/dist/runtime-dependencies/react-syntax-highlighter__dist__esm__styles__prism__one-dark.mjs +3 -0
  59. package/dist/runtime-dependencies/react-syntax-highlighter__dist__esm__styles__prism__one-light.mjs +3 -0
  60. package/dist/utils/cli-logger.cjs +1 -1
  61. package/dist/utils/cli-logger.js +2 -2
  62. package/dist/utils/license-status.cjs +240 -0
  63. package/dist/utils/license-status.d.cts +28 -0
  64. package/dist/utils/license-status.d.ts +28 -0
  65. package/dist/utils/license-status.js +12 -0
  66. package/eventcatalog/astro.config.mjs +1 -1
  67. package/eventcatalog/integrations/eventcatalog-runtime.mjs +1 -1
  68. package/eventcatalog/src/components/ChatPanel/ChatPanel.tsx +29 -19
  69. package/eventcatalog/src/components/ChatPanel/OfflineReply.tsx +74 -0
  70. package/eventcatalog/src/components/FieldsExplorer/FieldFilters.tsx +24 -28
  71. package/eventcatalog/src/components/FieldsExplorer/FieldNodeGraph.tsx +16 -11
  72. package/eventcatalog/src/components/FieldsExplorer/FieldsExplorer.tsx +4 -218
  73. package/eventcatalog/src/components/FieldsExplorer/FieldsTable.tsx +27 -36
  74. package/eventcatalog/src/components/Header.astro +43 -58
  75. package/eventcatalog/src/components/Lists/PillListFlat.styles.css +17 -17
  76. package/eventcatalog/src/components/MDX/EntityMap/EntityMap.astro +1 -1
  77. package/eventcatalog/src/components/MDX/Flow/Flow.astro +4 -0
  78. package/eventcatalog/src/components/MDX/NodeGraph/AstroNodeGraph.tsx +14 -0
  79. package/eventcatalog/src/components/MDX/NodeGraph/NodeGraph.astro +49 -18
  80. package/eventcatalog/src/components/MDX/RemoteFile.astro +2 -24
  81. package/eventcatalog/src/components/SchemaExplorer/ApiAccessSection.tsx +59 -93
  82. package/eventcatalog/src/components/SchemaExplorer/ApiContentViewer.tsx +1 -32
  83. package/eventcatalog/src/components/SchemaExplorer/DiffViewer.tsx +20 -67
  84. package/eventcatalog/src/components/SchemaExplorer/SchemaContentViewer.tsx +2 -11
  85. package/eventcatalog/src/components/SchemaExplorer/SchemaDetailsPanel.tsx +11 -60
  86. package/eventcatalog/src/components/SchemaExplorer/SchemaExplorer.tsx +1 -3
  87. package/eventcatalog/src/components/SchemaExplorer/SchemaPageViewer.tsx +0 -3
  88. package/eventcatalog/src/components/SchemaExplorer/VersionHistoryModal.tsx +2 -9
  89. package/eventcatalog/src/components/SchemaExplorer/parse-schema.ts +46 -0
  90. package/eventcatalog/src/components/SchemaExplorer/utils.ts +11 -0
  91. package/eventcatalog/src/components/Settings/AssistantSettingsForm.tsx +2 -9
  92. package/eventcatalog/src/components/Settings/LicenseSettings.tsx +288 -0
  93. package/eventcatalog/src/components/Settings/LlmAccessSettingsForm.tsx +3 -19
  94. package/eventcatalog/src/components/Settings/McpSettingsForm.tsx +85 -30
  95. package/eventcatalog/src/components/Settings/SettingsShared.tsx +7 -75
  96. package/eventcatalog/src/components/SideNav/CustomDocsNav.astro +2 -2
  97. package/eventcatalog/src/components/TrialBadge.astro +82 -0
  98. package/eventcatalog/src/content.config.ts +9 -90
  99. package/eventcatalog/src/{enterprise → features}/ai/chat-api.ts +22 -34
  100. package/eventcatalog/src/{enterprise → features}/analytics/adapters/ga4.ts +0 -5
  101. package/eventcatalog/src/{enterprise → features}/analytics/adapters/gtm.ts +0 -5
  102. package/eventcatalog/src/{enterprise → features}/analytics/adapters/posthog.ts +0 -5
  103. package/eventcatalog/src/{enterprise → features}/analytics/components/AnalyticsHead.astro +2 -4
  104. package/eventcatalog/src/{enterprise → features}/analytics/components/AnalyticsTracker.astro +2 -4
  105. package/eventcatalog/src/{enterprise → features}/analytics/tracker.ts +0 -5
  106. package/eventcatalog/src/{enterprise → features}/api/catalog.ts +0 -5
  107. package/eventcatalog/src/{enterprise → features}/auth/[...auth].ts +0 -5
  108. package/eventcatalog/src/{enterprise → features}/auth/middleware/middleware-auth.ts +0 -5
  109. package/eventcatalog/src/{enterprise → features}/auth/middleware/middleware.ts +0 -5
  110. package/eventcatalog/src/{enterprise → features}/collections/custom-pages.ts +0 -5
  111. package/eventcatalog/src/{enterprise → features}/collections/index.ts +0 -5
  112. package/eventcatalog/src/{enterprise → features}/collections/resource-docs.ts +0 -5
  113. package/eventcatalog/src/{enterprise → features}/custom-documentation/collection.ts +0 -5
  114. package/eventcatalog/src/features/custom-documentation/components/CustomDocsNav/CustomDocsNav.astro +9 -0
  115. package/eventcatalog/src/{enterprise → features}/custom-documentation/components/CustomDocsNav/CustomDocsNavWrapper.tsx +0 -5
  116. package/eventcatalog/src/{enterprise → features}/custom-documentation/components/CustomDocsNav/components/NestedItem.tsx +1 -6
  117. package/eventcatalog/src/{enterprise → features}/custom-documentation/components/CustomDocsNav/components/NoResultsFound.tsx +0 -5
  118. package/eventcatalog/src/{enterprise → features}/custom-documentation/components/CustomDocsNav/index.tsx +1 -6
  119. package/eventcatalog/src/{enterprise → features}/custom-documentation/components/CustomDocsNav/types.ts +0 -5
  120. package/eventcatalog/src/{enterprise/custom-documentation/pages/docs/custom/index.astro → features/custom-documentation/components/CustomDocumentationPage.astro} +5 -5
  121. package/eventcatalog/src/{enterprise → features}/custom-documentation/utils/badge-styles.ts +0 -5
  122. package/eventcatalog/src/{enterprise → features}/custom-documentation/utils/custom-docs.ts +1 -6
  123. package/eventcatalog/src/{enterprise → features}/custom-documentation/utils/docs-search.ts +0 -5
  124. package/eventcatalog/src/{enterprise → features}/custom-pages/routes.ts +0 -5
  125. package/eventcatalog/src/{enterprise → features}/directory/user-team-directory.ts +0 -5
  126. package/eventcatalog/src/{enterprise → features}/fields/field-extractor.ts +0 -5
  127. package/eventcatalog/src/{enterprise → features}/fields/field-indexer.ts +0 -5
  128. package/eventcatalog/src/{enterprise → features}/fields/fields-db.ts +0 -5
  129. package/eventcatalog/src/{enterprise → features}/fields/pages/api/fields.ts +1 -6
  130. package/eventcatalog/src/{enterprise → features}/fields/pages/fields.astro +1 -2
  131. package/eventcatalog/src/{enterprise → features}/integrations/eventcatalog-features.ts +17 -75
  132. package/eventcatalog/src/features/mcp/apps/architecture-diagram/links.ts +59 -0
  133. package/eventcatalog/src/features/mcp/apps/architecture-diagram/node-chat.tsx +299 -0
  134. package/eventcatalog/src/features/mcp/apps/architecture-diagram/shared.ts +14 -0
  135. package/eventcatalog/src/features/mcp/apps/architecture-diagram/view.tsx +454 -0
  136. package/eventcatalog/src/features/mcp/apps/generated/viewer.html +432 -0
  137. package/eventcatalog/src/features/mcp/apps/mermaid-stub.ts +10 -0
  138. package/eventcatalog/src/features/mcp/apps/schema-viewer/schema-code.tsx +65 -0
  139. package/eventcatalog/src/features/mcp/apps/schema-viewer/shared.ts +23 -0
  140. package/eventcatalog/src/features/mcp/apps/schema-viewer/view.tsx +170 -0
  141. package/eventcatalog/src/features/mcp/apps/shared/app-view.tsx +98 -0
  142. package/eventcatalog/src/features/mcp/apps/shared/resource-types.ts +15 -0
  143. package/eventcatalog/src/features/mcp/apps/viewer/shared.ts +24 -0
  144. package/eventcatalog/src/features/mcp/apps/viewer/styles.css +25 -0
  145. package/eventcatalog/src/features/mcp/apps/viewer/view.tsx +59 -0
  146. package/eventcatalog/src/features/mcp/mcp-app-icons.ts +63 -0
  147. package/eventcatalog/src/features/mcp/mcp-apps.ts +160 -0
  148. package/eventcatalog/src/{enterprise → features}/mcp/mcp-auth.ts +0 -5
  149. package/eventcatalog/src/{enterprise → features}/mcp/mcp-scope.ts +7 -7
  150. package/eventcatalog/src/{enterprise → features}/mcp/mcp-scoped-tools.ts +47 -40
  151. package/eventcatalog/src/{enterprise → features}/mcp/mcp-server.ts +347 -34
  152. package/eventcatalog/src/{enterprise → features}/mcp/oauth-protected-resource.ts +0 -5
  153. package/eventcatalog/src/features/mcp/schema-viewer.ts +24 -0
  154. package/eventcatalog/src/{enterprise → features}/tools/catalog-tools.ts +133 -102
  155. package/eventcatalog/src/features/tools/index.ts +5 -0
  156. package/eventcatalog/src/{enterprise → features}/visualizer-layout/reset.ts +0 -5
  157. package/eventcatalog/src/{enterprise → features}/visualizer-layout/save.ts +0 -5
  158. package/eventcatalog/src/layouts/BaseLayout.astro +2 -2
  159. package/eventcatalog/src/layouts/Footer.astro +31 -39
  160. package/eventcatalog/src/layouts/SettingsLayout.astro +6 -6
  161. package/eventcatalog/src/layouts/VerticalSideBarLayout.astro +7 -37
  162. package/eventcatalog/src/{enterprise → pages}/api/schemas/[collection]/[id]/[version]/index.ts +0 -22
  163. package/eventcatalog/src/{enterprise → pages}/api/schemas/services/[id]/[version]/[specification]/index.ts +0 -19
  164. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/[docType]/[docId]/[docVersion]/_index.data.ts +2 -6
  165. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/[docType]/[docId]/[docVersion]/index.astro +1 -5
  166. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/[docType]/[docId]/_index.data.ts +2 -6
  167. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/[docType]/[docId]/index.astro +1 -5
  168. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/[docType]/[docId].md.ts +2 -6
  169. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/[docType]/[docId].mdx.ts +2 -6
  170. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/index.astro +9 -5
  171. package/eventcatalog/src/pages/docs/custom/[...path]/index.astro +12 -0
  172. package/eventcatalog/src/{enterprise/custom-documentation/pages/docs/custom/root-index.astro → pages/docs/custom/index.astro} +1 -23
  173. package/eventcatalog/src/pages/docs/llm/llms-full.txt.ts +4 -9
  174. package/eventcatalog/src/pages/docs/llm/llms.txt.ts +4 -4
  175. package/eventcatalog/src/pages/docs/llm/schemas.txt.ts +0 -11
  176. package/eventcatalog/src/pages/docs/services/[id]/[docType]/[docId]/index.astro +2 -2
  177. package/eventcatalog/src/pages/index.astro +2 -2
  178. package/eventcatalog/src/pages/schemas/[type]/[id]/[version]/index.astro +0 -3
  179. package/eventcatalog/src/pages/schemas/explorer/index.astro +1 -4
  180. package/eventcatalog/src/pages/settings/assistant.astro +2 -4
  181. package/eventcatalog/src/pages/settings/license.astro +18 -0
  182. package/eventcatalog/src/pages/settings/llm-access.astro +1 -4
  183. package/eventcatalog/src/pages/settings/mcp.astro +33 -4
  184. package/eventcatalog/src/pages/visualiser/[type]/[id]/[version]/_index.data.ts +1 -2
  185. package/eventcatalog/src/pages/visualiser/[type]/[id]/[version]/data/index.astro +2 -45
  186. package/eventcatalog/src/pages/visualiser/[type]/[id]/[version]/entity-map/index.astro +1 -1
  187. package/eventcatalog/src/pages/visualiser/[type]/[id]/[version].mermaid.ts +7 -109
  188. package/eventcatalog/src/pages/visualiser/_mermaid-route.ts +78 -0
  189. package/eventcatalog/src/pages/visualiser/domains/[id]/[version]/_index.data.ts +44 -0
  190. package/eventcatalog/src/pages/visualiser/domains/[id]/[version]/entity-map/index.astro +1 -1
  191. package/eventcatalog/src/pages/visualiser/domains/[id]/[version]/index.astro +56 -0
  192. package/eventcatalog/src/pages/visualiser/domains/[id]/[version]/systems-context/index.astro +3 -40
  193. package/eventcatalog/src/pages/visualiser/domains/[id]/[version].mermaid.ts +20 -0
  194. package/eventcatalog/src/pages/visualiser/systems/[id]/[version]/_index.data.ts +29 -0
  195. package/eventcatalog/src/pages/visualiser/systems/[id]/[version]/context/index.astro +3 -38
  196. package/eventcatalog/src/pages/visualiser/systems/[id]/[version]/index.astro +56 -0
  197. package/eventcatalog/src/pages/visualiser/systems/[id]/[version].mermaid.ts +20 -0
  198. package/eventcatalog/src/stores/sidebar-store/builders/agent.ts +1 -1
  199. package/eventcatalog/src/stores/sidebar-store/builders/container.ts +1 -1
  200. package/eventcatalog/src/stores/sidebar-store/builders/data-product.ts +1 -1
  201. package/eventcatalog/src/stores/sidebar-store/builders/domain.ts +7 -32
  202. package/eventcatalog/src/stores/sidebar-store/builders/entity.ts +1 -1
  203. package/eventcatalog/src/stores/sidebar-store/builders/message.ts +1 -1
  204. package/eventcatalog/src/stores/sidebar-store/builders/service.ts +2 -8
  205. package/eventcatalog/src/stores/sidebar-store/builders/system.ts +2 -12
  206. package/eventcatalog/src/stores/sidebar-store/custom-sidebar.ts +0 -10
  207. package/eventcatalog/src/stores/sidebar-store/state.ts +4 -6
  208. package/eventcatalog/src/styles/theme.css +22 -18
  209. package/eventcatalog/src/styles/themes/forest.css +8 -8
  210. package/eventcatalog/src/styles/themes/ocean.css +8 -8
  211. package/eventcatalog/src/styles/themes/sapphire.css +8 -8
  212. package/eventcatalog/src/styles/themes/sunset.css +8 -8
  213. package/eventcatalog/src/utils/clipboard.ts +4 -4
  214. package/eventcatalog/src/utils/collections/flow-step-schema.ts +117 -0
  215. package/eventcatalog/src/utils/collections/pointer-schema.ts +7 -0
  216. package/eventcatalog/src/utils/collections/resource-docs.ts +677 -14
  217. package/eventcatalog/src/utils/collections/schema-loader.ts +0 -5
  218. package/eventcatalog/src/utils/feature.ts +14 -23
  219. package/eventcatalog/src/utils/license.ts +114 -0
  220. package/eventcatalog/src/utils/node-graphs/architecture-diagram-types.ts +32 -0
  221. package/eventcatalog/src/utils/node-graphs/architecture-diagram.ts +84 -0
  222. package/eventcatalog/src/utils/node-graphs/catalog-force-graph.ts +1 -0
  223. package/eventcatalog/src/utils/node-graphs/channel-node-graph.ts +4 -25
  224. package/eventcatalog/src/utils/node-graphs/container-node-graph.ts +12 -39
  225. package/eventcatalog/src/utils/node-graphs/data-products-node-graph.ts +11 -27
  226. package/eventcatalog/src/utils/node-graphs/domain-levels-node-graph.ts +679 -0
  227. package/eventcatalog/src/utils/node-graphs/domains-node-graph.ts +3 -20
  228. package/eventcatalog/src/utils/node-graphs/export-mermaid.ts +62 -4
  229. package/eventcatalog/src/utils/node-graphs/field-node-graph.ts +8 -23
  230. package/eventcatalog/src/utils/node-graphs/flow-lanes.ts +119 -0
  231. package/eventcatalog/src/utils/node-graphs/flows-node-graph.ts +153 -45
  232. package/eventcatalog/src/utils/node-graphs/layout-node-graph.ts +28 -0
  233. package/eventcatalog/src/utils/node-graphs/layout-persistence.ts +3 -3
  234. package/eventcatalog/src/utils/node-graphs/message-node-graph.ts +16 -34
  235. package/eventcatalog/src/utils/node-graphs/services-node-graph.ts +11 -25
  236. package/eventcatalog/src/utils/node-graphs/system-context-node-graph.ts +18 -54
  237. package/eventcatalog/src/utils/node-graphs/system-levels-node-graph.ts +59 -0
  238. package/eventcatalog/src/utils/node-graphs/systems-node-graph.ts +20 -80
  239. package/eventcatalog/src/utils/node-graphs/utils/utils.ts +1 -78
  240. package/eventcatalog/src/utils/trial.ts +36 -0
  241. package/eventcatalog/tsconfig.json +1 -1
  242. package/package.json +11 -11
  243. package/dist/chunk-FV56YFM4.js +0 -7
  244. package/dist/federation/entitlement.cjs +0 -31
  245. package/dist/federation/entitlement.d.cts +0 -12
  246. package/dist/federation/entitlement.d.ts +0 -12
  247. package/dist/federation/entitlement.js +0 -6
  248. package/dist/runtime-dependencies/dagre.mjs +0 -3
  249. package/eventcatalog/src/components/Settings/BillingSettingsForm.tsx +0 -265
  250. package/eventcatalog/src/enterprise/LICENSE +0 -57
  251. package/eventcatalog/src/enterprise/README.md +0 -5
  252. package/eventcatalog/src/enterprise/collections/resource-docs-utils.ts +0 -691
  253. package/eventcatalog/src/enterprise/custom-documentation/components/CustomDocsNav/CustomDocsNav.astro +0 -9
  254. package/eventcatalog/src/enterprise/custom-documentation/pages/docs/custom/[...path]/index.astro +0 -18
  255. package/eventcatalog/src/enterprise/custom-documentation/pages/docs/custom/feature.astro +0 -171
  256. package/eventcatalog/src/enterprise/feature.ts +0 -75
  257. package/eventcatalog/src/enterprise/tools/index.ts +0 -10
  258. package/eventcatalog/src/hooks/eventcatalog-visualizer.ts +0 -126
  259. package/eventcatalog/src/pages/settings/billing.astro +0 -17
  260. package/eventcatalog/src/pages/visualiser/domain-integrations/index.astro +0 -26
  261. package/eventcatalog/src/types/mcp-modules.d.ts +0 -67
  262. package/eventcatalog/src/utils/node-graphs/domains-canvas.ts +0 -261
  263. package/eventcatalog/src/utils/node-graphs/export-node-graph.ts +0 -91
  264. /package/eventcatalog/src/{enterprise → features}/auth/error.astro +0 -0
  265. /package/eventcatalog/src/{enterprise → features}/auth/login.astro +0 -0
  266. /package/eventcatalog/src/{enterprise → features}/auth/unauthorized.astro +0 -0
  267. /package/eventcatalog/src/{enterprise/custom-documentation/pages → pages}/docs/custom/[...path]/_index.data.ts +0 -0
  268. /package/eventcatalog/src/{enterprise/custom-documentation/pages → pages}/docs/custom/[...path].mdx.ts +0 -0
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Stands in for `mermaid` in MCP App views. The visualiser's optional Mermaid view loads it
3
+ * lazily, but MCP hosts already get the diagram as Mermaid text, and mermaid would more than
4
+ * double the size of the view's HTML.
5
+ */
6
+ const unavailable = async () => {
7
+ throw new Error('The Mermaid view is not available here');
8
+ };
9
+
10
+ export default { initialize: () => {}, render: unavailable, parse: unavailable };
@@ -0,0 +1,65 @@
1
+ /**
2
+ * The schema's source, highlighted like the Schema tab on EventCatalog's schema pages.
3
+ * Uses the light build of the highlighter with only the languages schemas are written in, to keep the view small
4
+ * (languages it doesn't have, such as TypeScript, are shown without highlighting).
5
+ */
6
+ import { useState } from 'react';
7
+ import { PrismLight as SyntaxHighlighter } from 'react-syntax-highlighter';
8
+ import json from 'react-syntax-highlighter/dist/esm/languages/prism/json';
9
+ import protobuf from 'react-syntax-highlighter/dist/esm/languages/prism/protobuf';
10
+ import yaml from 'react-syntax-highlighter/dist/esm/languages/prism/yaml';
11
+ import markup from 'react-syntax-highlighter/dist/esm/languages/prism/markup';
12
+ import graphql from 'react-syntax-highlighter/dist/esm/languages/prism/graphql';
13
+ import oneDark from 'react-syntax-highlighter/dist/esm/styles/prism/one-dark';
14
+ import oneLight from 'react-syntax-highlighter/dist/esm/styles/prism/one-light';
15
+ import { Check, Copy } from 'lucide-react';
16
+ import { copyToClipboard } from '@utils/clipboard';
17
+ import { SCHEMA_CODE_STYLE } from '../../../../components/SchemaExplorer/utils';
18
+
19
+ SyntaxHighlighter.registerLanguage('json', json);
20
+ SyntaxHighlighter.registerLanguage('protobuf', protobuf);
21
+ SyntaxHighlighter.registerLanguage('yaml', yaml);
22
+ SyntaxHighlighter.registerLanguage('xml', markup);
23
+ SyntaxHighlighter.registerLanguage('graphql', graphql);
24
+
25
+ type SchemaCodeProps = {
26
+ code: string;
27
+ /** Language to highlight the code in, from the server (see getLanguageForHighlight) */
28
+ language: string;
29
+ isDark: boolean;
30
+ /** Grows with the code up to this height; fills its container when not set */
31
+ maxHeight?: string;
32
+ };
33
+
34
+ export function SchemaCode({ code, language, isDark, maxHeight }: SchemaCodeProps) {
35
+ const [copyState, setCopyState] = useState<'idle' | 'copied' | 'failed'>('idle');
36
+
37
+ const copy = async () => {
38
+ setCopyState((await copyToClipboard(code)) ? 'copied' : 'failed');
39
+ setTimeout(() => setCopyState('idle'), 2000);
40
+ };
41
+
42
+ return (
43
+ <div
44
+ className={`relative overflow-hidden rounded-md border border-[rgb(var(--ec-page-border))] bg-[rgb(var(--ec-code-bg,var(--ec-card-bg)))] ${maxHeight ? '' : 'h-full'}`}
45
+ >
46
+ <button
47
+ type="button"
48
+ onClick={copy}
49
+ className="absolute right-3 top-3 z-10 inline-flex items-center gap-1 rounded-md bg-[rgb(var(--ec-content-hover)/0.9)] px-2 py-1.5 text-[11px] font-medium text-[rgb(var(--ec-page-text-muted))] backdrop-blur-xs transition-colors hover:text-[rgb(var(--ec-page-text))]"
50
+ >
51
+ {copyState === 'copied' ? <Check aria-hidden className="h-3 w-3" /> : <Copy aria-hidden className="h-3 w-3" />}
52
+ {copyState === 'copied' ? 'Copied!' : copyState === 'failed' ? 'Could not copy' : 'Copy'}
53
+ </button>
54
+ <SyntaxHighlighter
55
+ language={language}
56
+ style={isDark ? oneDark : oneLight}
57
+ customStyle={{ ...SCHEMA_CODE_STYLE, ...(maxHeight ? { maxHeight } : { height: '100%' }) }}
58
+ showLineNumbers
59
+ wrapLongLines
60
+ >
61
+ {code}
62
+ </SyntaxHighlighter>
63
+ </div>
64
+ );
65
+ }
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Shared by the MCP server and the schema viewer MCP App view (view.tsx, shown by the viewer in ../viewer).
3
+ * Kept free of server-only imports so it can be bundled into the view.
4
+ */
5
+ import type { ViewableSchema } from '@components/SchemaExplorer/parse-schema';
6
+
7
+ export type SchemaViewerSchema = ViewableSchema & {
8
+ /** Set when a message has more than one schema */
9
+ name?: string;
10
+ format: string;
11
+ /** Language to highlight the code in (see getLanguageForHighlight) */
12
+ language: string;
13
+ code: string;
14
+ };
15
+
16
+ export type SchemaViewerPayload = {
17
+ resource: { collection: string; id: string; version: string; name: string };
18
+ /** The EventCatalog the schema came from, for links back to it */
19
+ catalogUrl: string;
20
+ /** Path of the resource's page in EventCatalog */
21
+ docsPath: string;
22
+ schemas: SchemaViewerSchema[];
23
+ };
@@ -0,0 +1,170 @@
1
+ /**
2
+ * The schema, shown by the EventCatalog viewer MCP App (../viewer) for showResource with view "schema".
3
+ * Like EventCatalog's schema pages, it shows a message's schema as its source (Schema) or with the Schema
4
+ * Explorer's viewers (Properties, for JSON Schema, Avro and Protobuf), in MCP hosts that support MCP Apps.
5
+ * Hosts without MCP Apps support show the tool's text instead.
6
+ */
7
+ import { useCallback, useEffect, useState } from 'react';
8
+ import type { App, McpUiHostContext } from '@modelcontextprotocol/ext-apps';
9
+ import { Code, ExternalLink, Maximize2, Minimize2, Table } from 'lucide-react';
10
+ import JSONSchemaViewer from '../../../../components/SchemaExplorer/JSONSchemaViewer';
11
+ import AvroSchemaViewer from '../../../../components/SchemaExplorer/AvroSchemaViewer';
12
+ import ProtobufSchemaViewer from '../../../../components/SchemaExplorer/ProtobufSchemaViewer';
13
+ import { SchemaCode } from './schema-code';
14
+ import { RESOURCE_TYPE_LABELS } from '../shared/resource-types';
15
+ import type { SchemaViewerPayload, SchemaViewerSchema } from './shared';
16
+
17
+ const INLINE_MAX_HEIGHT = '560px';
18
+
19
+ const FORMAT_LABELS: Record<SchemaViewerSchema['kind'], string> = {
20
+ json: 'JSON Schema',
21
+ avro: 'Avro',
22
+ protobuf: 'Protobuf',
23
+ code: '',
24
+ };
25
+
26
+ type SchemaTab = 'code' | 'properties';
27
+
28
+ function SchemaProperties({ schema, maxHeight }: { schema: SchemaViewerSchema; maxHeight?: string }) {
29
+ switch (schema.kind) {
30
+ case 'json':
31
+ return <JSONSchemaViewer schema={schema.schema} maxHeight={maxHeight} />;
32
+ case 'avro':
33
+ return <AvroSchemaViewer schema={schema.schema} maxHeight={maxHeight} showRequired />;
34
+ case 'protobuf':
35
+ return <ProtobufSchemaViewer schema={schema.schema} maxHeight={maxHeight} showRequired />;
36
+ default:
37
+ return null;
38
+ }
39
+ }
40
+
41
+ type SchemaViewerViewProps = {
42
+ app: App | null;
43
+ /** The schemas from the latest tool result */
44
+ payload: SchemaViewerPayload;
45
+ hostContext?: McpUiHostContext;
46
+ };
47
+
48
+ export function SchemaViewerView({ app, payload, hostContext }: SchemaViewerViewProps) {
49
+ const [selectedIndex, setSelectedIndex] = useState(0);
50
+ // Properties first: it's what people asking to see a schema usually want
51
+ const [tab, setTab] = useState<SchemaTab>('properties');
52
+
53
+ // Show the first schema again when the view gets a new tool result
54
+ useEffect(() => setSelectedIndex(0), [payload]);
55
+
56
+ // The view runs in a sandbox and can't navigate, so EventCatalog opens in the host
57
+ const openInCatalog = useCallback(
58
+ (path: string) => app?.openLink({ url: new URL(path, payload.catalogUrl).href }),
59
+ [app, payload]
60
+ );
61
+
62
+ const { resource, schemas } = payload;
63
+ const schema = schemas[selectedIndex] ?? schemas[0];
64
+ // Schemas without a viewer, such as XML Schema, only have their source
65
+ const hasProperties = schema && schema.kind !== 'code';
66
+ const activeTab: SchemaTab = hasProperties ? tab : 'code';
67
+ const isDark = hostContext?.theme === 'dark';
68
+ const tabs: Array<{ id: SchemaTab; label: string; icon: typeof Code }> = [
69
+ { id: 'code', label: 'Schema', icon: Code },
70
+ ...(hasProperties ? [{ id: 'properties' as const, label: 'Properties', icon: Table }] : []),
71
+ ];
72
+ const isFullscreen = hostContext?.displayMode === 'fullscreen';
73
+ const canGoFullscreen = hostContext?.availableDisplayModes?.includes('fullscreen');
74
+ const buttonClass =
75
+ 'inline-flex h-7 items-center gap-1.5 rounded-md border border-[rgb(var(--ec-page-border))] bg-[rgb(var(--ec-card-bg))] px-2.5 text-xs font-medium text-[rgb(var(--ec-page-text))] transition-colors hover:border-[rgb(var(--ec-accent))] hover:text-[rgb(var(--ec-accent))]';
76
+
77
+ return (
78
+ <div className={`flex flex-col ${isFullscreen ? 'h-screen' : ''}`}>
79
+ <div className="flex items-center justify-between gap-3 border-b border-[rgb(var(--ec-page-border))] px-3 py-2">
80
+ <div className="flex min-w-0 items-center gap-2 text-[13px]">
81
+ <span className="shrink-0 rounded bg-[rgb(var(--ec-accent-subtle))] px-1.5 py-px text-[10px] font-semibold uppercase tracking-wide text-[rgb(var(--ec-accent))]">
82
+ {RESOURCE_TYPE_LABELS[resource.collection] ?? resource.collection}
83
+ </span>
84
+ <span className="truncate font-semibold">{resource.name}</span>
85
+ <span className="shrink-0 text-xs text-[rgb(var(--ec-page-text-muted))]">v{resource.version}</span>
86
+ {schema && FORMAT_LABELS[schema.kind] && (
87
+ <span className="shrink-0 text-xs text-[rgb(var(--ec-page-text-muted))]">· {FORMAT_LABELS[schema.kind]}</span>
88
+ )}
89
+ </div>
90
+ <div className="flex shrink-0 gap-1.5">
91
+ <button type="button" className={buttonClass} onClick={() => openInCatalog(payload.docsPath)}>
92
+ <ExternalLink aria-hidden className="h-3.5 w-3.5" />
93
+ Open in EventCatalog
94
+ </button>
95
+ {canGoFullscreen && (
96
+ <button
97
+ type="button"
98
+ className={buttonClass}
99
+ aria-label={isFullscreen ? 'Exit full screen' : 'Full screen'}
100
+ onClick={() => app?.requestDisplayMode({ mode: isFullscreen ? 'inline' : 'fullscreen' })}
101
+ >
102
+ {isFullscreen ? (
103
+ <Minimize2 aria-hidden className="h-3.5 w-3.5" />
104
+ ) : (
105
+ <Maximize2 aria-hidden className="h-3.5 w-3.5" />
106
+ )}
107
+ </button>
108
+ )}
109
+ </div>
110
+ </div>
111
+ {schema && (
112
+ <div className="flex items-center justify-between gap-3 border-b border-[rgb(var(--ec-page-border))] px-3">
113
+ <div className="flex items-center gap-1" role="tablist">
114
+ {tabs.map(({ id, label, icon: Icon }) => (
115
+ <button
116
+ key={id}
117
+ type="button"
118
+ role="tab"
119
+ aria-selected={activeTab === id}
120
+ onClick={() => setTab(id)}
121
+ className={`-mb-px inline-flex items-center gap-1.5 border-b-2 px-3 py-2 text-xs font-medium transition-colors ${
122
+ activeTab === id
123
+ ? 'border-[rgb(var(--ec-accent))] text-[rgb(var(--ec-page-text))]'
124
+ : 'border-transparent text-[rgb(var(--ec-page-text-muted))] hover:border-[rgb(var(--ec-page-border))] hover:text-[rgb(var(--ec-page-text))]'
125
+ }`}
126
+ >
127
+ <Icon aria-hidden className="h-3.5 w-3.5" />
128
+ {label}
129
+ </button>
130
+ ))}
131
+ </div>
132
+ {schemas.length > 1 && (
133
+ <div className="flex min-w-0 gap-1 overflow-x-auto py-1.5" aria-label="Schemas">
134
+ {schemas.map((item, index) => (
135
+ <button
136
+ key={`${item.name}-${index}`}
137
+ type="button"
138
+ aria-pressed={index === selectedIndex}
139
+ onClick={() => setSelectedIndex(index)}
140
+ className={`whitespace-nowrap rounded-md border px-2 py-1 text-[11px] font-medium transition-colors ${
141
+ index === selectedIndex
142
+ ? 'border-[rgb(var(--ec-accent)/0.4)] bg-[rgb(var(--ec-accent-subtle))] text-[rgb(var(--ec-page-text))]'
143
+ : 'border-[rgb(var(--ec-page-border))] text-[rgb(var(--ec-page-text-muted))] hover:text-[rgb(var(--ec-page-text))]'
144
+ }`}
145
+ >
146
+ {item.name}
147
+ </button>
148
+ ))}
149
+ </div>
150
+ )}
151
+ </div>
152
+ )}
153
+ <div className={`p-3 ${isFullscreen ? 'min-h-0 flex-1' : ''}`}>
154
+ {!schema ? (
155
+ <p className="text-sm text-[rgb(var(--ec-page-text-muted))]">{resource.name} has no schema.</p>
156
+ ) : activeTab === 'properties' ? (
157
+ <SchemaProperties key={selectedIndex} schema={schema} maxHeight={isFullscreen ? undefined : INLINE_MAX_HEIGHT} />
158
+ ) : (
159
+ <SchemaCode
160
+ key={selectedIndex}
161
+ code={schema.code}
162
+ language={schema.language}
163
+ isDark={isDark}
164
+ maxHeight={isFullscreen ? undefined : INLINE_MAX_HEIGHT}
165
+ />
166
+ )}
167
+ </div>
168
+ </div>
169
+ );
170
+ }
@@ -0,0 +1,98 @@
1
+ /**
2
+ * Connecting the EventCatalog viewer MCP App to the host, loading the tool result's payload, following the
3
+ * host's theme and display mode, and mounting the view.
4
+ */
5
+ import { useEffect, useState, type ComponentType } from 'react';
6
+ import { config as configureZod } from 'zod';
7
+ import { createRoot } from 'react-dom/client';
8
+ import type { App, McpUiHostContext } from '@modelcontextprotocol/ext-apps';
9
+ import { useApp } from '@modelcontextprotocol/ext-apps/react';
10
+ import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';
11
+
12
+ // MCP hosts block eval in views, so stop zod (used by the MCP Apps SDK) from probing for it
13
+ configureZod({ jitless: true });
14
+
15
+ /** The payload for the view: in the result's `_meta` from the model-facing tool, or `structuredContent` from the app-only tool */
16
+ export const getToolResultPayload = <Payload,>(result: CallToolResult, metaKey: string) =>
17
+ (result._meta?.[metaKey] as Payload | undefined) ?? (result.structuredContent as Payload | undefined);
18
+
19
+ const applyTheme = (context?: McpUiHostContext) => {
20
+ if (context?.theme) document.documentElement.setAttribute('data-theme', context.theme);
21
+ };
22
+
23
+ type McpAppViewOptions<Payload> = {
24
+ name: string;
25
+ /** Key of the payload in the model-facing tool result's `_meta` */
26
+ metaKey: string;
27
+ /** App-only tool that loads the payload when the host doesn't pass the result's `_meta` to the view */
28
+ viewTool: string;
29
+ /** Whether a tool result carried a complete payload */
30
+ isPayload: (payload: Payload | undefined) => payload is Payload;
31
+ /** The view tool's arguments, from the model-facing tool result's text */
32
+ getViewToolArguments: (text: Record<string, any>) => Record<string, unknown>;
33
+ loadErrorMessage: string;
34
+ };
35
+
36
+ /** The payload from a tool result, or loaded through the app-only tool when the host doesn't pass `_meta` */
37
+ async function loadPayload<Payload>(app: App, result: CallToolResult, options: McpAppViewOptions<Payload>) {
38
+ const payload = getToolResultPayload<Payload>(result, options.metaKey);
39
+ if (options.isPayload(payload)) return payload;
40
+
41
+ const text = result.content?.find((item) => item.type === 'text')?.text;
42
+ const viewResult = await app.callServerTool({
43
+ name: options.viewTool,
44
+ arguments: options.getViewToolArguments(JSON.parse(text ?? '{}')),
45
+ });
46
+ const loaded = getToolResultPayload<Payload>(viewResult, options.metaKey);
47
+ if (!options.isPayload(loaded)) throw new Error(options.loadErrorMessage);
48
+ return loaded;
49
+ }
50
+
51
+ /**
52
+ * Connects the view to the MCP host and loads the payload of each tool result shown in it.
53
+ * Also applies the host's theme (light or dark) and keeps its context (such as the display mode) up to date.
54
+ */
55
+ export function useMcpAppView<Payload>(options: McpAppViewOptions<Payload>) {
56
+ const [payload, setPayload] = useState<Payload | null>(null);
57
+ const [error, setError] = useState<string | null>(null);
58
+ const [hostContext, setHostContext] = useState<McpUiHostContext | undefined>();
59
+
60
+ const { app, error: connectionError } = useApp({
61
+ appInfo: { name: options.name, version: '1.0.0' },
62
+ // The views fit inline and have a full screen button (the server asks hosts to open them full screen)
63
+ capabilities: { availableDisplayModes: ['inline', 'fullscreen'] },
64
+ onAppCreated: (createdApp) => {
65
+ createdApp.ontoolresult = (result) => {
66
+ if (result.isError) {
67
+ setError(result.content?.find((item) => item.type === 'text')?.text ?? options.loadErrorMessage);
68
+ return;
69
+ }
70
+ loadPayload(createdApp, result, options)
71
+ .then(setPayload)
72
+ .catch((loadError) => setError(String(loadError?.message ?? loadError)));
73
+ };
74
+ createdApp.onhostcontextchanged = (context) => {
75
+ applyTheme(context);
76
+ setHostContext((previous) => ({ ...previous, ...context }));
77
+ };
78
+ },
79
+ });
80
+
81
+ useEffect(() => {
82
+ const context = app?.getHostContext();
83
+ applyTheme(context);
84
+ setHostContext(context);
85
+ }, [app]);
86
+
87
+ return { app, payload, error: connectionError?.message ?? error, hostContext };
88
+ }
89
+
90
+ /** Adds the view's styles and renders it */
91
+ export function mountView(View: ComponentType, styles: string) {
92
+ const style = document.createElement('style');
93
+ style.textContent = styles;
94
+ document.head.appendChild(style);
95
+
96
+ // No StrictMode: it mounts the view twice, which connects to the host twice
97
+ createRoot(document.getElementById('root')!).render(<View />);
98
+ }
@@ -0,0 +1,15 @@
1
+ /** Labels for the collections the MCP App views show, e.g. in a view's header */
2
+ export const RESOURCE_TYPE_LABELS: Record<string, string> = {
3
+ domains: 'Domain',
4
+ systems: 'System',
5
+ services: 'Service',
6
+ agents: 'Agent',
7
+ events: 'Event',
8
+ commands: 'Command',
9
+ queries: 'Query',
10
+ flows: 'Flow',
11
+ channels: 'Channel',
12
+ entities: 'Entity',
13
+ containers: 'Data store',
14
+ 'data-products': 'Data product',
15
+ };
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Shared by the MCP server and the EventCatalog viewer MCP App (view.tsx): one view that shows a
3
+ * resource's architecture diagram or its schema, so hosts keep one panel open and update it.
4
+ * Kept free of server-only imports so it can be bundled into the view.
5
+ */
6
+ import type { ArchitectureDiagramPayload } from '../architecture-diagram/shared';
7
+ import type { SchemaViewerPayload } from '../schema-viewer/shared';
8
+
9
+ /** The ui:// resource holding the view's HTML (built by scripts/build-mcp-apps.mjs) */
10
+ export const VIEWER_RESOURCE_URI = 'ui://eventcatalog/viewer.html';
11
+
12
+ /** Key in a tool result's `_meta` carrying what the view shows (not shown to the model) */
13
+ export const VIEWER_META_KEY = 'eventcatalog/view';
14
+
15
+ /** App-only tool the view calls to load a view when the host doesn't pass the result's `_meta`, or to open another one */
16
+ export const VIEWER_VIEW_TOOL = 'getResourceView';
17
+
18
+ /** What the viewer can show of a resource */
19
+ export const RESOURCE_VIEWS = ['architecture', 'schema'] as const;
20
+ export type ResourceView = (typeof RESOURCE_VIEWS)[number];
21
+
22
+ export type ViewerPayload =
23
+ | { view: 'architecture'; diagram: ArchitectureDiagramPayload }
24
+ | { view: 'schema'; schema: SchemaViewerPayload };
@@ -0,0 +1,25 @@
1
+ /*
2
+ * The viewer's Tailwind stylesheet (with Tailwind's base reset) and EventCatalog's theme variables.
3
+ * Only the classes the views and the components they render use, not every template in EventCatalog:
4
+ * add every component a view imports here, or its Tailwind classes won't be in the viewer's styles.
5
+ */
6
+ @import 'tailwindcss' source(none);
7
+ @import '../../../../styles/theme.css';
8
+
9
+ @source './*.tsx';
10
+ @source '../schema-viewer/*.tsx';
11
+ @source '../../../../components/SchemaExplorer/JSONSchemaViewer.tsx';
12
+ @source '../../../../components/SchemaExplorer/AvroSchemaViewer.tsx';
13
+ @source '../../../../components/SchemaExplorer/ProtobufSchemaViewer.tsx';
14
+
15
+ body {
16
+ margin: 0;
17
+ background: rgb(var(--ec-page-bg));
18
+ color: rgb(var(--ec-page-text));
19
+ font-family:
20
+ ui-sans-serif,
21
+ system-ui,
22
+ -apple-system,
23
+ 'Segoe UI',
24
+ sans-serif;
25
+ }
@@ -0,0 +1,59 @@
1
+ /**
2
+ * The EventCatalog viewer MCP App: one view that shows what showResource asks for, a resource's
3
+ * architecture diagram or its schema. Hosts keep one viewer open (ChatGPT, with the result's widget
4
+ * session) and each new result switches it to the new view. Hosts without MCP Apps support show the
5
+ * tool's text instead.
6
+ *
7
+ * Built into a single HTML file by scripts/build-mcp-apps.mjs and served as a ui:// resource.
8
+ */
9
+ import { useEffect } from 'react';
10
+ // Tailwind (with its base reset), built from the classes the views use, and EventCatalog's theme variables
11
+ import styles from './styles.css?inline';
12
+ import { mountView, useMcpAppView } from '../shared/app-view';
13
+ import { ArchitectureDiagramView, architectureDiagramStyles, VISUALISER_BODY_CLASS } from '../architecture-diagram/view';
14
+ import { SchemaViewerView } from '../schema-viewer/view';
15
+ import { VIEWER_META_KEY, VIEWER_VIEW_TOOL, type ViewerPayload } from './shared';
16
+
17
+ const isViewerPayload = (payload?: ViewerPayload): payload is ViewerPayload =>
18
+ (payload?.view === 'architecture' && Boolean(payload.diagram?.view)) ||
19
+ (payload?.view === 'schema' && Boolean(payload.schema?.schemas));
20
+
21
+ const VIEW_OPTIONS = {
22
+ name: 'EventCatalog',
23
+ metaKey: VIEWER_META_KEY,
24
+ viewTool: VIEWER_VIEW_TOOL,
25
+ isPayload: isViewerPayload,
26
+ getViewToolArguments: ({ view, resourceId, resourceVersion, resourceCollection }: Record<string, any>) => ({
27
+ view,
28
+ resourceId,
29
+ resourceVersion,
30
+ resourceCollection,
31
+ }),
32
+ loadErrorMessage: 'This could not be loaded from EventCatalog',
33
+ };
34
+
35
+ function Viewer() {
36
+ const { app, payload, error, hostContext } = useMcpAppView(VIEW_OPTIONS);
37
+
38
+ // The visualiser's theme variables would override EventCatalog's, so they only apply to the diagram
39
+ const isDiagram = payload?.view === 'architecture';
40
+ useEffect(() => {
41
+ document.body.classList.toggle(VISUALISER_BODY_CLASS, isDiagram);
42
+ }, [isDiagram]);
43
+
44
+ if (error) {
45
+ return <p className="p-4 text-sm">{error}</p>;
46
+ }
47
+
48
+ if (!payload) {
49
+ return <p className="p-4 text-sm text-[rgb(var(--ec-page-text-muted))]">Loading from EventCatalog…</p>;
50
+ }
51
+
52
+ return payload.view === 'architecture' ? (
53
+ <ArchitectureDiagramView app={app} payload={payload.diagram} hostContext={hostContext} />
54
+ ) : (
55
+ <SchemaViewerView app={app} payload={payload.schema} hostContext={hostContext} />
56
+ );
57
+ }
58
+
59
+ mountView(Viewer, [styles, architectureDiagramStyles].join('\n'));
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Embeds the icons shown on diagram nodes (e.g. a service's language, a database's engine) as data
3
+ * URIs, for MCP App views. Views run in a sandbox in the MCP host, where icon paths served by
4
+ * EventCatalog (e.g. /icons/languages/nodejs.svg) don't load.
5
+ */
6
+ import fs from 'node:fs/promises';
7
+ import path from 'node:path';
8
+
9
+ const MIME_TYPES: Record<string, string> = {
10
+ '.svg': 'image/svg+xml',
11
+ '.png': 'image/png',
12
+ '.jpg': 'image/jpeg',
13
+ '.jpeg': 'image/jpeg',
14
+ '.webp': 'image/webp',
15
+ '.gif': 'image/gif',
16
+ };
17
+
18
+ const MAX_DEPTH = 6;
19
+
20
+ async function toDataUri(iconPath: string, publicDirectory: string) {
21
+ // Icons from other sites are left as they are
22
+ if (!iconPath.startsWith('/')) return undefined;
23
+
24
+ const filePath = path.resolve(publicDirectory, `.${iconPath.split(/[?#]/)[0]}`);
25
+ if (!filePath.startsWith(path.resolve(publicDirectory) + path.sep)) return undefined;
26
+
27
+ const mimeType = MIME_TYPES[path.extname(filePath).toLowerCase()];
28
+ if (!mimeType) return undefined;
29
+
30
+ try {
31
+ return `data:${mimeType};base64,${(await fs.readFile(filePath)).toString('base64')}`;
32
+ } catch {
33
+ return undefined;
34
+ }
35
+ }
36
+
37
+ /** Every `styles.icon` in the node's data, which is where nodes keep their icon */
38
+ function collectIconStyles(value: unknown, found: Array<{ icon: string }>, depth = 0) {
39
+ if (!value || typeof value !== 'object' || depth > MAX_DEPTH) return;
40
+ for (const [key, child] of Object.entries(value)) {
41
+ if (key === 'styles' && child && typeof (child as any).icon === 'string') found.push(child as { icon: string });
42
+ else collectIconStyles(child, found, depth + 1);
43
+ }
44
+ }
45
+
46
+ /** A copy of the nodes with their icons embedded (the nodes given are not changed) */
47
+ export async function inlineNodeIcons<T extends { data?: unknown }>(nodes: T[], publicDirectory: string): Promise<T[]> {
48
+ const copy = structuredClone(nodes);
49
+ const styles: Array<{ icon: string }> = [];
50
+ copy.forEach((node) => collectIconStyles(node.data, styles));
51
+
52
+ const dataUris = new Map<string, string | undefined>();
53
+ await Promise.all(
54
+ [...new Set(styles.map((style) => style.icon))].map(async (icon) =>
55
+ dataUris.set(icon, await toDataUri(icon, publicDirectory))
56
+ )
57
+ );
58
+
59
+ styles.forEach((style) => {
60
+ style.icon = dataUris.get(style.icon) ?? style.icon;
61
+ });
62
+ return copy;
63
+ }