@frontmcp/uipack 0.12.2 → 1.0.0-beta.10

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 (298) hide show
  1. package/CLAUDE.md +56 -154
  2. package/README.md +367 -62
  3. package/adapters/base-template.d.ts +30 -0
  4. package/adapters/base-template.d.ts.map +1 -0
  5. package/adapters/cdn-info.d.ts +34 -0
  6. package/adapters/cdn-info.d.ts.map +1 -0
  7. package/adapters/constants.d.ts +18 -0
  8. package/adapters/constants.d.ts.map +1 -0
  9. package/adapters/content-detector.d.ts +19 -0
  10. package/adapters/content-detector.d.ts.map +1 -0
  11. package/adapters/content-renderers.d.ts +27 -0
  12. package/adapters/content-renderers.d.ts.map +1 -0
  13. package/adapters/index.d.ts +14 -7
  14. package/adapters/index.d.ts.map +1 -1
  15. package/adapters/index.js +2343 -426
  16. package/adapters/render-failure.d.ts +18 -0
  17. package/adapters/render-failure.d.ts.map +1 -0
  18. package/adapters/response-builder.d.ts +34 -104
  19. package/adapters/response-builder.d.ts.map +1 -1
  20. package/adapters/serving-mode.d.ts +28 -91
  21. package/adapters/serving-mode.d.ts.map +1 -1
  22. package/adapters/template-renderer.d.ts +50 -0
  23. package/adapters/template-renderer.d.ts.map +1 -0
  24. package/adapters/type-detector.d.ts +18 -0
  25. package/adapters/type-detector.d.ts.map +1 -0
  26. package/bridge-runtime/index.js +1 -1
  27. package/component/index.d.ts +14 -0
  28. package/component/index.d.ts.map +1 -0
  29. package/component/index.js +2043 -0
  30. package/component/loader.d.ts +36 -0
  31. package/component/loader.d.ts.map +1 -0
  32. package/component/renderer.d.ts +30 -0
  33. package/component/renderer.d.ts.map +1 -0
  34. package/component/transpiler.d.ts +49 -0
  35. package/component/transpiler.d.ts.map +1 -0
  36. package/component/types.d.ts +82 -0
  37. package/component/types.d.ts.map +1 -0
  38. package/esm/adapters/index.mjs +2337 -422
  39. package/esm/bridge-runtime/index.mjs +1 -1
  40. package/esm/component/index.mjs +2013 -0
  41. package/esm/index.mjs +3446 -13935
  42. package/esm/package.json +4 -13
  43. package/esm/resolver/index.mjs +661 -0
  44. package/esm/shell/index.mjs +1406 -0
  45. package/esm/types/index.mjs +11 -11
  46. package/esm/utils/index.mjs +53 -8
  47. package/index.d.ts +12 -40
  48. package/index.d.ts.map +1 -1
  49. package/index.js +3579 -14218
  50. package/package.json +4 -13
  51. package/resolver/cdn-registry.d.ts +39 -0
  52. package/resolver/cdn-registry.d.ts.map +1 -0
  53. package/resolver/esm-sh.resolver.d.ts +54 -0
  54. package/resolver/esm-sh.resolver.d.ts.map +1 -0
  55. package/resolver/import-map.d.ts +47 -0
  56. package/resolver/import-map.d.ts.map +1 -0
  57. package/resolver/import-parser.d.ts +28 -0
  58. package/resolver/import-parser.d.ts.map +1 -0
  59. package/resolver/import-rewriter.d.ts +29 -0
  60. package/resolver/import-rewriter.d.ts.map +1 -0
  61. package/resolver/index.d.ts +15 -0
  62. package/resolver/index.d.ts.map +1 -0
  63. package/resolver/index.js +708 -0
  64. package/resolver/types.d.ts +191 -0
  65. package/resolver/types.d.ts.map +1 -0
  66. package/shell/builder.d.ts +31 -0
  67. package/shell/builder.d.ts.map +1 -0
  68. package/shell/csp.d.ts +37 -0
  69. package/shell/csp.d.ts.map +1 -0
  70. package/shell/custom-shell-applier.d.ts +33 -0
  71. package/shell/custom-shell-applier.d.ts.map +1 -0
  72. package/shell/custom-shell-resolver.d.ts +47 -0
  73. package/shell/custom-shell-resolver.d.ts.map +1 -0
  74. package/shell/custom-shell-types.d.ts +75 -0
  75. package/shell/custom-shell-types.d.ts.map +1 -0
  76. package/shell/custom-shell-validator.d.ts +26 -0
  77. package/shell/custom-shell-validator.d.ts.map +1 -0
  78. package/shell/data-injector.d.ts +40 -0
  79. package/shell/data-injector.d.ts.map +1 -0
  80. package/shell/index.d.ts +19 -0
  81. package/shell/index.d.ts.map +1 -0
  82. package/shell/index.js +1453 -0
  83. package/shell/types.d.ts +54 -0
  84. package/shell/types.d.ts.map +1 -0
  85. package/types/index.d.ts +1 -3
  86. package/types/index.d.ts.map +1 -1
  87. package/types/index.js +11 -11
  88. package/types/ui-config.d.ts +50 -11
  89. package/types/ui-config.d.ts.map +1 -1
  90. package/types/ui-runtime.d.ts +8 -82
  91. package/types/ui-runtime.d.ts.map +1 -1
  92. package/utils/index.d.ts +9 -3
  93. package/utils/index.d.ts.map +1 -1
  94. package/utils/index.js +59 -7
  95. package/adapters/platform-meta.constants.d.ts +0 -26
  96. package/adapters/platform-meta.constants.d.ts.map +0 -1
  97. package/adapters/platform-meta.d.ts +0 -234
  98. package/adapters/platform-meta.d.ts.map +0 -1
  99. package/base-template/bridge.d.ts +0 -90
  100. package/base-template/bridge.d.ts.map +0 -1
  101. package/base-template/default-base-template.d.ts +0 -91
  102. package/base-template/default-base-template.d.ts.map +0 -1
  103. package/base-template/index.d.ts +0 -15
  104. package/base-template/index.d.ts.map +0 -1
  105. package/base-template/index.js +0 -1393
  106. package/base-template/polyfills.d.ts +0 -31
  107. package/base-template/polyfills.d.ts.map +0 -1
  108. package/base-template/theme-styles.d.ts +0 -74
  109. package/base-template/theme-styles.d.ts.map +0 -1
  110. package/build/builders/base-builder.d.ts +0 -124
  111. package/build/builders/base-builder.d.ts.map +0 -1
  112. package/build/builders/esbuild-config.d.ts +0 -94
  113. package/build/builders/esbuild-config.d.ts.map +0 -1
  114. package/build/builders/hybrid-builder.d.ts +0 -93
  115. package/build/builders/hybrid-builder.d.ts.map +0 -1
  116. package/build/builders/index.d.ts +0 -17
  117. package/build/builders/index.d.ts.map +0 -1
  118. package/build/builders/inline-builder.d.ts +0 -83
  119. package/build/builders/inline-builder.d.ts.map +0 -1
  120. package/build/builders/static-builder.d.ts +0 -78
  121. package/build/builders/static-builder.d.ts.map +0 -1
  122. package/build/builders/types.d.ts +0 -341
  123. package/build/builders/types.d.ts.map +0 -1
  124. package/build/cdn-resources.d.ts +0 -244
  125. package/build/cdn-resources.d.ts.map +0 -1
  126. package/build/hybrid-data.d.ts +0 -127
  127. package/build/hybrid-data.d.ts.map +0 -1
  128. package/build/index.d.ts +0 -299
  129. package/build/index.d.ts.map +0 -1
  130. package/build/index.js +0 -8699
  131. package/build/ui-components-browser.d.ts +0 -64
  132. package/build/ui-components-browser.d.ts.map +0 -1
  133. package/build/widget-manifest.d.ts +0 -362
  134. package/build/widget-manifest.d.ts.map +0 -1
  135. package/bundler/cache.d.ts +0 -173
  136. package/bundler/cache.d.ts.map +0 -1
  137. package/bundler/file-cache/component-builder.d.ts +0 -167
  138. package/bundler/file-cache/component-builder.d.ts.map +0 -1
  139. package/bundler/file-cache/hash-calculator.d.ts +0 -155
  140. package/bundler/file-cache/hash-calculator.d.ts.map +0 -1
  141. package/bundler/file-cache/index.d.ts +0 -12
  142. package/bundler/file-cache/index.d.ts.map +0 -1
  143. package/bundler/file-cache/storage/filesystem.d.ts +0 -149
  144. package/bundler/file-cache/storage/filesystem.d.ts.map +0 -1
  145. package/bundler/file-cache/storage/index.d.ts +0 -11
  146. package/bundler/file-cache/storage/index.d.ts.map +0 -1
  147. package/bundler/file-cache/storage/interface.d.ts +0 -152
  148. package/bundler/file-cache/storage/interface.d.ts.map +0 -1
  149. package/bundler/file-cache/storage/redis.d.ts +0 -139
  150. package/bundler/file-cache/storage/redis.d.ts.map +0 -1
  151. package/bundler/index.d.ts +0 -35
  152. package/bundler/index.d.ts.map +0 -1
  153. package/bundler/index.js +0 -2953
  154. package/bundler/sandbox/enclave-adapter.d.ts +0 -121
  155. package/bundler/sandbox/enclave-adapter.d.ts.map +0 -1
  156. package/bundler/sandbox/executor.d.ts +0 -14
  157. package/bundler/sandbox/executor.d.ts.map +0 -1
  158. package/bundler/sandbox/policy.d.ts +0 -62
  159. package/bundler/sandbox/policy.d.ts.map +0 -1
  160. package/bundler/types.d.ts +0 -702
  161. package/bundler/types.d.ts.map +0 -1
  162. package/dependency/cdn-registry.d.ts +0 -98
  163. package/dependency/cdn-registry.d.ts.map +0 -1
  164. package/dependency/import-map.d.ts +0 -186
  165. package/dependency/import-map.d.ts.map +0 -1
  166. package/dependency/import-parser.d.ts +0 -82
  167. package/dependency/import-parser.d.ts.map +0 -1
  168. package/dependency/index.d.ts +0 -17
  169. package/dependency/index.d.ts.map +0 -1
  170. package/dependency/index.js +0 -3180
  171. package/dependency/resolver.d.ts +0 -164
  172. package/dependency/resolver.d.ts.map +0 -1
  173. package/dependency/schemas.d.ts +0 -486
  174. package/dependency/schemas.d.ts.map +0 -1
  175. package/dependency/template-loader.d.ts +0 -204
  176. package/dependency/template-loader.d.ts.map +0 -1
  177. package/dependency/template-processor.d.ts +0 -118
  178. package/dependency/template-processor.d.ts.map +0 -1
  179. package/dependency/types.d.ts +0 -739
  180. package/dependency/types.d.ts.map +0 -1
  181. package/esm/base-template/index.mjs +0 -1359
  182. package/esm/build/index.mjs +0 -8601
  183. package/esm/bundler/index.mjs +0 -2895
  184. package/esm/dependency/index.mjs +0 -3068
  185. package/esm/handlebars/index.mjs +0 -587
  186. package/esm/registry/index.mjs +0 -6305
  187. package/esm/renderers/index.mjs +0 -1557
  188. package/esm/runtime/index.mjs +0 -5361
  189. package/esm/styles/index.mjs +0 -171
  190. package/esm/theme/index.mjs +0 -756
  191. package/esm/tool-template/index.mjs +0 -3652
  192. package/esm/validation/index.mjs +0 -542
  193. package/handlebars/expression-extractor.d.ts +0 -147
  194. package/handlebars/expression-extractor.d.ts.map +0 -1
  195. package/handlebars/helpers.d.ts +0 -339
  196. package/handlebars/helpers.d.ts.map +0 -1
  197. package/handlebars/index.d.ts +0 -195
  198. package/handlebars/index.d.ts.map +0 -1
  199. package/handlebars/index.js +0 -659
  200. package/preview/claude-preview.d.ts +0 -67
  201. package/preview/claude-preview.d.ts.map +0 -1
  202. package/preview/generic-preview.d.ts +0 -66
  203. package/preview/generic-preview.d.ts.map +0 -1
  204. package/preview/index.d.ts +0 -36
  205. package/preview/index.d.ts.map +0 -1
  206. package/preview/openai-preview.d.ts +0 -70
  207. package/preview/openai-preview.d.ts.map +0 -1
  208. package/preview/types.d.ts +0 -199
  209. package/preview/types.d.ts.map +0 -1
  210. package/registry/index.d.ts +0 -46
  211. package/registry/index.d.ts.map +0 -1
  212. package/registry/index.js +0 -6342
  213. package/registry/render-template.d.ts +0 -91
  214. package/registry/render-template.d.ts.map +0 -1
  215. package/registry/tool-ui.registry.d.ts +0 -294
  216. package/registry/tool-ui.registry.d.ts.map +0 -1
  217. package/registry/uri-utils.d.ts +0 -56
  218. package/registry/uri-utils.d.ts.map +0 -1
  219. package/renderers/cache.d.ts +0 -145
  220. package/renderers/cache.d.ts.map +0 -1
  221. package/renderers/html.renderer.d.ts +0 -123
  222. package/renderers/html.renderer.d.ts.map +0 -1
  223. package/renderers/index.d.ts +0 -36
  224. package/renderers/index.d.ts.map +0 -1
  225. package/renderers/index.js +0 -1603
  226. package/renderers/mdx-client.renderer.d.ts +0 -124
  227. package/renderers/mdx-client.renderer.d.ts.map +0 -1
  228. package/renderers/registry.d.ts +0 -133
  229. package/renderers/registry.d.ts.map +0 -1
  230. package/renderers/types.d.ts +0 -343
  231. package/renderers/types.d.ts.map +0 -1
  232. package/renderers/utils/detect.d.ts +0 -107
  233. package/renderers/utils/detect.d.ts.map +0 -1
  234. package/renderers/utils/hash.d.ts +0 -40
  235. package/renderers/utils/hash.d.ts.map +0 -1
  236. package/renderers/utils/index.d.ts +0 -9
  237. package/renderers/utils/index.d.ts.map +0 -1
  238. package/renderers/utils/transpiler.d.ts +0 -70
  239. package/renderers/utils/transpiler.d.ts.map +0 -1
  240. package/runtime/adapters/html.adapter.d.ts +0 -59
  241. package/runtime/adapters/html.adapter.d.ts.map +0 -1
  242. package/runtime/adapters/index.d.ts +0 -26
  243. package/runtime/adapters/index.d.ts.map +0 -1
  244. package/runtime/adapters/mdx.adapter.d.ts +0 -73
  245. package/runtime/adapters/mdx.adapter.d.ts.map +0 -1
  246. package/runtime/adapters/types.d.ts +0 -95
  247. package/runtime/adapters/types.d.ts.map +0 -1
  248. package/runtime/csp.d.ts +0 -48
  249. package/runtime/csp.d.ts.map +0 -1
  250. package/runtime/index.d.ts +0 -17
  251. package/runtime/index.d.ts.map +0 -1
  252. package/runtime/index.js +0 -5432
  253. package/runtime/mcp-bridge.d.ts +0 -101
  254. package/runtime/mcp-bridge.d.ts.map +0 -1
  255. package/runtime/renderer-runtime.d.ts +0 -133
  256. package/runtime/renderer-runtime.d.ts.map +0 -1
  257. package/runtime/sanitizer.d.ts +0 -180
  258. package/runtime/sanitizer.d.ts.map +0 -1
  259. package/runtime/types.d.ts +0 -415
  260. package/runtime/types.d.ts.map +0 -1
  261. package/runtime/wrapper.d.ts +0 -421
  262. package/runtime/wrapper.d.ts.map +0 -1
  263. package/styles/index.d.ts +0 -8
  264. package/styles/index.d.ts.map +0 -1
  265. package/styles/index.js +0 -222
  266. package/styles/variants.d.ts +0 -51
  267. package/styles/variants.d.ts.map +0 -1
  268. package/theme/cdn.d.ts +0 -195
  269. package/theme/cdn.d.ts.map +0 -1
  270. package/theme/css-to-theme.d.ts +0 -64
  271. package/theme/css-to-theme.d.ts.map +0 -1
  272. package/theme/index.d.ts +0 -19
  273. package/theme/index.d.ts.map +0 -1
  274. package/theme/index.js +0 -814
  275. package/theme/platforms.d.ts +0 -102
  276. package/theme/platforms.d.ts.map +0 -1
  277. package/theme/presets/github-openai.d.ts +0 -50
  278. package/theme/presets/github-openai.d.ts.map +0 -1
  279. package/theme/presets/index.d.ts +0 -11
  280. package/theme/presets/index.d.ts.map +0 -1
  281. package/theme/theme.d.ts +0 -396
  282. package/theme/theme.d.ts.map +0 -1
  283. package/tool-template/builder.d.ts +0 -213
  284. package/tool-template/builder.d.ts.map +0 -1
  285. package/tool-template/index.d.ts +0 -16
  286. package/tool-template/index.d.ts.map +0 -1
  287. package/tool-template/index.js +0 -3690
  288. package/validation/error-box.d.ts +0 -56
  289. package/validation/error-box.d.ts.map +0 -1
  290. package/validation/index.d.ts +0 -13
  291. package/validation/index.d.ts.map +0 -1
  292. package/validation/index.js +0 -576
  293. package/validation/schema-paths.d.ts +0 -118
  294. package/validation/schema-paths.d.ts.map +0 -1
  295. package/validation/template-validator.d.ts +0 -143
  296. package/validation/template-validator.d.ts.map +0 -1
  297. package/validation/wrapper.d.ts +0 -97
  298. package/validation/wrapper.d.ts.map +0 -1
@@ -1,514 +1,2429 @@
1
- // libs/uipack/src/adapters/platform-meta.constants.ts
2
- var DISPLAY_MODE_MAP = {
3
- // Standard MCP Apps modes
4
- inline: "inline",
5
- fullscreen: "fullscreen",
6
- pip: "pip",
7
- // OpenAI-style aliases
8
- widget: "inline",
9
- panel: "fullscreen"
10
- };
1
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
2
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
3
+ }) : x)(function(x) {
4
+ if (typeof require !== "undefined") return require.apply(this, arguments);
5
+ throw Error('Dynamic require of "' + x + '" is not supported');
6
+ });
11
7
 
12
- // libs/uipack/src/adapters/platform-meta.ts
13
- function getExtAppsMimeType(variant = "standard") {
14
- switch (variant) {
15
- case "profile":
16
- return "text/html;profile=mcp-app";
17
- case "standard":
18
- default:
19
- return "text/html+mcp";
20
- }
21
- }
22
- function isExtAppsMimeType(mimeType) {
23
- return mimeType === "text/html+mcp" || mimeType === "text/html;profile=mcp-app";
24
- }
25
- function buildUIMeta(options) {
26
- const { uiConfig, platformType, html, token, directUrl, rendererType, contentHash, manifestUri } = options;
27
- const meta = {};
28
- switch (platformType) {
29
- case "openai":
30
- meta["openai/html"] = html;
31
- meta["openai/mimeType"] = "text/html+skybridge";
32
- if (rendererType) meta["openai/type"] = rendererType;
33
- if (contentHash) meta["openai/contentHash"] = contentHash;
34
- if (manifestUri) meta["openai/manifestUri"] = manifestUri;
35
- if (token) meta["openai/widgetToken"] = token;
36
- if (directUrl) meta["openai/directUrl"] = directUrl;
37
- return buildOpenAIMeta(meta, uiConfig);
38
- case "ext-apps":
39
- meta["ui/html"] = html;
40
- meta["ui/mimeType"] = "text/html+mcp";
41
- if (rendererType) meta["ui/type"] = rendererType;
42
- if (contentHash) meta["ui/contentHash"] = contentHash;
43
- if (manifestUri) meta["ui/manifestUri"] = manifestUri;
44
- if (token) meta["ui/widgetToken"] = token;
45
- if (directUrl) meta["ui/directUrl"] = directUrl;
46
- return buildExtAppsMeta(meta, uiConfig);
47
- case "claude":
48
- case "cursor":
49
- case "continue":
50
- case "cody":
51
- case "generic-mcp":
52
- case "gemini":
53
- default:
54
- meta["ui/html"] = html;
55
- meta["ui/mimeType"] = "text/html+mcp";
56
- if (rendererType) meta["ui/type"] = rendererType;
57
- if (contentHash) meta["ui/contentHash"] = contentHash;
58
- if (manifestUri) meta["ui/manifestUri"] = manifestUri;
59
- if (token) meta["ui/widgetToken"] = token;
60
- if (directUrl) meta["ui/directUrl"] = directUrl;
61
- if (platformType === "claude") {
62
- return buildClaudeMeta(meta, uiConfig);
63
- } else if (platformType === "gemini") {
64
- return buildGeminiMeta(meta, uiConfig);
65
- } else if (platformType === "cursor" || platformType === "continue" || platformType === "cody") {
66
- return buildIDEMeta(meta, uiConfig);
67
- } else if (platformType === "generic-mcp") {
68
- return buildGenericMeta(meta, uiConfig);
69
- }
70
- return meta;
71
- }
72
- }
73
- function buildOpenAIMeta(meta, uiConfig) {
74
- if (uiConfig.invocationStatus?.invoking) {
75
- meta["openai/toolInvocation/invoking"] = uiConfig.invocationStatus.invoking;
8
+ // libs/uipack/src/adapters/constants.ts
9
+ var MCP_APPS_MIME_TYPE = "text/html;profile=mcp-app";
10
+ var MCP_APPS_EXTENSION_ID = "io.modelcontextprotocol/ui";
11
+
12
+ // libs/uipack/src/adapters/serving-mode.ts
13
+ function resolveServingMode(options) {
14
+ const { configuredMode, platformType } = options;
15
+ if (platformType === "gemini") {
16
+ return {
17
+ mode: configuredMode,
18
+ supportsUI: false,
19
+ effectiveMode: null,
20
+ useStructuredContent: false,
21
+ reason: "Gemini does not support MCP Apps UI"
22
+ };
76
23
  }
77
- if (uiConfig.invocationStatus?.invoked) {
78
- meta["openai/toolInvocation/invoked"] = uiConfig.invocationStatus.invoked;
24
+ const effectiveMode = configuredMode === "auto" ? "inline" : configuredMode;
25
+ if (effectiveMode === "hybrid") {
26
+ const hybridCapable = platformType === "openai" || platformType === "ext-apps" || platformType === "cursor";
27
+ if (!hybridCapable) {
28
+ return {
29
+ mode: configuredMode,
30
+ supportsUI: false,
31
+ effectiveMode: null,
32
+ useStructuredContent: false,
33
+ reason: `Platform ${platformType} does not support hybrid serving mode`
34
+ };
35
+ }
79
36
  }
80
- return meta;
37
+ const useStructuredContent = platformType === "openai" || platformType === "ext-apps" || platformType === "generic-mcp" || platformType === "unknown";
38
+ return {
39
+ mode: configuredMode,
40
+ supportsUI: true,
41
+ effectiveMode,
42
+ useStructuredContent,
43
+ reason: `Platform ${platformType} supports UI, mode: ${effectiveMode}`
44
+ };
81
45
  }
82
- function buildOpenAICSP(csp) {
83
- const result = {};
84
- if (csp.connectDomains?.length) {
85
- result.connect_domains = csp.connectDomains;
46
+
47
+ // libs/uipack/src/adapters/response-builder.ts
48
+ function buildToolResponseContent(options) {
49
+ const { rawOutput, htmlContent, servingMode, useStructuredContent, platformType } = options;
50
+ const textSummary = typeof rawOutput === "string" ? rawOutput : rawOutput !== null && rawOutput !== void 0 ? JSON.stringify(rawOutput) : "";
51
+ const result = {
52
+ content: [{ type: "text", text: textSummary }]
53
+ };
54
+ if (useStructuredContent && rawOutput !== null && rawOutput !== void 0) {
55
+ const structured = typeof rawOutput === "object" && !Array.isArray(rawOutput) ? rawOutput : { value: rawOutput };
56
+ result.structuredContent = structured;
86
57
  }
87
- if (csp.resourceDomains?.length) {
88
- result.resource_domains = csp.resourceDomains;
58
+ if (htmlContent && servingMode === "inline") {
59
+ const htmlKey = "ui/html";
60
+ result._meta = {
61
+ [htmlKey]: htmlContent
62
+ };
63
+ result.format = "html";
89
64
  }
90
65
  return result;
91
66
  }
92
- function buildClaudeMeta(meta, uiConfig) {
93
- if (uiConfig.widgetDescription) {
94
- meta["claude/widgetDescription"] = uiConfig.widgetDescription;
67
+
68
+ // libs/uipack/src/adapters/render-failure.ts
69
+ function isUIRenderFailure(result) {
70
+ if (typeof result !== "object" || result === null) {
71
+ return false;
95
72
  }
96
- if (uiConfig.displayMode) {
97
- meta["claude/displayMode"] = uiConfig.displayMode;
73
+ const rec = result;
74
+ return typeof rec["reason"] === "string" && !("meta" in rec);
75
+ }
76
+
77
+ // libs/uipack/src/shell/csp.ts
78
+ var DEFAULT_CDN_DOMAINS = [
79
+ "https://cdn.jsdelivr.net",
80
+ "https://cdnjs.cloudflare.com",
81
+ "https://fonts.googleapis.com",
82
+ "https://fonts.gstatic.com",
83
+ "https://esm.sh"
84
+ ];
85
+ var DEFAULT_CSP_DIRECTIVES = [
86
+ "default-src 'none'",
87
+ `script-src 'self' 'unsafe-inline' ${DEFAULT_CDN_DOMAINS.join(" ")}`,
88
+ `style-src 'self' 'unsafe-inline' ${DEFAULT_CDN_DOMAINS.join(" ")}`,
89
+ `img-src 'self' data: ${DEFAULT_CDN_DOMAINS.join(" ")}`,
90
+ `font-src 'self' data: ${DEFAULT_CDN_DOMAINS.join(" ")}`,
91
+ `connect-src ${DEFAULT_CDN_DOMAINS.join(" ")}`,
92
+ "object-src 'self' data:"
93
+ ];
94
+ function buildCSPDirectives(csp) {
95
+ if (!csp) {
96
+ return [...DEFAULT_CSP_DIRECTIVES];
98
97
  }
99
- if (uiConfig.widgetAccessible) {
100
- meta["claude/widgetAccessible"] = true;
98
+ const validResourceDomains = sanitizeCSPDomains(csp.resourceDomains);
99
+ const validConnectDomains = sanitizeCSPDomains(csp.connectDomains);
100
+ const allResourceDomains = [.../* @__PURE__ */ new Set([...DEFAULT_CDN_DOMAINS, ...validResourceDomains])];
101
+ const directives = [
102
+ "default-src 'none'",
103
+ `script-src 'self' 'unsafe-inline' ${allResourceDomains.join(" ")}`,
104
+ `style-src 'self' 'unsafe-inline' ${allResourceDomains.join(" ")}`
105
+ ];
106
+ const imgSources = ["'self'", "data:", ...allResourceDomains];
107
+ directives.push(`img-src ${imgSources.join(" ")}`);
108
+ const fontSources = ["'self'", "data:", ...allResourceDomains];
109
+ directives.push(`font-src ${fontSources.join(" ")}`);
110
+ if (validConnectDomains.length) {
111
+ directives.push(`connect-src ${validConnectDomains.join(" ")}`);
112
+ } else {
113
+ directives.push(`connect-src ${allResourceDomains.join(" ")}`);
101
114
  }
102
- if (uiConfig.prefersBorder !== void 0) {
103
- meta["claude/prefersBorder"] = uiConfig.prefersBorder;
115
+ directives.push("object-src 'self' data:");
116
+ return directives;
117
+ }
118
+ function buildCSPMetaTag(csp) {
119
+ const directives = buildCSPDirectives(csp);
120
+ const content = directives.join("; ");
121
+ return `<meta http-equiv="Content-Security-Policy" content="${escapeAttribute(content)}">`;
122
+ }
123
+ function validateCSPDomain(domain) {
124
+ if (domain.startsWith("https://*.")) {
125
+ const rest = domain.slice(10);
126
+ return /^[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?\.[a-zA-Z]{2,}$/.test(rest);
104
127
  }
105
- if (uiConfig.resourceUri) {
106
- meta["ui/resourceUri"] = uiConfig.resourceUri;
128
+ try {
129
+ const url = new URL(domain);
130
+ return url.protocol === "https:";
131
+ } catch {
132
+ return false;
107
133
  }
108
- if (uiConfig.csp) {
109
- const csp = {};
110
- if (uiConfig.csp.connectDomains?.length) {
111
- csp.connectDomains = uiConfig.csp.connectDomains;
134
+ }
135
+ function sanitizeCSPDomains(domains) {
136
+ if (!domains) return [];
137
+ const valid = [];
138
+ for (const domain of domains) {
139
+ if (validateCSPDomain(domain)) {
140
+ valid.push(domain);
141
+ } else {
142
+ console.warn(`Invalid CSP domain ignored: ${domain}`);
112
143
  }
113
- if (uiConfig.csp.resourceDomains?.length) {
114
- csp.resourceDomains = uiConfig.csp.resourceDomains;
144
+ }
145
+ return valid;
146
+ }
147
+ function escapeAttribute(str) {
148
+ return str.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/'/g, "&#39;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
149
+ }
150
+
151
+ // libs/uipack/src/utils/index.ts
152
+ function escapeHtml(str) {
153
+ if (str === null || str === void 0) {
154
+ return "";
155
+ }
156
+ const s = String(str);
157
+ return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;").replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029");
158
+ }
159
+ function escapeScriptClose(jsonString) {
160
+ return jsonString.replace(/<\//g, "<\\/");
161
+ }
162
+ function safeJsonForScript(value) {
163
+ if (value === void 0) {
164
+ return "null";
165
+ }
166
+ try {
167
+ const jsonString = JSON.stringify(value, (_key, val) => {
168
+ if (typeof val === "bigint") {
169
+ return val.toString();
170
+ }
171
+ return val;
172
+ });
173
+ if (jsonString === void 0) {
174
+ return "null";
115
175
  }
116
- if (Object.keys(csp).length > 0) {
117
- meta["ui/csp"] = csp;
176
+ return escapeScriptClose(jsonString);
177
+ } catch {
178
+ return '{"error":"Value could not be serialized"}';
179
+ }
180
+ }
181
+
182
+ // libs/uipack/src/shell/data-injector.ts
183
+ function buildDataInjectionScript(options) {
184
+ const { toolName, input, output, structuredContent } = options;
185
+ const lines = [
186
+ `window.__mcpToolName = ${safeJsonForScript(toolName)};`,
187
+ `window.__mcpToolInput = ${safeJsonForScript(input ?? null)};`,
188
+ `window.__mcpToolOutput = ${safeJsonForScript(output ?? null)};`,
189
+ `window.__mcpStructuredContent = ${safeJsonForScript(structuredContent ?? null)};`
190
+ ];
191
+ return `<script>
192
+ ${lines.join("\n")}
193
+ </script>`;
194
+ }
195
+ var _uniqueIdCounter = 0;
196
+ function createTemplateHelpers() {
197
+ return {
198
+ escapeHtml: (str) => escapeHtml(str),
199
+ formatDate: (date, format) => {
200
+ const d = date instanceof Date ? date : new Date(date);
201
+ if (isNaN(d.getTime())) return String(date);
202
+ if (format === "iso") return d.toISOString();
203
+ if (format === "date") return d.toLocaleDateString();
204
+ if (format === "time") return d.toLocaleTimeString();
205
+ return d.toLocaleString();
206
+ },
207
+ formatCurrency: (amount, currency = "USD") => {
208
+ return new Intl.NumberFormat("en-US", {
209
+ style: "currency",
210
+ currency
211
+ }).format(amount);
212
+ },
213
+ uniqueId: (prefix = "mcp") => {
214
+ return `${prefix}-${++_uniqueIdCounter}`;
215
+ },
216
+ jsonEmbed: (data) => {
217
+ return escapeScriptClose(JSON.stringify(data));
118
218
  }
219
+ };
220
+ }
221
+
222
+ // libs/uipack/src/bridge-runtime/iife-generator.ts
223
+ function generateBridgeIIFE(options = {}) {
224
+ const { debug = false, trustedOrigins = [], minify = false } = options;
225
+ const adapters = options.adapters || ["openai", "ext-apps", "claude", "gemini", "generic"];
226
+ const parts = [];
227
+ parts.push("(function() {");
228
+ parts.push('"use strict";');
229
+ parts.push("");
230
+ if (debug) {
231
+ parts.push('function log(msg) { console.log("[FrontMcpBridge] " + msg); }');
232
+ } else {
233
+ parts.push("function log() {}");
234
+ }
235
+ parts.push("");
236
+ parts.push("var DEFAULT_SAFE_AREA = { top: 0, bottom: 0, left: 0, right: 0 };");
237
+ parts.push("");
238
+ parts.push(generateContextDetection());
239
+ parts.push("");
240
+ parts.push(generateBaseCapabilities());
241
+ parts.push("");
242
+ if (adapters.includes("openai")) {
243
+ parts.push(generateOpenAIAdapter());
244
+ parts.push("");
245
+ }
246
+ if (adapters.includes("ext-apps")) {
247
+ parts.push(generateExtAppsAdapter(trustedOrigins));
248
+ parts.push("");
249
+ }
250
+ if (adapters.includes("claude")) {
251
+ parts.push(generateClaudeAdapter());
252
+ parts.push("");
119
253
  }
120
- return meta;
254
+ if (adapters.includes("gemini")) {
255
+ parts.push(generateGeminiAdapter());
256
+ parts.push("");
257
+ }
258
+ if (adapters.includes("generic")) {
259
+ parts.push(generateGenericAdapter());
260
+ parts.push("");
261
+ }
262
+ parts.push(generatePlatformDetection(adapters));
263
+ parts.push("");
264
+ parts.push(generateBridgeClass());
265
+ parts.push("");
266
+ parts.push("var bridge = new FrontMcpBridge();");
267
+ parts.push("bridge.initialize().then(function() {");
268
+ parts.push(' log("Bridge initialized with adapter: " + bridge.adapterId);');
269
+ parts.push(' window.dispatchEvent(new CustomEvent("bridge:ready", { detail: { adapter: bridge.adapterId } }));');
270
+ parts.push("}).catch(function(err) {");
271
+ parts.push(' console.error("[FrontMcpBridge] Init failed:", err);');
272
+ parts.push(' window.dispatchEvent(new CustomEvent("bridge:error", { detail: { error: err } }));');
273
+ parts.push("});");
274
+ parts.push("");
275
+ parts.push("window.FrontMcpBridge = bridge;");
276
+ parts.push("})();");
277
+ const code = parts.join("\n");
278
+ if (minify) {
279
+ return minifyJS(code);
280
+ }
281
+ return code;
121
282
  }
122
- function buildGeminiMeta(meta, uiConfig) {
123
- if (uiConfig.widgetDescription) {
124
- meta["gemini/widgetDescription"] = uiConfig.widgetDescription;
283
+ function generateContextDetection() {
284
+ return `
285
+ function detectTheme() {
286
+ if (typeof window !== 'undefined' && window.matchMedia) {
287
+ return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
125
288
  }
126
- return meta;
289
+ return 'light';
127
290
  }
128
- function buildIDEMeta(meta, uiConfig) {
129
- if (uiConfig.widgetDescription) {
130
- meta["ide/widgetDescription"] = uiConfig.widgetDescription;
291
+
292
+ function detectLocale() {
293
+ if (typeof navigator !== 'undefined') {
294
+ return navigator.language || 'en-US';
131
295
  }
132
- return meta;
296
+ return 'en-US';
133
297
  }
134
- function buildFrontMCPCSP(csp) {
135
- const result = {};
136
- if (csp.connectDomains?.length) {
137
- result.connectDomains = csp.connectDomains;
298
+
299
+ function detectUserAgent() {
300
+ if (typeof navigator === 'undefined') {
301
+ return { type: 'web', hover: true, touch: false };
138
302
  }
139
- if (csp.resourceDomains?.length) {
140
- result.resourceDomains = csp.resourceDomains;
303
+ var ua = navigator.userAgent || '';
304
+ var isMobile = /iPhone|iPad|iPod|Android/i.test(ua);
305
+ var hasTouch = 'ontouchstart' in window || navigator.maxTouchPoints > 0;
306
+ var hasHover = window.matchMedia && window.matchMedia('(hover: hover)').matches;
307
+ return { type: isMobile ? 'mobile' : 'web', hover: hasHover !== false, touch: hasTouch };
308
+ }
309
+
310
+ function detectViewport() {
311
+ if (typeof window !== 'undefined') {
312
+ return { width: window.innerWidth, height: window.innerHeight };
141
313
  }
142
- return result;
314
+ return undefined;
315
+ }
316
+
317
+ function readInjectedData() {
318
+ var data = { toolInput: {}, toolOutput: undefined, structuredContent: undefined };
319
+ if (typeof window !== 'undefined') {
320
+ if (window.__mcpToolInput) data.toolInput = window.__mcpToolInput;
321
+ if (window.__mcpToolOutput) data.toolOutput = window.__mcpToolOutput;
322
+ if (window.__mcpStructuredContent) data.structuredContent = window.__mcpStructuredContent;
323
+ }
324
+ return data;
325
+ }
326
+ `.trim();
327
+ }
328
+ function generateBaseCapabilities() {
329
+ return `
330
+ var DEFAULT_CAPABILITIES = {
331
+ canCallTools: false,
332
+ canSendMessages: false,
333
+ canOpenLinks: false,
334
+ canPersistState: true,
335
+ hasNetworkAccess: true,
336
+ supportsDisplayModes: false,
337
+ supportsTheme: true
338
+ };
339
+ `.trim();
143
340
  }
144
- function buildGenericMeta(meta, uiConfig) {
145
- if (uiConfig.csp) {
146
- const csp = {};
147
- if (uiConfig.csp.connectDomains?.length) {
148
- csp.connectDomains = uiConfig.csp.connectDomains;
341
+ function generateOpenAIAdapter() {
342
+ return `
343
+ var OpenAIAdapter = {
344
+ id: 'openai',
345
+ name: 'OpenAI ChatGPT',
346
+ priority: 100,
347
+ capabilities: Object.assign({}, DEFAULT_CAPABILITIES, {
348
+ canCallTools: true,
349
+ canSendMessages: true,
350
+ canOpenLinks: true,
351
+ supportsDisplayModes: true
352
+ }),
353
+ canHandle: function() {
354
+ if (typeof window === 'undefined') return false;
355
+ // Check for window.openai.callTool (the actual OpenAI SDK API)
356
+ if (window.openai && typeof window.openai.callTool === 'function') return true;
357
+ // Also check if we're being injected with tool metadata (OpenAI injects toolOutput)
358
+ if (window.openai && (window.openai.toolOutput !== undefined || window.openai.toolInput !== undefined)) return true;
359
+ return false;
360
+ },
361
+ initialize: function(context) {
362
+ var sdk = window.openai;
363
+ context.sdk = sdk;
364
+ // OpenAI SDK exposes theme and displayMode directly as properties
365
+ if (sdk.theme) {
366
+ context.hostContext.theme = sdk.theme;
149
367
  }
150
- if (uiConfig.csp.resourceDomains?.length) {
151
- csp.resourceDomains = uiConfig.csp.resourceDomains;
368
+ if (sdk.displayMode) {
369
+ context.hostContext.displayMode = sdk.displayMode;
152
370
  }
153
- if (Object.keys(csp).length > 0) {
154
- meta["ui/csp"] = csp;
371
+ // Note: OpenAI SDK does not have an onContextChange equivalent
372
+ return Promise.resolve();
373
+ },
374
+ callTool: function(context, name, args) {
375
+ return context.sdk.callTool(name, args);
376
+ },
377
+ sendMessage: function(context, content) {
378
+ if (typeof context.sdk.sendFollowUpMessage === 'function') {
379
+ return context.sdk.sendFollowUpMessage(content);
155
380
  }
381
+ return Promise.reject(new Error('Messages not supported'));
382
+ },
383
+ openLink: function(context, url) {
384
+ window.open(url, '_blank', 'noopener,noreferrer');
385
+ return Promise.resolve();
386
+ },
387
+ requestDisplayMode: function(context, mode) {
388
+ return Promise.resolve();
389
+ },
390
+ requestClose: function(context) {
391
+ return Promise.resolve();
156
392
  }
157
- if (uiConfig.displayMode) {
158
- const mappedMode = DISPLAY_MODE_MAP[uiConfig.displayMode];
159
- if (mappedMode) {
160
- meta["ui/displayMode"] = mappedMode;
393
+ };
394
+ `.trim();
395
+ }
396
+ function generateExtAppsAdapter(trustedOrigins) {
397
+ const originsArray = trustedOrigins.length > 0 ? JSON.stringify(trustedOrigins) : "[]";
398
+ return `
399
+ var ExtAppsAdapter = {
400
+ id: 'ext-apps',
401
+ name: 'ext-apps (SEP-1865)',
402
+ priority: 80,
403
+ capabilities: Object.assign({}, DEFAULT_CAPABILITIES, { canPersistState: true, hasNetworkAccess: true }),
404
+ trustedOrigins: ${originsArray},
405
+ trustedOrigin: null,
406
+ originTrustPending: false,
407
+ pendingRequests: {},
408
+ requestId: 0,
409
+ hostCapabilities: {},
410
+ canHandle: function() {
411
+ if (typeof window === 'undefined') return false;
412
+ if (window.parent === window) return false;
413
+
414
+ // Check for OpenAI SDK - defer to OpenAIAdapter
415
+ if (window.openai && window.openai.canvas) return false;
416
+ if (window.openai && typeof window.openai.callTool === 'function') return false;
417
+
418
+ // Explicit ext-apps marker
419
+ if (window.__mcpPlatform === 'ext-apps') return true;
420
+ if (window.__extAppsInitialized) return true;
421
+
422
+ // Claude MCP Apps mode (2026+) - uses ext-apps protocol
423
+ if (window.__mcpAppsEnabled) return true;
424
+
425
+ // Legacy Claude detection - defer to ClaudeAdapter
426
+ if (window.claude) return false;
427
+ if (window.__claudeArtifact) return false;
428
+ if (window.__mcpPlatform === 'claude') return false;
429
+ if (typeof location !== 'undefined') {
430
+ try {
431
+ var url = new URL(location.href);
432
+ var hostname = url.hostname.toLowerCase();
433
+ var isClaudeHost = hostname === 'claude.ai' || (hostname.length > 10 && hostname.slice(-10) === '.claude.ai');
434
+ var isAnthropicHost = hostname === 'anthropic.com' || (hostname.length > 14 && hostname.slice(-14) === '.anthropic.com');
435
+ if (isClaudeHost || isAnthropicHost) return false;
436
+ } catch (e) {
437
+ // If URL parsing fails, fall through to other checks
438
+ }
161
439
  }
440
+
441
+ // Do NOT default to true for any iframe
442
+ return false;
443
+ },
444
+ initialize: function(context) {
445
+ var self = this;
446
+ context.extApps = this;
447
+
448
+ window.addEventListener('message', function(event) {
449
+ self.handleMessage(context, event);
450
+ });
451
+
452
+ return self.performHandshake(context);
453
+ },
454
+ handleMessage: function(context, event) {
455
+ if (!this.isOriginTrusted(event.origin)) return;
456
+ var data = event.data;
457
+ if (!data || typeof data !== 'object' || data.jsonrpc !== '2.0') return;
458
+
459
+ if ('id' in data && (data.result !== undefined || data.error !== undefined)) {
460
+ var pending = this.pendingRequests[data.id];
461
+ if (pending) {
462
+ clearTimeout(pending.timeout);
463
+ delete this.pendingRequests[data.id];
464
+ if (data.error) {
465
+ pending.reject(new Error(data.error.message + ' (code: ' + data.error.code + ')'));
466
+ } else {
467
+ pending.resolve(data.result);
468
+ }
469
+ }
470
+ return;
471
+ }
472
+
473
+ if ('method' in data && !('id' in data)) {
474
+ this.handleNotification(context, data);
475
+ }
476
+ },
477
+ handleNotification: function(context, notification) {
478
+ var params = notification.params || {};
479
+ switch (notification.method) {
480
+ case 'ui/notifications/tool-input':
481
+ context.toolInput = params.arguments || {};
482
+ window.dispatchEvent(new CustomEvent('tool:input', { detail: { arguments: context.toolInput } }));
483
+ break;
484
+ case 'ui/notifications/tool-input-partial':
485
+ // Streaming: merge partial input with existing
486
+ context.toolInput = Object.assign({}, context.toolInput, params.arguments || {});
487
+ window.dispatchEvent(new CustomEvent('tool:input-partial', { detail: { arguments: context.toolInput } }));
488
+ break;
489
+ case 'ui/notifications/tool-result':
490
+ context.toolOutput = params.content;
491
+ context.structuredContent = params.structuredContent;
492
+ context.notifyToolResult(params.content);
493
+ window.dispatchEvent(new CustomEvent('tool:result', { detail: params }));
494
+ break;
495
+ case 'ui/notifications/host-context-changed':
496
+ Object.assign(context.hostContext, params);
497
+ context.notifyContextChange(params);
498
+ break;
499
+ case 'ui/notifications/cancelled':
500
+ window.dispatchEvent(new CustomEvent('tool:cancelled', { detail: { reason: params.reason } }));
501
+ break;
502
+ }
503
+ },
504
+ isOriginTrusted: function(origin) {
505
+ if (this.trustedOrigins.length > 0) {
506
+ return this.trustedOrigins.indexOf(origin) !== -1;
507
+ }
508
+ // Trust-on-first-use: trust first message origin.
509
+ // SECURITY WARNING: For production, always configure trustedOrigins.
510
+ if (!this.trustedOrigin) {
511
+ // Guard against race condition where multiple messages arrive simultaneously
512
+ if (this.originTrustPending) {
513
+ return false;
514
+ }
515
+ if (window.parent !== window && origin) {
516
+ this.originTrustPending = true;
517
+ this.trustedOrigin = origin;
518
+ this.originTrustPending = false; // Reset after successful trust establishment
519
+ return true;
520
+ }
521
+ return false;
522
+ }
523
+ return this.trustedOrigin === origin;
524
+ },
525
+ sendRequest: function(method, params) {
526
+ var self = this;
527
+ return new Promise(function(resolve, reject) {
528
+ // Security: Require trusted origin before sending requests to prevent message leaks
529
+ if (!self.trustedOrigin && self.trustedOrigins.length === 0) {
530
+ reject(new Error('Cannot send request: no trusted origin established'));
531
+ return;
532
+ }
533
+
534
+ var id = ++self.requestId;
535
+ var timeout = setTimeout(function() {
536
+ delete self.pendingRequests[id];
537
+ reject(new Error('Request ' + method + ' timed out'));
538
+ }, 10000);
539
+
540
+ self.pendingRequests[id] = { resolve: resolve, reject: reject, timeout: timeout };
541
+
542
+ var targetOrigin = self.trustedOrigin || self.trustedOrigins[0];
543
+ window.parent.postMessage({ jsonrpc: '2.0', id: id, method: method, params: params }, targetOrigin);
544
+ });
545
+ },
546
+ performHandshake: function(context) {
547
+ var self = this;
548
+ var params = {
549
+ appInfo: { name: 'FrontMCP Widget', version: '1.0.0' },
550
+ appCapabilities: { tools: { listChanged: false } },
551
+ protocolVersion: '2024-11-05'
552
+ };
553
+
554
+ return this.sendRequest('ui/initialize', params).then(function(result) {
555
+ self.hostCapabilities = result.hostCapabilities || {};
556
+ self.capabilities = Object.assign({}, self.capabilities, {
557
+ canCallTools: Boolean(self.hostCapabilities.serverToolProxy),
558
+ canSendMessages: true,
559
+ canOpenLinks: Boolean(self.hostCapabilities.openLink),
560
+ supportsDisplayModes: true
561
+ });
562
+ if (result.hostContext) {
563
+ Object.assign(context.hostContext, result.hostContext);
564
+ }
565
+ });
566
+ },
567
+ callTool: function(context, name, args) {
568
+ if (!this.hostCapabilities.serverToolProxy) {
569
+ return Promise.reject(new Error('Server tool proxy not supported'));
570
+ }
571
+ return this.sendRequest('ui/callServerTool', { name: name, arguments: args });
572
+ },
573
+ sendMessage: function(context, content) {
574
+ return this.sendRequest('ui/message', { content: content });
575
+ },
576
+ openLink: function(context, url) {
577
+ if (!this.hostCapabilities.openLink) {
578
+ window.open(url, '_blank', 'noopener,noreferrer');
579
+ return Promise.resolve();
580
+ }
581
+ return this.sendRequest('ui/openLink', { url: url });
582
+ },
583
+ requestDisplayMode: function(context, mode) {
584
+ return this.sendRequest('ui/setDisplayMode', { mode: mode });
585
+ },
586
+ requestClose: function(context) {
587
+ return this.sendRequest('ui/close', {});
588
+ },
589
+ // Extended ext-apps methods (full specification)
590
+ updateModelContext: function(context, data, merge) {
591
+ if (!this.hostCapabilities.modelContextUpdate) {
592
+ return Promise.reject(new Error('Model context update not supported'));
593
+ }
594
+ return this.sendRequest('ui/updateModelContext', { context: data, merge: merge !== false });
595
+ },
596
+ log: function(context, level, message, data) {
597
+ if (!this.hostCapabilities.logging) {
598
+ // Fallback to console logging if host doesn't support it
599
+ var logFn = console[level] || console.log;
600
+ logFn('[Widget] ' + message, data);
601
+ return Promise.resolve();
602
+ }
603
+ return this.sendRequest('ui/log', { level: level, message: message, data: data });
604
+ },
605
+ registerTool: function(context, name, description, inputSchema) {
606
+ if (!this.hostCapabilities.widgetTools) {
607
+ return Promise.reject(new Error('Widget tool registration not supported'));
608
+ }
609
+ return this.sendRequest('ui/registerTool', { name: name, description: description, inputSchema: inputSchema });
610
+ },
611
+ unregisterTool: function(context, name) {
612
+ if (!this.hostCapabilities.widgetTools) {
613
+ return Promise.reject(new Error('Widget tool unregistration not supported'));
614
+ }
615
+ return this.sendRequest('ui/unregisterTool', { name: name });
162
616
  }
163
- if (uiConfig.prefersBorder !== void 0) {
164
- meta["ui/prefersBorder"] = uiConfig.prefersBorder;
165
- }
166
- if (uiConfig.sandboxDomain) {
167
- meta["ui/domain"] = uiConfig.sandboxDomain;
617
+ };
618
+ `.trim();
619
+ }
620
+ function generateClaudeAdapter() {
621
+ return `
622
+ var ClaudeAdapter = {
623
+ id: 'claude',
624
+ name: 'Claude (Anthropic)',
625
+ priority: 60,
626
+ capabilities: Object.assign({}, DEFAULT_CAPABILITIES, {
627
+ canCallTools: false,
628
+ canSendMessages: false,
629
+ canOpenLinks: true,
630
+ hasNetworkAccess: false,
631
+ supportsDisplayModes: false
632
+ }),
633
+ canHandle: function() {
634
+ if (typeof window === 'undefined') return false;
635
+
636
+ // If MCP Apps is enabled, let ext-apps adapter handle it
637
+ if (window.__mcpAppsEnabled) return false;
638
+ if (window.__mcpPlatform === 'ext-apps') return false;
639
+ if (window.__extAppsInitialized) return false;
640
+
641
+ // Legacy Claude detection
642
+ if (window.__mcpPlatform === 'claude') return true;
643
+ if (window.claude) return true;
644
+ if (window.__claudeArtifact) return true;
645
+ if (typeof location !== 'undefined') {
646
+ try {
647
+ var url = new URL(location.href);
648
+ var hostname = url.hostname.toLowerCase();
649
+ var isClaudeHost = hostname === 'claude.ai' || (hostname.length > 10 && hostname.slice(-10) === '.claude.ai');
650
+ var isAnthropicHost = hostname === 'anthropic.com' || (hostname.length > 14 && hostname.slice(-14) === '.anthropic.com');
651
+ if (isClaudeHost || isAnthropicHost) return true;
652
+ } catch (e) {
653
+ // If URL parsing fails, fall through
654
+ }
655
+ }
656
+ return false;
657
+ },
658
+ initialize: function(context) {
659
+ return Promise.resolve();
660
+ },
661
+ callTool: function() {
662
+ return Promise.reject(new Error('Tool calls not supported in Claude'));
663
+ },
664
+ sendMessage: function() {
665
+ return Promise.reject(new Error('Messages not supported in Claude'));
666
+ },
667
+ openLink: function(context, url) {
668
+ window.open(url, '_blank', 'noopener,noreferrer');
669
+ return Promise.resolve();
670
+ },
671
+ requestDisplayMode: function() {
672
+ return Promise.resolve();
673
+ },
674
+ requestClose: function() {
675
+ return Promise.resolve();
168
676
  }
169
- return meta;
677
+ };
678
+ `.trim();
170
679
  }
171
- function buildExtAppsMeta(meta, uiConfig) {
172
- meta["ui/mimeType"] = "text/html+mcp";
173
- if (uiConfig.csp) {
174
- const csp = {};
175
- if (uiConfig.csp.connectDomains?.length) {
176
- csp.connectDomains = uiConfig.csp.connectDomains;
680
+ function generateGeminiAdapter() {
681
+ return `
682
+ var GeminiAdapter = {
683
+ id: 'gemini',
684
+ name: 'Google Gemini',
685
+ priority: 40,
686
+ capabilities: Object.assign({}, DEFAULT_CAPABILITIES, {
687
+ canOpenLinks: true,
688
+ hasNetworkAccess: true
689
+ }),
690
+ canHandle: function() {
691
+ if (typeof window === 'undefined') return false;
692
+ if (window.__mcpPlatform === 'gemini') return true;
693
+ if (window.gemini) return true;
694
+ if (typeof location !== 'undefined') {
695
+ var href = location.href;
696
+ if (href.indexOf('gemini.google.com') !== -1 || href.indexOf('bard.google.com') !== -1) return true;
177
697
  }
178
- if (uiConfig.csp.resourceDomains?.length) {
179
- csp.resourceDomains = uiConfig.csp.resourceDomains;
698
+ return false;
699
+ },
700
+ initialize: function(context) {
701
+ if (window.gemini && window.gemini.ui && window.gemini.ui.getTheme) {
702
+ context.hostContext.theme = window.gemini.ui.getTheme() === 'dark' ? 'dark' : 'light';
180
703
  }
181
- if (Object.keys(csp).length > 0) {
182
- meta["ui/csp"] = csp;
704
+ return Promise.resolve();
705
+ },
706
+ callTool: function() {
707
+ return Promise.reject(new Error('Tool calls not supported in Gemini'));
708
+ },
709
+ sendMessage: function(context, content) {
710
+ if (window.gemini && window.gemini.ui && window.gemini.ui.sendMessage) {
711
+ return window.gemini.ui.sendMessage(content);
183
712
  }
184
- }
185
- if (uiConfig.displayMode) {
186
- const mappedMode = DISPLAY_MODE_MAP[uiConfig.displayMode];
187
- if (mappedMode) {
188
- meta["ui/displayMode"] = mappedMode;
713
+ return Promise.reject(new Error('Messages not supported in Gemini'));
714
+ },
715
+ openLink: function(context, url) {
716
+ if (window.gemini && window.gemini.ui && window.gemini.ui.openLink) {
717
+ return window.gemini.ui.openLink(url);
189
718
  }
719
+ window.open(url, '_blank', 'noopener,noreferrer');
720
+ return Promise.resolve();
721
+ },
722
+ requestDisplayMode: function() {
723
+ return Promise.resolve();
724
+ },
725
+ requestClose: function() {
726
+ return Promise.resolve();
190
727
  }
191
- if (uiConfig.prefersBorder !== void 0) {
192
- meta["ui/prefersBorder"] = uiConfig.prefersBorder;
728
+ };
729
+ `.trim();
730
+ }
731
+ function generateGenericAdapter() {
732
+ return `
733
+ var GenericAdapter = {
734
+ id: 'generic',
735
+ name: 'Generic Web',
736
+ priority: 0,
737
+ capabilities: Object.assign({}, DEFAULT_CAPABILITIES, {
738
+ canOpenLinks: true,
739
+ hasNetworkAccess: true
740
+ }),
741
+ canHandle: function() {
742
+ return typeof window !== 'undefined';
743
+ },
744
+ initialize: function(context) {
745
+ return Promise.resolve();
746
+ },
747
+ callTool: function() {
748
+ return Promise.reject(new Error('Tool calls not supported'));
749
+ },
750
+ sendMessage: function() {
751
+ return Promise.reject(new Error('Messages not supported'));
752
+ },
753
+ openLink: function(context, url) {
754
+ window.open(url, '_blank', 'noopener,noreferrer');
755
+ return Promise.resolve();
756
+ },
757
+ requestDisplayMode: function() {
758
+ return Promise.resolve();
759
+ },
760
+ requestClose: function() {
761
+ return Promise.resolve();
193
762
  }
194
- if (uiConfig.sandboxDomain) {
195
- meta["ui/domain"] = uiConfig.sandboxDomain;
763
+ };
764
+ `.trim();
765
+ }
766
+ function generatePlatformDetection(adapters) {
767
+ const adapterVars = adapters.map((a) => {
768
+ switch (a) {
769
+ case "openai":
770
+ return "OpenAIAdapter";
771
+ case "ext-apps":
772
+ return "ExtAppsAdapter";
773
+ case "claude":
774
+ return "ClaudeAdapter";
775
+ case "gemini":
776
+ return "GeminiAdapter";
777
+ case "generic":
778
+ return "GenericAdapter";
779
+ default:
780
+ return "";
781
+ }
782
+ }).filter(Boolean);
783
+ return `
784
+ var ADAPTERS = [${adapterVars.join(", ")}].sort(function(a, b) { return b.priority - a.priority; });
785
+
786
+ function detectPlatform() {
787
+ for (var i = 0; i < ADAPTERS.length; i++) {
788
+ if (ADAPTERS[i].canHandle()) {
789
+ log('Detected platform: ' + ADAPTERS[i].id);
790
+ return ADAPTERS[i];
791
+ }
196
792
  }
197
- return meta;
793
+ log('No platform detected, using generic');
794
+ return GenericAdapter;
198
795
  }
199
- function buildToolDiscoveryMeta(options) {
200
- const { uiConfig, platformType, staticWidgetUri } = options;
201
- const meta = {};
202
- switch (platformType) {
203
- case "openai":
204
- meta["openai/outputTemplate"] = staticWidgetUri;
205
- meta["openai/resultCanProduceWidget"] = true;
206
- if (uiConfig.widgetAccessible) {
207
- meta["openai/widgetAccessible"] = true;
208
- }
209
- if (uiConfig.csp) {
210
- meta["openai/widgetCSP"] = buildOpenAICSP(uiConfig.csp);
211
- }
212
- if (uiConfig.displayMode) {
213
- meta["openai/displayMode"] = uiConfig.displayMode;
796
+ `.trim();
797
+ }
798
+ function generateBridgeClass() {
799
+ return `
800
+ function FrontMcpBridge() {
801
+ this._adapter = null;
802
+ this._initialized = false;
803
+ this._context = {
804
+ hostContext: {
805
+ theme: detectTheme(),
806
+ displayMode: 'inline',
807
+ locale: detectLocale(),
808
+ userAgent: detectUserAgent(),
809
+ safeArea: DEFAULT_SAFE_AREA,
810
+ viewport: detectViewport()
811
+ },
812
+ toolInput: {},
813
+ toolOutput: undefined,
814
+ structuredContent: undefined,
815
+ widgetState: {},
816
+ contextListeners: [],
817
+ toolResultListeners: [],
818
+ notifyContextChange: function(changes) {
819
+ Object.assign(this.hostContext, changes);
820
+ for (var i = 0; i < this.contextListeners.length; i++) {
821
+ try { this.contextListeners[i](changes); } catch(e) {}
214
822
  }
215
- if (uiConfig.widgetDescription) {
216
- meta["openai/widgetDescription"] = uiConfig.widgetDescription;
823
+ },
824
+ notifyToolResult: function(result) {
825
+ this.toolOutput = result;
826
+ for (var i = 0; i < this.toolResultListeners.length; i++) {
827
+ try { this.toolResultListeners[i](result); } catch(e) {}
217
828
  }
218
- break;
219
- case "generic-mcp":
220
- meta["openai/outputTemplate"] = staticWidgetUri;
221
- meta["openai/resultCanProduceWidget"] = true;
222
- if (uiConfig.widgetAccessible) {
223
- meta["openai/widgetAccessible"] = true;
829
+ }
830
+ };
831
+
832
+ var injected = readInjectedData();
833
+ this._context.toolInput = injected.toolInput;
834
+ this._context.toolOutput = injected.toolOutput;
835
+ this._context.structuredContent = injected.structuredContent;
836
+
837
+ this._loadWidgetState();
838
+ }
839
+
840
+ FrontMcpBridge.prototype._loadWidgetState = function() {
841
+ try {
842
+ var key = 'frontmcp:widget:' + (window.__mcpToolName || 'unknown');
843
+ var stored = localStorage.getItem(key);
844
+ if (stored) this._context.widgetState = JSON.parse(stored);
845
+ } catch(e) {}
846
+ };
847
+
848
+ FrontMcpBridge.prototype._saveWidgetState = function() {
849
+ try {
850
+ var key = 'frontmcp:widget:' + (window.__mcpToolName || 'unknown');
851
+ localStorage.setItem(key, JSON.stringify(this._context.widgetState));
852
+ } catch(e) {}
853
+ };
854
+
855
+ FrontMcpBridge.prototype.initialize = function() {
856
+ if (this._initialized) return Promise.resolve();
857
+ var self = this;
858
+ this._adapter = detectPlatform();
859
+ return this._adapter.initialize(this._context).then(function() {
860
+ self._initialized = true;
861
+ // Set up the data-tool-call click handler after initialization
862
+ self._setupDataToolCallHandler();
863
+ });
864
+ };
865
+
866
+ Object.defineProperty(FrontMcpBridge.prototype, 'initialized', { get: function() { return this._initialized; } });
867
+ Object.defineProperty(FrontMcpBridge.prototype, 'adapterId', { get: function() { return this._adapter ? this._adapter.id : undefined; } });
868
+ Object.defineProperty(FrontMcpBridge.prototype, 'capabilities', { get: function() { return this._adapter ? this._adapter.capabilities : DEFAULT_CAPABILITIES; } });
869
+
870
+ FrontMcpBridge.prototype.getTheme = function() { return this._context.hostContext.theme; };
871
+ FrontMcpBridge.prototype.getDisplayMode = function() { return this._context.hostContext.displayMode; };
872
+ FrontMcpBridge.prototype.getToolInput = function() { return this._context.toolInput; };
873
+ FrontMcpBridge.prototype.getToolOutput = function() { return this._context.toolOutput; };
874
+ FrontMcpBridge.prototype.getStructuredContent = function() { return this._context.structuredContent; };
875
+ FrontMcpBridge.prototype.getWidgetState = function() { return this._context.widgetState; };
876
+ FrontMcpBridge.prototype.getHostContext = function() { return Object.assign({}, this._context.hostContext); };
877
+ FrontMcpBridge.prototype.hasCapability = function(cap) { return this._adapter && this._adapter.capabilities[cap] === true; };
878
+
879
+ // Get tool response metadata (platform-agnostic)
880
+ // Used by inline mode widgets to detect when ui/html arrives
881
+ FrontMcpBridge.prototype.getToolResponseMetadata = function() {
882
+ // OpenAI injects toolResponseMetadata for widget-producing tools
883
+ if (typeof window !== 'undefined' && window.openai && window.openai.toolResponseMetadata) {
884
+ return window.openai.toolResponseMetadata;
885
+ }
886
+ // Claude (future support)
887
+ if (typeof window !== 'undefined' && window.claude && window.claude.toolResponseMetadata) {
888
+ return window.claude.toolResponseMetadata;
889
+ }
890
+ // FrontMCP direct injection (for testing/ext-apps)
891
+ if (typeof window !== 'undefined' && window.__mcpToolResponseMetadata) {
892
+ return window.__mcpToolResponseMetadata;
893
+ }
894
+ return null;
895
+ };
896
+
897
+ // Subscribe to tool response metadata changes (for inline mode injection)
898
+ FrontMcpBridge.prototype.onToolResponseMetadata = function(callback) {
899
+ var self = this;
900
+ var called = false;
901
+
902
+ // Check if already available
903
+ var existing = self.getToolResponseMetadata();
904
+ if (existing) {
905
+ called = true;
906
+ callback(existing);
907
+ }
908
+
909
+ // Set up property interceptors for OpenAI
910
+ if (typeof window !== 'undefined') {
911
+ // OpenAI: Intercept toolResponseMetadata assignment
912
+ if (!window.__frontmcpMetadataIntercepted) {
913
+ window.__frontmcpMetadataIntercepted = true;
914
+ window.__frontmcpMetadataCallbacks = [];
915
+
916
+ // Create openai object if it doesn't exist
917
+ if (!window.openai) window.openai = {};
918
+
919
+ var originalMetadata = window.openai.toolResponseMetadata;
920
+ Object.defineProperty(window.openai, 'toolResponseMetadata', {
921
+ get: function() { return originalMetadata; },
922
+ set: function(val) {
923
+ originalMetadata = val;
924
+ log('toolResponseMetadata set, notifying ' + window.__frontmcpMetadataCallbacks.length + ' listeners');
925
+ for (var i = 0; i < window.__frontmcpMetadataCallbacks.length; i++) {
926
+ try { window.__frontmcpMetadataCallbacks[i](val); } catch(e) {}
927
+ }
928
+ },
929
+ configurable: true
930
+ });
931
+ }
932
+
933
+ // Register callback wrapper (store reference for unsubscribe)
934
+ var wrapper = function(metadata) {
935
+ if (!called) {
936
+ called = true;
937
+ callback(metadata);
224
938
  }
225
- if (uiConfig.csp) {
226
- meta["openai/widgetCSP"] = buildOpenAICSP(uiConfig.csp);
939
+ };
940
+ window.__frontmcpMetadataCallbacks.push(wrapper);
941
+
942
+ // Return unsubscribe function that removes the wrapper (not the original callback)
943
+ return function() {
944
+ if (window.__frontmcpMetadataCallbacks) {
945
+ var idx = window.__frontmcpMetadataCallbacks.indexOf(wrapper);
946
+ if (idx !== -1) window.__frontmcpMetadataCallbacks.splice(idx, 1);
227
947
  }
228
- break;
229
- case "ext-apps":
230
- meta["ui/resourceUri"] = staticWidgetUri;
231
- meta["ui/mimeType"] = "text/html+mcp";
232
- if (uiConfig.csp) {
233
- const csp = {};
234
- if (uiConfig.csp.connectDomains?.length) {
235
- csp.connectDomains = uiConfig.csp.connectDomains;
948
+ };
949
+ }
950
+
951
+ // Return no-op unsubscribe for non-window environments
952
+ return function() {};
953
+ };
954
+
955
+ FrontMcpBridge.prototype.callTool = function(name, args) {
956
+ // Priority 1: Direct OpenAI SDK call (most reliable in OpenAI iframe)
957
+ // This bypasses adapter abstraction for maximum compatibility
958
+ if (typeof window !== 'undefined' && window.openai && typeof window.openai.callTool === 'function') {
959
+ log('callTool: Using OpenAI SDK directly');
960
+ return window.openai.callTool(name, args);
961
+ }
962
+
963
+ // Priority 2: Use adapter (if initialized and supports tool calls)
964
+ if (this._adapter && this._adapter.capabilities && this._adapter.capabilities.canCallTools) {
965
+ log('callTool: Using adapter ' + this._adapter.id);
966
+ return this._adapter.callTool(this._context, name, args);
967
+ }
968
+
969
+ // Not initialized or no tool support
970
+ if (!this._adapter) {
971
+ return Promise.reject(new Error('Bridge not initialized. Wait for bridge:ready event.'));
972
+ }
973
+ return Promise.reject(new Error('Tool calls not supported on this platform (' + this._adapter.id + ')'));
974
+ };
975
+
976
+ FrontMcpBridge.prototype.sendMessage = function(content) {
977
+ if (!this._adapter) return Promise.reject(new Error('Not initialized'));
978
+ return this._adapter.sendMessage(this._context, content);
979
+ };
980
+
981
+ FrontMcpBridge.prototype.openLink = function(url) {
982
+ if (!this._adapter) return Promise.reject(new Error('Not initialized'));
983
+ return this._adapter.openLink(this._context, url);
984
+ };
985
+
986
+ FrontMcpBridge.prototype.requestDisplayMode = function(mode) {
987
+ if (!this._adapter) return Promise.reject(new Error('Not initialized'));
988
+ var self = this;
989
+ return this._adapter.requestDisplayMode(this._context, mode).then(function() {
990
+ self._context.hostContext.displayMode = mode;
991
+ });
992
+ };
993
+
994
+ FrontMcpBridge.prototype.requestClose = function() {
995
+ if (!this._adapter) return Promise.reject(new Error('Not initialized'));
996
+ return this._adapter.requestClose(this._context);
997
+ };
998
+
999
+ // Extended ext-apps methods (full specification)
1000
+ FrontMcpBridge.prototype.updateModelContext = function(context, merge) {
1001
+ if (!this._adapter) return Promise.reject(new Error('Not initialized'));
1002
+ if (!this._adapter.updateModelContext) {
1003
+ return Promise.reject(new Error('updateModelContext not supported on this platform'));
1004
+ }
1005
+ return this._adapter.updateModelContext(this._context, context, merge);
1006
+ };
1007
+
1008
+ FrontMcpBridge.prototype.log = function(level, message, data) {
1009
+ if (!this._adapter) return Promise.reject(new Error('Not initialized'));
1010
+ if (!this._adapter.log) {
1011
+ // Fallback to console
1012
+ var logFn = console[level] || console.log;
1013
+ logFn('[Widget] ' + message, data);
1014
+ return Promise.resolve();
1015
+ }
1016
+ return this._adapter.log(this._context, level, message, data);
1017
+ };
1018
+
1019
+ FrontMcpBridge.prototype.registerTool = function(name, description, inputSchema) {
1020
+ if (!this._adapter) return Promise.reject(new Error('Not initialized'));
1021
+ if (!this._adapter.registerTool) {
1022
+ return Promise.reject(new Error('registerTool not supported on this platform'));
1023
+ }
1024
+ return this._adapter.registerTool(this._context, name, description, inputSchema);
1025
+ };
1026
+
1027
+ FrontMcpBridge.prototype.unregisterTool = function(name) {
1028
+ if (!this._adapter) return Promise.reject(new Error('Not initialized'));
1029
+ if (!this._adapter.unregisterTool) {
1030
+ return Promise.reject(new Error('unregisterTool not supported on this platform'));
1031
+ }
1032
+ return this._adapter.unregisterTool(this._context, name);
1033
+ };
1034
+
1035
+ FrontMcpBridge.prototype.setWidgetState = function(state) {
1036
+ Object.assign(this._context.widgetState, state);
1037
+ this._saveWidgetState();
1038
+ };
1039
+
1040
+ FrontMcpBridge.prototype.onContextChange = function(callback) {
1041
+ var listeners = this._context.contextListeners;
1042
+ listeners.push(callback);
1043
+ return function() {
1044
+ var idx = listeners.indexOf(callback);
1045
+ if (idx !== -1) listeners.splice(idx, 1);
1046
+ };
1047
+ };
1048
+
1049
+ FrontMcpBridge.prototype.onToolResult = function(callback) {
1050
+ var listeners = this._context.toolResultListeners;
1051
+ listeners.push(callback);
1052
+ return function() {
1053
+ var idx = listeners.indexOf(callback);
1054
+ if (idx !== -1) listeners.splice(idx, 1);
1055
+ };
1056
+ };
1057
+
1058
+ // ==================== data-tool-call Click Handler ====================
1059
+
1060
+ FrontMcpBridge.prototype._setupDataToolCallHandler = function() {
1061
+ var self = this;
1062
+
1063
+ document.addEventListener('click', function(e) {
1064
+ // Find the closest element with data-tool-call attribute
1065
+ var target = e.target;
1066
+ while (target && target !== document) {
1067
+ if (target.hasAttribute && target.hasAttribute('data-tool-call')) {
1068
+ var toolName = target.getAttribute('data-tool-call');
1069
+ var argsAttr = target.getAttribute('data-tool-args');
1070
+ var args = {};
1071
+
1072
+ try {
1073
+ if (argsAttr) {
1074
+ args = JSON.parse(argsAttr);
1075
+ }
1076
+ } catch (parseErr) {
1077
+ console.error('[frontmcp] Failed to parse data-tool-args:', parseErr);
236
1078
  }
237
- if (uiConfig.csp.resourceDomains?.length) {
238
- csp.resourceDomains = uiConfig.csp.resourceDomains;
1079
+
1080
+ log('data-tool-call clicked: ' + toolName);
1081
+
1082
+ // Show loading state - save original content first
1083
+ var originalContent = target.innerHTML;
1084
+ var originalDisabled = target.disabled;
1085
+ target.disabled = true;
1086
+ target.classList.add('opacity-50', 'cursor-not-allowed');
1087
+
1088
+ // Add spinner for buttons
1089
+ var spinner = '<svg class="animate-spin -ml-1 mr-2 h-4 w-4 inline" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path></svg>';
1090
+ if (target.tagName === 'BUTTON') {
1091
+ target.innerHTML = spinner + 'Loading...';
239
1092
  }
240
- if (Object.keys(csp).length > 0) {
241
- meta["ui/csp"] = csp;
1093
+
1094
+ // Helper to reset button state
1095
+ function resetButton() {
1096
+ target.innerHTML = originalContent;
1097
+ target.disabled = originalDisabled;
1098
+ target.classList.remove('opacity-50', 'cursor-not-allowed');
242
1099
  }
243
- }
244
- if (uiConfig.displayMode) {
245
- const mappedMode = DISPLAY_MODE_MAP[uiConfig.displayMode];
246
- if (mappedMode) {
247
- meta["ui/displayMode"] = mappedMode;
1100
+
1101
+ // Determine how to call the tool
1102
+ var toolCallPromise;
1103
+
1104
+ // Priority 1: Direct OpenAI SDK call (bypasses adapter abstraction)
1105
+ if (typeof window !== 'undefined' && window.openai && typeof window.openai.callTool === 'function') {
1106
+ log('Using OpenAI SDK directly for tool call');
1107
+ toolCallPromise = window.openai.callTool(toolName, args);
1108
+ }
1109
+ // Priority 2: Use adapter (if it supports tool calls)
1110
+ else if (self.hasCapability('canCallTools')) {
1111
+ log('Using adapter for tool call');
1112
+ toolCallPromise = self.callTool(toolName, args);
1113
+ }
1114
+ // No tool call capability
1115
+ else {
1116
+ console.error('[frontmcp] Tool calls not supported on this platform (' + self.adapterId + ')');
1117
+ resetButton();
1118
+ target.dispatchEvent(new CustomEvent('tool:error', {
1119
+ detail: { name: toolName, args: args, error: 'Tool calls not supported on this platform' },
1120
+ bubbles: true
1121
+ }));
1122
+ e.preventDefault();
1123
+ return;
248
1124
  }
1125
+
1126
+ // Handle the tool call result
1127
+ toolCallPromise.then(function(result) {
1128
+ log('Tool call succeeded: ' + toolName);
1129
+ resetButton();
1130
+
1131
+ // Update bridge state to trigger widget re-render
1132
+ // React isn't hydrated in OpenAI iframe, so useState doesn't work
1133
+ // Instead, we use the bridge's reactive state system
1134
+ if (result && window.__frontmcp && window.__frontmcp.bridge && typeof window.__frontmcp.bridge.setWidgetState === 'function') {
1135
+ var newData = result.structuredContent || result;
1136
+ log('Updating bridge state with new data');
1137
+ window.__frontmcp.bridge.setWidgetState(newData);
1138
+ }
1139
+
1140
+ // Dispatch success event
1141
+ target.dispatchEvent(new CustomEvent('tool:success', {
1142
+ detail: { name: toolName, args: args, result: result },
1143
+ bubbles: true
1144
+ }));
1145
+ }).catch(function(err) {
1146
+ console.error('[frontmcp] Tool call failed: ' + toolName, err);
1147
+ resetButton();
1148
+ // Dispatch error event
1149
+ target.dispatchEvent(new CustomEvent('tool:error', {
1150
+ detail: { name: toolName, args: args, error: err.message || err },
1151
+ bubbles: true
1152
+ }));
1153
+ });
1154
+
1155
+ // Prevent default behavior (e.g., form submission)
1156
+ e.preventDefault();
1157
+ return;
249
1158
  }
250
- if (uiConfig.prefersBorder !== void 0) {
251
- meta["ui/prefersBorder"] = uiConfig.prefersBorder;
1159
+ target = target.parentElement;
1160
+ }
1161
+ }, true); // Use capture phase to handle before React handlers
1162
+ };
1163
+ `.trim();
1164
+ }
1165
+ var MAX_MINIFY_CODE_LENGTH = 5e5;
1166
+ function minifyJS(code) {
1167
+ if (code.length > MAX_MINIFY_CODE_LENGTH) {
1168
+ return code;
1169
+ }
1170
+ return code.replace(/\/\*[\s\S]*?\*\//g, "").replace(/(^|[^:])\/\/.*$/gm, "$1").replace(/\s+/g, " ").replace(/\s*([{};,:()[\]])\s*/g, "$1").replace(/;\}/g, "}").trim();
1171
+ }
1172
+ function generatePlatformBundle(platform, options = {}) {
1173
+ const platformAdapters = {
1174
+ chatgpt: ["openai", "generic"],
1175
+ claude: ["claude", "generic"],
1176
+ gemini: ["gemini", "generic"],
1177
+ universal: ["openai", "ext-apps", "claude", "gemini", "generic"]
1178
+ };
1179
+ return generateBridgeIIFE({
1180
+ ...options,
1181
+ adapters: platformAdapters[platform]
1182
+ });
1183
+ }
1184
+ var UNIVERSAL_BRIDGE_SCRIPT = generateBridgeIIFE();
1185
+ var BRIDGE_SCRIPT_TAGS = {
1186
+ universal: `<script>${UNIVERSAL_BRIDGE_SCRIPT}</script>`,
1187
+ chatgpt: `<script>${generatePlatformBundle("chatgpt")}</script>`,
1188
+ claude: `<script>${generatePlatformBundle("claude")}</script>`,
1189
+ gemini: `<script>${generatePlatformBundle("gemini")}</script>`
1190
+ };
1191
+
1192
+ // libs/uipack/src/shell/custom-shell-types.ts
1193
+ var SHELL_PLACEHOLDER_NAMES = ["CSP", "DATA", "BRIDGE", "CONTENT", "TITLE"];
1194
+ var SHELL_PLACEHOLDERS = {
1195
+ CSP: "{{CSP}}",
1196
+ DATA: "{{DATA}}",
1197
+ BRIDGE: "{{BRIDGE}}",
1198
+ CONTENT: "{{CONTENT}}",
1199
+ TITLE: "{{TITLE}}"
1200
+ };
1201
+ var REQUIRED_PLACEHOLDERS = ["CONTENT"];
1202
+ var OPTIONAL_PLACEHOLDERS = ["CSP", "DATA", "BRIDGE", "TITLE"];
1203
+
1204
+ // libs/uipack/src/shell/custom-shell-validator.ts
1205
+ function validateShellTemplate(template) {
1206
+ const found = {};
1207
+ for (const name of SHELL_PLACEHOLDER_NAMES) {
1208
+ found[name] = template.includes(SHELL_PLACEHOLDERS[name]);
1209
+ }
1210
+ const missingRequired = REQUIRED_PLACEHOLDERS.filter((name) => !found[name]);
1211
+ const missingOptional = OPTIONAL_PLACEHOLDERS.filter((name) => !found[name]);
1212
+ return {
1213
+ valid: missingRequired.length === 0,
1214
+ found,
1215
+ missingRequired,
1216
+ missingOptional
1217
+ };
1218
+ }
1219
+
1220
+ // libs/uipack/src/shell/custom-shell-applier.ts
1221
+ function applyShellTemplate(template, values) {
1222
+ let result = template;
1223
+ result = result.replaceAll(SHELL_PLACEHOLDERS.CSP, values.csp);
1224
+ result = result.replaceAll(SHELL_PLACEHOLDERS.DATA, values.data);
1225
+ result = result.replaceAll(SHELL_PLACEHOLDERS.BRIDGE, values.bridge);
1226
+ result = result.replaceAll(SHELL_PLACEHOLDERS.CONTENT, values.content);
1227
+ result = result.replaceAll(SHELL_PLACEHOLDERS.TITLE, values.title);
1228
+ return result;
1229
+ }
1230
+
1231
+ // libs/uipack/src/shell/builder.ts
1232
+ function buildShell(content, config) {
1233
+ const { toolName, csp, withShell = true, input, output, structuredContent, includeBridge = true, title } = config;
1234
+ const { customShell } = config;
1235
+ const dataScript = buildDataInjectionScript({ toolName, input, output, structuredContent });
1236
+ if (!withShell) {
1237
+ const html2 = `${dataScript}
1238
+ ${content}`;
1239
+ return {
1240
+ html: html2,
1241
+ hash: simpleHash(html2),
1242
+ size: Buffer.byteLength(html2, "utf-8")
1243
+ };
1244
+ }
1245
+ if (customShell) {
1246
+ return buildCustomShell(content, customShell, {
1247
+ csp,
1248
+ toolName,
1249
+ input,
1250
+ output,
1251
+ structuredContent,
1252
+ includeBridge,
1253
+ title
1254
+ });
1255
+ }
1256
+ const headParts = [
1257
+ '<meta charset="UTF-8">',
1258
+ '<meta name="viewport" content="width=device-width, initial-scale=1.0">'
1259
+ ];
1260
+ if (title) {
1261
+ headParts.push(`<title>${escapeHtmlForTag(title)}</title>`);
1262
+ }
1263
+ headParts.push(buildCSPMetaTag(csp));
1264
+ headParts.push(dataScript);
1265
+ if (includeBridge) {
1266
+ const bridgeScript = generateBridgeIIFE({ minify: true });
1267
+ headParts.push(`<script>${bridgeScript}</script>`);
1268
+ }
1269
+ const html = `<!DOCTYPE html>
1270
+ <html lang="en">
1271
+ <head>
1272
+ ${headParts.map((p) => ` ${p}`).join("\n")}
1273
+ </head>
1274
+ <body>
1275
+ ${content}
1276
+ </body>
1277
+ </html>`;
1278
+ return {
1279
+ html,
1280
+ hash: simpleHash(html),
1281
+ size: Buffer.byteLength(html, "utf-8")
1282
+ };
1283
+ }
1284
+ function buildCustomShell(content, customShell, ctx) {
1285
+ let template;
1286
+ if (typeof customShell === "string") {
1287
+ const validation = validateShellTemplate(customShell);
1288
+ if (!validation.valid) {
1289
+ throw new Error(
1290
+ `Custom shell template is missing required placeholder(s): ${validation.missingRequired.map((n) => `{{${n}}}`).join(", ")}`
1291
+ );
1292
+ }
1293
+ template = customShell;
1294
+ } else {
1295
+ template = customShell.template;
1296
+ }
1297
+ const cspTag = buildCSPMetaTag(ctx.csp);
1298
+ const dataScript = buildDataInjectionScript({
1299
+ toolName: ctx.toolName,
1300
+ input: ctx.input,
1301
+ output: ctx.output,
1302
+ structuredContent: ctx.structuredContent
1303
+ });
1304
+ let bridgeHtml = "";
1305
+ if (ctx.includeBridge) {
1306
+ const bridgeScript = generateBridgeIIFE({ minify: true });
1307
+ bridgeHtml = `<script>${bridgeScript}</script>`;
1308
+ }
1309
+ const html = applyShellTemplate(template, {
1310
+ csp: cspTag,
1311
+ data: dataScript,
1312
+ bridge: bridgeHtml,
1313
+ content,
1314
+ title: ctx.title ? escapeHtmlForTag(ctx.title) : ""
1315
+ });
1316
+ return {
1317
+ html,
1318
+ hash: simpleHash(html),
1319
+ size: Buffer.byteLength(html, "utf-8")
1320
+ };
1321
+ }
1322
+ function simpleHash(str) {
1323
+ let hash = 0;
1324
+ for (let i = 0; i < str.length; i++) {
1325
+ const char = str.charCodeAt(i);
1326
+ hash = (hash << 5) - hash + char | 0;
1327
+ }
1328
+ return Math.abs(hash).toString(16).padStart(8, "0");
1329
+ }
1330
+ function escapeHtmlForTag(str) {
1331
+ return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
1332
+ }
1333
+
1334
+ // libs/uipack/src/resolver/cdn-registry.ts
1335
+ var DEFAULT_CDN_REGISTRY = {
1336
+ react: {
1337
+ packageName: "react",
1338
+ defaultVersion: "19.2.4",
1339
+ providers: {
1340
+ cloudflare: {
1341
+ url: "https://cdnjs.cloudflare.com/ajax/libs/react/19.2.4/umd/react.production.min.js",
1342
+ global: "React",
1343
+ crossorigin: "anonymous"
1344
+ },
1345
+ jsdelivr: {
1346
+ url: "https://cdn.jsdelivr.net/npm/react@19.2.4/umd/react.production.min.js",
1347
+ global: "React",
1348
+ crossorigin: "anonymous"
1349
+ },
1350
+ unpkg: {
1351
+ url: "https://unpkg.com/react@19.2.4/umd/react.production.min.js",
1352
+ global: "React",
1353
+ crossorigin: "anonymous"
1354
+ },
1355
+ "esm.sh": {
1356
+ url: "https://esm.sh/react@19.2.4",
1357
+ esm: true,
1358
+ crossorigin: "anonymous"
252
1359
  }
253
- if (uiConfig.sandboxDomain) {
254
- meta["ui/domain"] = uiConfig.sandboxDomain;
1360
+ },
1361
+ preferredProviders: ["esm.sh", "cloudflare", "jsdelivr", "unpkg"],
1362
+ metadata: {
1363
+ description: "A JavaScript library for building user interfaces",
1364
+ homepage: "https://react.dev",
1365
+ license: "MIT"
1366
+ }
1367
+ },
1368
+ "react-dom": {
1369
+ packageName: "react-dom",
1370
+ defaultVersion: "19.2.4",
1371
+ providers: {
1372
+ cloudflare: {
1373
+ url: "https://cdnjs.cloudflare.com/ajax/libs/react-dom/19.2.4/umd/react-dom.production.min.js",
1374
+ global: "ReactDOM",
1375
+ crossorigin: "anonymous",
1376
+ peerDependencies: ["react"]
1377
+ },
1378
+ jsdelivr: {
1379
+ url: "https://cdn.jsdelivr.net/npm/react-dom@19.2.4/umd/react-dom.production.min.js",
1380
+ global: "ReactDOM",
1381
+ crossorigin: "anonymous",
1382
+ peerDependencies: ["react"]
1383
+ },
1384
+ unpkg: {
1385
+ url: "https://unpkg.com/react-dom@19.2.4/umd/react-dom.production.min.js",
1386
+ global: "ReactDOM",
1387
+ crossorigin: "anonymous",
1388
+ peerDependencies: ["react"]
1389
+ },
1390
+ "esm.sh": {
1391
+ url: "https://esm.sh/react-dom@19.2.4",
1392
+ esm: true,
1393
+ crossorigin: "anonymous",
1394
+ peerDependencies: ["react"]
255
1395
  }
256
- break;
257
- case "claude":
258
- meta["ui/resourceUri"] = staticWidgetUri;
259
- meta["ui/mimeType"] = "text/html+mcp";
260
- if (uiConfig.displayMode) {
261
- const mappedMode = DISPLAY_MODE_MAP[uiConfig.displayMode];
262
- if (mappedMode) {
263
- meta["ui/displayMode"] = mappedMode;
264
- }
1396
+ },
1397
+ preferredProviders: ["esm.sh", "cloudflare", "jsdelivr", "unpkg"],
1398
+ metadata: {
1399
+ description: "React package for working with the DOM",
1400
+ homepage: "https://react.dev",
1401
+ license: "MIT"
1402
+ }
1403
+ },
1404
+ "chart.js": {
1405
+ packageName: "chart.js",
1406
+ defaultVersion: "4.5.1",
1407
+ providers: {
1408
+ cloudflare: {
1409
+ url: "https://cdnjs.cloudflare.com/ajax/libs/Chart.js/4.5.1/chart.umd.min.js",
1410
+ global: "Chart",
1411
+ crossorigin: "anonymous"
1412
+ },
1413
+ jsdelivr: {
1414
+ url: "https://cdn.jsdelivr.net/npm/chart.js@4.5.1/dist/chart.umd.min.js",
1415
+ global: "Chart",
1416
+ crossorigin: "anonymous"
1417
+ },
1418
+ unpkg: {
1419
+ url: "https://unpkg.com/chart.js@4.5.1/dist/chart.umd.min.js",
1420
+ global: "Chart",
1421
+ crossorigin: "anonymous"
265
1422
  }
266
- if (uiConfig.prefersBorder !== void 0) {
267
- meta["ui/prefersBorder"] = uiConfig.prefersBorder;
1423
+ },
1424
+ preferredProviders: ["esm.sh", "cloudflare", "jsdelivr", "unpkg"],
1425
+ metadata: {
1426
+ description: "Simple yet flexible JavaScript charting library",
1427
+ homepage: "https://www.chartjs.org",
1428
+ license: "MIT"
1429
+ }
1430
+ },
1431
+ d3: {
1432
+ packageName: "d3",
1433
+ defaultVersion: "7.9.0",
1434
+ providers: {
1435
+ cloudflare: {
1436
+ url: "https://cdnjs.cloudflare.com/ajax/libs/d3/7.9.0/d3.min.js",
1437
+ integrity: "sha512-vc58qvvBdrDR4etbxMdlTt4GBQk1qjvyORR2nrsPsFPyrs+/u5c3+1Ct6upOgdZoIl7eq6k3a1UPDSNAQi/32A==",
1438
+ global: "d3",
1439
+ crossorigin: "anonymous"
1440
+ },
1441
+ jsdelivr: {
1442
+ url: "https://cdn.jsdelivr.net/npm/d3@7.9.0/dist/d3.min.js",
1443
+ global: "d3",
1444
+ crossorigin: "anonymous"
1445
+ },
1446
+ unpkg: {
1447
+ url: "https://unpkg.com/d3@7.9.0/dist/d3.min.js",
1448
+ global: "d3",
1449
+ crossorigin: "anonymous"
268
1450
  }
269
- if (uiConfig.csp) {
270
- const csp = {};
271
- if (uiConfig.csp.connectDomains?.length) {
272
- csp.connectDomains = uiConfig.csp.connectDomains;
273
- }
274
- if (uiConfig.csp.resourceDomains?.length) {
275
- csp.resourceDomains = uiConfig.csp.resourceDomains;
276
- }
277
- if (Object.keys(csp).length > 0) {
278
- meta["ui/csp"] = csp;
279
- }
1451
+ },
1452
+ preferredProviders: ["esm.sh", "cloudflare", "jsdelivr", "unpkg"],
1453
+ metadata: {
1454
+ description: "Data-Driven Documents",
1455
+ homepage: "https://d3js.org",
1456
+ license: "ISC"
1457
+ }
1458
+ },
1459
+ lodash: {
1460
+ packageName: "lodash",
1461
+ defaultVersion: "4.17.21",
1462
+ providers: {
1463
+ cloudflare: {
1464
+ url: "https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.21/lodash.min.js",
1465
+ integrity: "sha512-WFN04846sdKMIP5LKNphMaWzU7YpMyCU245etK3g/2ARYbPK9Ub18eG+ljU96qKRCWh+quCY7yefSmlkQw1ANQ==",
1466
+ global: "_",
1467
+ crossorigin: "anonymous"
1468
+ },
1469
+ jsdelivr: {
1470
+ url: "https://cdn.jsdelivr.net/npm/lodash@4.17.21/lodash.min.js",
1471
+ global: "_",
1472
+ crossorigin: "anonymous"
1473
+ },
1474
+ unpkg: {
1475
+ url: "https://unpkg.com/lodash@4.17.21/lodash.min.js",
1476
+ global: "_",
1477
+ crossorigin: "anonymous"
280
1478
  }
281
- break;
282
- // Gemini, IDEs don't need discovery metadata
283
- // They use inline HTML at call time
284
- default:
285
- break;
1479
+ },
1480
+ preferredProviders: ["esm.sh", "cloudflare", "jsdelivr", "unpkg"],
1481
+ metadata: {
1482
+ description: "A modern JavaScript utility library",
1483
+ homepage: "https://lodash.com",
1484
+ license: "MIT"
1485
+ }
286
1486
  }
287
- return meta;
1487
+ };
1488
+ function lookupPackage(packageName, registry = DEFAULT_CDN_REGISTRY) {
1489
+ return registry[packageName];
288
1490
  }
289
1491
 
290
- // libs/uipack/src/adapters/serving-mode.ts
291
- var PLATFORM_CAPABILITIES = {
292
- openai: {
293
- supportsWidgets: true,
294
- useStructuredContent: true,
295
- supportedModes: ["inline", "static", "hybrid", "direct-url", "custom-url"],
296
- defaultMode: "inline"
297
- },
298
- "ext-apps": {
299
- supportsWidgets: true,
300
- useStructuredContent: true,
301
- supportedModes: ["inline", "static", "hybrid", "direct-url", "custom-url"],
302
- defaultMode: "inline"
303
- },
304
- claude: {
305
- supportsWidgets: true,
306
- useStructuredContent: true,
307
- // Claude supports inline only (no resource fetching for static)
308
- supportedModes: ["inline"],
309
- defaultMode: "inline"
310
- },
311
- cursor: {
312
- // Cursor (IDE) - similar to OpenAI, supports widgets
313
- supportsWidgets: true,
314
- useStructuredContent: true,
315
- supportedModes: ["inline", "static", "hybrid", "direct-url", "custom-url"],
316
- defaultMode: "inline"
317
- },
318
- continue: {
319
- // Continue (IDE extension) - basic widget support
320
- supportsWidgets: true,
321
- useStructuredContent: true,
322
- supportedModes: ["inline"],
323
- defaultMode: "inline"
324
- },
325
- cody: {
326
- // Sourcegraph Cody - basic widget support
327
- supportsWidgets: true,
328
- useStructuredContent: true,
329
- supportedModes: ["inline"],
330
- defaultMode: "inline"
331
- },
332
- "generic-mcp": {
333
- // Generic MCP clients - assume widget support
334
- supportsWidgets: true,
335
- useStructuredContent: true,
336
- supportedModes: ["inline", "static"],
337
- defaultMode: "inline"
338
- },
339
- gemini: {
340
- supportsWidgets: false,
341
- useStructuredContent: false,
342
- supportedModes: [],
343
- defaultMode: "inline"
344
- // Not used since supportsWidgets is false
345
- },
346
- unknown: {
347
- // Unknown clients: assume widget support, return ui/html + text/html+mcp
348
- // This allows generic MCP clients to receive and render widget UI
349
- supportsWidgets: true,
350
- useStructuredContent: true,
351
- supportedModes: ["inline"],
352
- defaultMode: "inline"
1492
+ // libs/uipack/src/resolver/import-parser.ts
1493
+ var IMPORT_PATTERNS = {
1494
+ named: /import\s*\{([^}]+)\}\s*from\s*['"]([^'"]+)['"]/g,
1495
+ default: /import\s+(\w+)\s+from\s*['"]([^'"]+)['"]/g,
1496
+ namespace: /import\s*\*\s*as\s+(\w+)\s+from\s*['"]([^'"]+)['"]/g,
1497
+ sideEffect: /import\s*['"]([^'"]+)['"]/g,
1498
+ dynamic: /(?:await\s+)?import\s*\(\s*['"]([^'"]+)['"]\s*\)/g,
1499
+ defaultAndNamed: /import\s+(\w+)\s*,\s*\{([^}]+)\}\s*from\s*['"]([^'"]+)['"]/g,
1500
+ defaultAndNamespace: /import\s+(\w+)\s*,\s*\*\s*as\s+(\w+)\s+from\s*['"]([^'"]+)['"]/g,
1501
+ reExport: /export\s*\{[^}]+\}\s*from\s*['"]([^'"]+)['"]/g,
1502
+ reExportAll: /export\s*\*\s*from\s*['"]([^'"]+)['"]/g
1503
+ };
1504
+ function parseNamedImports(namedString) {
1505
+ return namedString.split(",").map((s) => s.trim()).filter((s) => s.length > 0).map((s) => {
1506
+ const asMatch = s.match(/^(\w+)\s+as\s+\w+$/);
1507
+ return asMatch ? asMatch[1] : s;
1508
+ });
1509
+ }
1510
+ function isRelativeImport(specifier) {
1511
+ return specifier.startsWith("./") || specifier.startsWith("../") || specifier.startsWith("/");
1512
+ }
1513
+ function isExternalImport(specifier) {
1514
+ return !isRelativeImport(specifier) && !specifier.startsWith("#");
1515
+ }
1516
+ function getPackageName(specifier) {
1517
+ if (specifier.startsWith("@")) {
1518
+ const parts = specifier.split("/");
1519
+ if (parts.length >= 2) {
1520
+ return `${parts[0]}/${parts[1]}`;
1521
+ }
1522
+ return specifier;
1523
+ }
1524
+ const firstSlash = specifier.indexOf("/");
1525
+ return firstSlash === -1 ? specifier : specifier.slice(0, firstSlash);
1526
+ }
1527
+ function getLineNumber(source, index) {
1528
+ let line = 1;
1529
+ for (let i = 0; i < index && i < source.length; i++) {
1530
+ if (source[i] === "\n") {
1531
+ line++;
1532
+ }
1533
+ }
1534
+ return line;
1535
+ }
1536
+ function getColumnNumber(source, index) {
1537
+ let column = 0;
1538
+ for (let i = index - 1; i >= 0 && source[i] !== "\n"; i--) {
1539
+ column++;
1540
+ }
1541
+ return column;
1542
+ }
1543
+ function parseImports(source) {
1544
+ const imports = [];
1545
+ const seenStatements = /* @__PURE__ */ new Set();
1546
+ const addImport = (imp) => {
1547
+ const key = `${imp.type}:${imp.specifier}:${imp.statement}`;
1548
+ if (!seenStatements.has(key)) {
1549
+ seenStatements.add(key);
1550
+ imports.push(imp);
1551
+ }
1552
+ };
1553
+ let match;
1554
+ const defaultAndNamedRegex = new RegExp(IMPORT_PATTERNS.defaultAndNamed.source, "g");
1555
+ while ((match = defaultAndNamedRegex.exec(source)) !== null) {
1556
+ const [statement, defaultName, namedString, specifier] = match;
1557
+ addImport({
1558
+ statement,
1559
+ specifier,
1560
+ type: "default",
1561
+ defaultImport: defaultName,
1562
+ namedImports: parseNamedImports(namedString),
1563
+ line: getLineNumber(source, match.index),
1564
+ column: getColumnNumber(source, match.index)
1565
+ });
1566
+ }
1567
+ const defaultAndNamespaceRegex = new RegExp(IMPORT_PATTERNS.defaultAndNamespace.source, "g");
1568
+ while ((match = defaultAndNamespaceRegex.exec(source)) !== null) {
1569
+ const [statement, defaultName, namespaceName, specifier] = match;
1570
+ addImport({
1571
+ statement,
1572
+ specifier,
1573
+ type: "default",
1574
+ defaultImport: defaultName,
1575
+ namespaceImport: namespaceName,
1576
+ line: getLineNumber(source, match.index),
1577
+ column: getColumnNumber(source, match.index)
1578
+ });
1579
+ }
1580
+ const namedRegex = new RegExp(IMPORT_PATTERNS.named.source, "g");
1581
+ while ((match = namedRegex.exec(source)) !== null) {
1582
+ const [statement, namedString, specifier] = match;
1583
+ addImport({
1584
+ statement,
1585
+ specifier,
1586
+ type: "named",
1587
+ namedImports: parseNamedImports(namedString),
1588
+ line: getLineNumber(source, match.index),
1589
+ column: getColumnNumber(source, match.index)
1590
+ });
1591
+ }
1592
+ const defaultRegex = new RegExp(IMPORT_PATTERNS.default.source, "g");
1593
+ while ((match = defaultRegex.exec(source)) !== null) {
1594
+ const [statement, defaultName, specifier] = match;
1595
+ const afterMatch = source.slice(match.index + match[0].length - specifier.length - 2);
1596
+ if (afterMatch.startsWith(",")) continue;
1597
+ addImport({
1598
+ statement,
1599
+ specifier,
1600
+ type: "default",
1601
+ defaultImport: defaultName,
1602
+ line: getLineNumber(source, match.index),
1603
+ column: getColumnNumber(source, match.index)
1604
+ });
1605
+ }
1606
+ const namespaceRegex = new RegExp(IMPORT_PATTERNS.namespace.source, "g");
1607
+ while ((match = namespaceRegex.exec(source)) !== null) {
1608
+ const [statement, namespaceName, specifier] = match;
1609
+ addImport({
1610
+ statement,
1611
+ specifier,
1612
+ type: "namespace",
1613
+ namespaceImport: namespaceName,
1614
+ line: getLineNumber(source, match.index),
1615
+ column: getColumnNumber(source, match.index)
1616
+ });
1617
+ }
1618
+ const sideEffectRegex = new RegExp(IMPORT_PATTERNS.sideEffect.source, "g");
1619
+ while ((match = sideEffectRegex.exec(source)) !== null) {
1620
+ const [statement, specifier] = match;
1621
+ const beforeMatch = source.slice(Math.max(0, match.index - 50), match.index);
1622
+ if (beforeMatch.includes("from")) continue;
1623
+ addImport({
1624
+ statement,
1625
+ specifier,
1626
+ type: "side-effect",
1627
+ line: getLineNumber(source, match.index),
1628
+ column: getColumnNumber(source, match.index)
1629
+ });
1630
+ }
1631
+ const dynamicRegex = new RegExp(IMPORT_PATTERNS.dynamic.source, "g");
1632
+ while ((match = dynamicRegex.exec(source)) !== null) {
1633
+ const [statement, specifier] = match;
1634
+ addImport({
1635
+ statement,
1636
+ specifier,
1637
+ type: "dynamic",
1638
+ line: getLineNumber(source, match.index),
1639
+ column: getColumnNumber(source, match.index)
1640
+ });
1641
+ }
1642
+ const reExportRegex = new RegExp(IMPORT_PATTERNS.reExport.source, "g");
1643
+ while ((match = reExportRegex.exec(source)) !== null) {
1644
+ const [statement, specifier] = match;
1645
+ addImport({
1646
+ statement,
1647
+ specifier,
1648
+ type: "named",
1649
+ line: getLineNumber(source, match.index),
1650
+ column: getColumnNumber(source, match.index)
1651
+ });
1652
+ }
1653
+ const reExportAllRegex = new RegExp(IMPORT_PATTERNS.reExportAll.source, "g");
1654
+ while ((match = reExportAllRegex.exec(source)) !== null) {
1655
+ const [statement, specifier] = match;
1656
+ addImport({
1657
+ statement,
1658
+ specifier,
1659
+ type: "namespace",
1660
+ line: getLineNumber(source, match.index),
1661
+ column: getColumnNumber(source, match.index)
1662
+ });
1663
+ }
1664
+ const externalImports = imports.filter((imp) => isExternalImport(imp.specifier));
1665
+ const relativeImports = imports.filter((imp) => isRelativeImport(imp.specifier));
1666
+ const externalPackages = [...new Set(externalImports.map((imp) => getPackageName(imp.specifier)))];
1667
+ return {
1668
+ imports,
1669
+ externalImports,
1670
+ relativeImports,
1671
+ externalPackages
1672
+ };
1673
+ }
1674
+
1675
+ // libs/uipack/src/resolver/esm-sh.resolver.ts
1676
+ var DEFAULT_FALLBACK_CDN = "https://esm.sh";
1677
+ function createEsmShResolver(options = {}) {
1678
+ const {
1679
+ fallbackCdnBase = DEFAULT_FALLBACK_CDN,
1680
+ registry = DEFAULT_CDN_REGISTRY,
1681
+ providerOrder = ["esm.sh", "cloudflare", "jsdelivr", "unpkg"]
1682
+ } = options;
1683
+ return {
1684
+ resolve(specifier, _context) {
1685
+ if (specifier.startsWith("./") || specifier.startsWith("../") || specifier.startsWith("/")) {
1686
+ return null;
1687
+ }
1688
+ if (specifier.startsWith("node:") || specifier.startsWith("#")) {
1689
+ return null;
1690
+ }
1691
+ const pkgName = getPackageName(specifier);
1692
+ const entry = lookupPackage(pkgName, registry);
1693
+ if (entry) {
1694
+ for (const provider of providerOrder) {
1695
+ const config = entry.providers[provider];
1696
+ if (config?.url) {
1697
+ const subpath = specifier.slice(pkgName.length);
1698
+ if (subpath && config.url.includes("esm.sh/")) {
1699
+ return {
1700
+ value: config.url + subpath,
1701
+ type: "url",
1702
+ integrity: config.integrity
1703
+ };
1704
+ }
1705
+ if (subpath) {
1706
+ return {
1707
+ value: `${fallbackCdnBase}/${specifier}`,
1708
+ type: "url"
1709
+ };
1710
+ }
1711
+ if (config.global && !config.esm) {
1712
+ return {
1713
+ value: config.url,
1714
+ type: "url",
1715
+ integrity: config.integrity
1716
+ };
1717
+ }
1718
+ return {
1719
+ value: config.url,
1720
+ type: "url",
1721
+ integrity: config.integrity
1722
+ };
1723
+ }
1724
+ }
1725
+ }
1726
+ return {
1727
+ value: `${fallbackCdnBase}/${specifier}`,
1728
+ type: "url"
1729
+ };
1730
+ }
1731
+ };
1732
+ }
1733
+
1734
+ // libs/uipack/src/component/types.ts
1735
+ function isNpmSource(source) {
1736
+ return typeof source === "object" && source !== null && "npm" in source;
1737
+ }
1738
+ function isFileSource(source) {
1739
+ return typeof source === "object" && source !== null && "file" in source;
1740
+ }
1741
+ function isImportSource(source) {
1742
+ return typeof source === "object" && source !== null && "import" in source;
1743
+ }
1744
+ function isFunctionSource(source) {
1745
+ return typeof source === "function";
1746
+ }
1747
+
1748
+ // libs/uipack/src/component/transpiler.ts
1749
+ function bundleFileSource(source, filename, resolveDir, componentName) {
1750
+ const esbuild = __require("esbuild");
1751
+ const mountCode = `
1752
+ // --- Auto-generated mount ---
1753
+ import { createRoot } from 'react-dom/client';
1754
+ import { McpBridgeProvider } from '@frontmcp/ui/react';
1755
+ import React from 'react';
1756
+ const __root = document.getElementById('root');
1757
+ if (__root) {
1758
+ createRoot(__root).render(
1759
+ React.createElement(McpBridgeProvider, null,
1760
+ React.createElement(${componentName})
1761
+ )
1762
+ );
1763
+ }`;
1764
+ const loader = {
1765
+ ".tsx": "tsx",
1766
+ ".jsx": "jsx"
1767
+ };
1768
+ const stdinLoader = filename.endsWith(".tsx") ? "tsx" : "jsx";
1769
+ try {
1770
+ const result = esbuild.buildSync({
1771
+ stdin: {
1772
+ contents: source + "\n" + mountCode,
1773
+ loader: stdinLoader,
1774
+ resolveDir,
1775
+ sourcefile: filename
1776
+ },
1777
+ bundle: true,
1778
+ write: false,
1779
+ format: "esm",
1780
+ target: "es2020",
1781
+ jsx: "transform",
1782
+ jsxFactory: "React.createElement",
1783
+ jsxFragment: "React.Fragment",
1784
+ external: ["react", "react-dom"],
1785
+ define: { "process.env.NODE_ENV": '"production"' },
1786
+ platform: "browser",
1787
+ treeShaking: true,
1788
+ logLevel: "warning",
1789
+ loader
1790
+ });
1791
+ return { code: result.outputFiles[0].text };
1792
+ } catch (err) {
1793
+ const message = err instanceof Error ? err.message : String(err);
1794
+ throw new Error(
1795
+ `Failed to bundle FileSource "${filename}": ${message}. Ensure workspace packages are built (e.g. nx build ui).`
1796
+ );
353
1797
  }
1798
+ }
1799
+ function extractDefaultExportName(code) {
1800
+ let match = code.match(/export\s+default\s+(?:function|class)\s+(\w+)/);
1801
+ if (match) return match[1];
1802
+ match = code.match(/export\s+default\s+(\w+)\s*;/);
1803
+ if (match) return match[1];
1804
+ return null;
1805
+ }
1806
+
1807
+ // libs/uipack/src/component/loader.ts
1808
+ var DEFAULT_META = {
1809
+ mcpAware: false,
1810
+ renderer: "auto"
354
1811
  };
355
- function resolveServingMode(options) {
356
- const { configuredMode = "auto", platformType } = options;
357
- const capabilities = PLATFORM_CAPABILITIES[platformType] || PLATFORM_CAPABILITIES.unknown;
358
- if (!capabilities.supportsWidgets) {
359
- return {
360
- effectiveMode: null,
361
- useStructuredContent: false,
362
- supportsUI: false,
363
- reason: `Platform '${platformType}' does not support widget UI`
364
- };
1812
+ function resolveUISource(source, options) {
1813
+ const resolver = options?.resolver ?? createEsmShResolver();
1814
+ if (isNpmSource(source)) {
1815
+ return resolveNpmSource(source, resolver);
1816
+ }
1817
+ if (isImportSource(source)) {
1818
+ return resolveImportSource(source);
365
1819
  }
366
- if (configuredMode === "auto") {
1820
+ if (isFileSource(source)) {
1821
+ return resolveFileSource(source);
1822
+ }
1823
+ if (isFunctionSource(source)) {
1824
+ return resolveFunctionSource(source, options?.input, options?.output);
1825
+ }
1826
+ throw new Error("Unknown UISource type");
1827
+ }
1828
+ function resolveNpmSource(source, resolver) {
1829
+ const specifier = source.version ? `${source.npm}@${source.version}` : source.npm;
1830
+ const pkgName = getPackageName(source.npm);
1831
+ const resolved = resolver.resolve(specifier);
1832
+ const url = resolved?.value ?? `https://esm.sh/${specifier}`;
1833
+ const peerDeps = [];
1834
+ if (pkgName !== "react" && pkgName !== "react-dom") {
1835
+ peerDeps.push("react", "react-dom");
1836
+ }
1837
+ return {
1838
+ mode: "module",
1839
+ url,
1840
+ exportName: source.exportName ?? "default",
1841
+ meta: { ...DEFAULT_META, renderer: "react" },
1842
+ peerDependencies: peerDeps
1843
+ };
1844
+ }
1845
+ function resolveImportSource(source) {
1846
+ return {
1847
+ mode: "module",
1848
+ url: source.import,
1849
+ exportName: source.exportName ?? "default",
1850
+ meta: { ...DEFAULT_META },
1851
+ peerDependencies: []
1852
+ };
1853
+ }
1854
+ function resolveFileSource(source) {
1855
+ const path = __require("path");
1856
+ const ext = path.extname(source.file).toLowerCase();
1857
+ if (ext === ".tsx" || ext === ".jsx") {
1858
+ const fs = __require("fs");
1859
+ const filePath = path.isAbsolute(source.file) ? source.file : path.resolve(process.cwd(), source.file);
1860
+ const rawSource = fs.readFileSync(filePath, "utf-8");
1861
+ const componentName = source.exportName || extractDefaultExportName(rawSource) || "Component";
1862
+ const bundled = bundleFileSource(rawSource, source.file, path.dirname(filePath), componentName);
1863
+ const parsed = parseImports(bundled.code);
367
1864
  return {
368
- effectiveMode: capabilities.defaultMode,
369
- useStructuredContent: capabilities.useStructuredContent,
370
- supportsUI: true,
371
- reason: `Auto-selected '${capabilities.defaultMode}' for platform '${platformType}'`
1865
+ mode: "module",
1866
+ code: bundled.code,
1867
+ imports: [...new Set(parsed.externalImports.map((i) => i.specifier))],
1868
+ exportName: componentName,
1869
+ meta: { mcpAware: true, renderer: "react" },
1870
+ peerDependencies: source.peerDependencies || ["react", "react-dom"],
1871
+ bundled: true
372
1872
  };
373
1873
  }
374
- if (capabilities.supportedModes.includes(configuredMode)) {
1874
+ if (source.inline) {
375
1875
  return {
376
- effectiveMode: configuredMode,
377
- useStructuredContent: capabilities.useStructuredContent,
378
- supportsUI: true,
379
- reason: `Using configured mode '${configuredMode}' (supported by '${platformType}')`
1876
+ mode: "inline",
1877
+ html: `<!-- File source: ${source.file} (inline mode - content to be resolved at build time) -->`,
1878
+ exportName: "default",
1879
+ meta: { ...DEFAULT_META, renderer: "html" },
1880
+ peerDependencies: []
380
1881
  };
381
1882
  }
382
1883
  return {
383
- effectiveMode: null,
384
- useStructuredContent: false,
385
- supportsUI: false,
386
- reason: `Mode '${configuredMode}' not supported by platform '${platformType}'. Supported: ${capabilities.supportedModes.join(", ") || "none"}`
1884
+ mode: "module",
1885
+ url: source.file,
1886
+ exportName: "default",
1887
+ meta: { ...DEFAULT_META },
1888
+ peerDependencies: []
387
1889
  };
388
1890
  }
389
- function isPlatformModeSupported(platformType, mode) {
390
- const capabilities = PLATFORM_CAPABILITIES[platformType] || PLATFORM_CAPABILITIES.unknown;
391
- if (mode === "auto") {
392
- return capabilities.supportsWidgets;
1891
+ function resolveFunctionSource(source, input, output) {
1892
+ const html = source(input, output);
1893
+ return {
1894
+ mode: "inline",
1895
+ html,
1896
+ exportName: "default",
1897
+ meta: { mcpAware: false, renderer: "html" },
1898
+ peerDependencies: []
1899
+ };
1900
+ }
1901
+ function generateMountScript(resolved, propsMapping) {
1902
+ if (resolved.mode !== "module" || !resolved.url) {
1903
+ return "";
1904
+ }
1905
+ const componentImport = resolved.exportName === "default" ? `import Component from '${resolved.url}';` : `import { ${resolved.exportName} as Component } from '${resolved.url}';`;
1906
+ if (resolved.meta.mcpAware) {
1907
+ return `${componentImport}
1908
+ import React from 'react';
1909
+ import { createRoot } from 'react-dom/client';
1910
+ createRoot(document.getElementById('root'))
1911
+ .render(React.createElement(Component));`;
393
1912
  }
394
- return capabilities.supportedModes.includes(mode);
1913
+ const propsCode = propsMapping ? generateMappedPropsCode(propsMapping) : "window.__mcpToolOutput";
1914
+ return `${componentImport}
1915
+ import React from 'react';
1916
+ import { createRoot } from 'react-dom/client';
1917
+ const output = window.__mcpToolOutput;
1918
+ const props = ${propsCode};
1919
+ createRoot(document.getElementById('root'))
1920
+ .render(React.createElement(Component, props));`;
395
1921
  }
396
- function getDefaultServingMode(platformType) {
397
- const capabilities = PLATFORM_CAPABILITIES[platformType] || PLATFORM_CAPABILITIES.unknown;
398
- return capabilities.supportsWidgets ? capabilities.defaultMode : null;
1922
+ function generateMappedPropsCode(mapping) {
1923
+ const entries = Object.entries(mapping).map(([prop, path]) => {
1924
+ const accessPath = path.split(".").map((part) => `[${safeJsonForScript(part)}]`).join("");
1925
+ return `${safeJsonForScript(prop)}: output${accessPath}`;
1926
+ }).join(", ");
1927
+ return `({ ${entries} })`;
399
1928
  }
400
- function platformUsesStructuredContent(platformType) {
401
- const capabilities = PLATFORM_CAPABILITIES[platformType] || PLATFORM_CAPABILITIES.unknown;
402
- return capabilities.useStructuredContent;
1929
+
1930
+ // libs/uipack/src/resolver/import-map.ts
1931
+ function createImportMapFromResolved(resolved) {
1932
+ const imports = {};
1933
+ const integrity = {};
1934
+ for (const [specifier, res] of Object.entries(resolved)) {
1935
+ if (res.type === "url") {
1936
+ imports[specifier] = res.value;
1937
+ if (res.integrity) {
1938
+ integrity[res.value] = res.integrity;
1939
+ }
1940
+ }
1941
+ }
1942
+ return {
1943
+ imports,
1944
+ integrity: Object.keys(integrity).length > 0 ? integrity : void 0
1945
+ };
403
1946
  }
404
- function platformSupportsWidgets(platformType) {
405
- const capabilities = PLATFORM_CAPABILITIES[platformType] || PLATFORM_CAPABILITIES.unknown;
406
- return capabilities.supportsWidgets;
1947
+ function generateImportMapScriptTag(map) {
1948
+ const json = JSON.stringify(map, null, 2).replace(/<\//g, "<\\/");
1949
+ return `<script type="importmap">
1950
+ ${json}
1951
+ </script>`;
407
1952
  }
408
1953
 
409
- // libs/uipack/src/utils/index.ts
410
- import {
411
- safeStringify,
412
- escapeHtml,
413
- escapeHtmlAttr,
414
- escapeJsString,
415
- escapeScriptClose,
416
- safeJsonForScript
417
- } from "@frontmcp/utils";
1954
+ // libs/uipack/src/component/renderer.ts
1955
+ function renderComponent(config, shellConfig) {
1956
+ const resolver = shellConfig.resolver ?? createEsmShResolver();
1957
+ const resolved = resolveUISource(config.source, {
1958
+ resolver,
1959
+ input: shellConfig.input,
1960
+ output: shellConfig.output
1961
+ });
1962
+ const mergedShellConfig = {
1963
+ ...shellConfig,
1964
+ withShell: config.withShell ?? shellConfig.withShell,
1965
+ includeBridge: config.includeBridge ?? shellConfig.includeBridge,
1966
+ customShell: config.customShell ?? shellConfig.customShell
1967
+ };
1968
+ if (resolved.mode === "inline") {
1969
+ return buildShell(resolved.html ?? "", mergedShellConfig);
1970
+ }
1971
+ const content = buildModuleContent(resolved, resolver, config.props);
1972
+ return buildShell(content, mergedShellConfig);
1973
+ }
1974
+ function buildModuleContent(resolved, resolver, propsMapping) {
1975
+ const parts = [];
1976
+ if (resolved.code && resolved.bundled) {
1977
+ const importEntries = {};
1978
+ for (const spec of resolved.imports || []) {
1979
+ const depResolved = resolver.resolve(spec);
1980
+ if (depResolved) {
1981
+ importEntries[spec] = depResolved;
1982
+ }
1983
+ }
1984
+ if (Object.keys(importEntries).length > 0) {
1985
+ const importMap = createImportMapFromResolved(importEntries);
1986
+ parts.push(generateImportMapScriptTag(importMap));
1987
+ }
1988
+ parts.push('<div id="root"></div>');
1989
+ parts.push(`<script type="module">
1990
+ ${resolved.code}
1991
+ </script>`);
1992
+ } else if (resolved.code) {
1993
+ const importEntries = {};
1994
+ const allSpecifiers = /* @__PURE__ */ new Set([
1995
+ ...resolved.imports || [],
1996
+ "react-dom/client",
1997
+ // needed by mount script
1998
+ "@frontmcp/ui/react",
1999
+ // needed for McpBridgeProvider
2000
+ // React subpath entries needed by esm.sh externalized modules:
2001
+ "react/jsx-runtime",
2002
+ "react/jsx-dev-runtime",
2003
+ "react-dom/server",
2004
+ "react-dom/static"
2005
+ ]);
2006
+ const coreDeps = /* @__PURE__ */ new Set([
2007
+ "react",
2008
+ "react-dom",
2009
+ "react-dom/client",
2010
+ "react/jsx-runtime",
2011
+ "react/jsx-dev-runtime",
2012
+ "react-dom/server",
2013
+ "react-dom/static"
2014
+ ]);
2015
+ for (const spec of allSpecifiers) {
2016
+ const depResolved = resolver.resolve(spec);
2017
+ if (depResolved) {
2018
+ if (!coreDeps.has(spec) && depResolved.value?.includes("esm.sh")) {
2019
+ importEntries[spec] = {
2020
+ ...depResolved,
2021
+ value: addExternalParam(depResolved.value, ["react", "react-dom"])
2022
+ };
2023
+ } else {
2024
+ importEntries[spec] = depResolved;
2025
+ }
2026
+ }
2027
+ }
2028
+ if (importEntries["react-dom"] && !importEntries["react-dom/client"]) {
2029
+ const rdc = resolver.resolve("react-dom/client");
2030
+ if (rdc) importEntries["react-dom/client"] = rdc;
2031
+ }
2032
+ if (Object.keys(importEntries).length > 0) {
2033
+ const importMap = createImportMapFromResolved(importEntries);
2034
+ parts.push(generateImportMapScriptTag(importMap));
2035
+ }
2036
+ parts.push('<div id="root"></div>');
2037
+ const mountCode = generateInlineMountCode(resolved.exportName);
2038
+ parts.push(`<script type="module">
2039
+ ${resolved.code}
2040
+ ${mountCode}
2041
+ </script>`);
2042
+ } else {
2043
+ const importEntries = {};
2044
+ for (const dep of resolved.peerDependencies) {
2045
+ const depResolved = resolver.resolve(dep);
2046
+ if (depResolved) {
2047
+ importEntries[dep] = depResolved;
2048
+ }
2049
+ }
2050
+ if (importEntries["react-dom"]) {
2051
+ const reactDomClient = resolver.resolve("react-dom/client");
2052
+ if (reactDomClient) {
2053
+ importEntries["react-dom/client"] = reactDomClient;
2054
+ }
2055
+ }
2056
+ if (Object.keys(importEntries).length > 0) {
2057
+ const importMap = createImportMapFromResolved(importEntries);
2058
+ parts.push(generateImportMapScriptTag(importMap));
2059
+ }
2060
+ parts.push('<div id="root"></div>');
2061
+ const mountScript = generateMountScript(resolved, propsMapping);
2062
+ if (mountScript) {
2063
+ parts.push(`<script type="module">
2064
+ ${mountScript}
2065
+ </script>`);
2066
+ }
2067
+ }
2068
+ return parts.join("\n");
2069
+ }
2070
+ function addExternalParam(url, externals) {
2071
+ const sep = url.includes("?") ? "&" : "?";
2072
+ return `${url}${sep}external=${externals.join(",")}`;
2073
+ }
2074
+ function generateInlineMountCode(componentName) {
2075
+ return `
2076
+ // --- Mount ---
2077
+ import { createRoot as __createRoot } from 'react-dom/client';
2078
+ import { McpBridgeProvider as __McpBridgeProvider } from '@frontmcp/ui/react';
2079
+ const __root = document.getElementById('root');
2080
+ if (__root) {
2081
+ __createRoot(__root).render(
2082
+ React.createElement(__McpBridgeProvider, null,
2083
+ React.createElement(${componentName})
2084
+ )
2085
+ );
2086
+ }`;
2087
+ }
418
2088
 
419
- // libs/uipack/src/adapters/response-builder.ts
420
- function buildToolResponseContent(options) {
421
- const { rawOutput, htmlContent, servingMode, useStructuredContent, platformType } = options;
422
- if (servingMode === "static") {
423
- return {
424
- content: [{ type: "text", text: safeStringify(rawOutput) }],
425
- structuredContent: useStructuredContent ? rawOutput : void 0,
426
- contentCleared: false,
427
- format: "json-only"
428
- };
2089
+ // libs/uipack/src/adapters/type-detector.ts
2090
+ function detectUIType(template) {
2091
+ if (template === null || template === void 0) {
2092
+ return "auto";
429
2093
  }
430
- if (servingMode === "hybrid") {
431
- return {
432
- content: [{ type: "text", text: safeStringify(rawOutput) }],
433
- structuredContent: useStructuredContent ? rawOutput : void 0,
434
- contentCleared: false,
435
- format: "json-only"
436
- };
2094
+ if (typeof template === "object" && template !== null && "file" in template) {
2095
+ const file = template.file;
2096
+ if (/\.(tsx|jsx)$/i.test(file)) return "react";
437
2097
  }
438
- if (useStructuredContent) {
439
- if (htmlContent) {
440
- const htmlInContent = platformType === "openai" || platformType === "ext-apps";
441
- if (htmlInContent) {
442
- return {
443
- content: [{ type: "text", text: htmlContent }],
444
- structuredContent: rawOutput,
445
- contentCleared: false,
446
- format: "structured-content"
447
- };
448
- } else {
449
- return {
450
- content: [{ type: "text", text: safeStringify(rawOutput) }],
451
- structuredContent: rawOutput,
452
- contentCleared: false,
453
- format: "structured-content"
454
- };
2098
+ if (typeof template === "function") {
2099
+ const proto = template.prototype;
2100
+ if (proto && typeof proto.render === "function") {
2101
+ return "react";
2102
+ }
2103
+ const asRecord = template;
2104
+ if (asRecord["$$typeof"] !== void 0) {
2105
+ return "react";
2106
+ }
2107
+ if (template.name && /^[A-Z]/.test(template.name)) {
2108
+ return "react";
2109
+ }
2110
+ return "html";
2111
+ }
2112
+ if (typeof template === "string") {
2113
+ if (template.includes("<") && template.includes(">")) {
2114
+ return "html";
2115
+ }
2116
+ return "markdown";
2117
+ }
2118
+ return "auto";
2119
+ }
2120
+
2121
+ // libs/uipack/src/adapters/content-detector.ts
2122
+ var CHART_TYPES = /* @__PURE__ */ new Set(["bar", "line", "pie", "area", "scatter", "doughnut", "radar", "polarArea", "bubble"]);
2123
+ var MERMAID_PREFIXES = [
2124
+ "flowchart",
2125
+ "graph",
2126
+ "sequenceDiagram",
2127
+ "classDiagram",
2128
+ "stateDiagram",
2129
+ "erDiagram",
2130
+ "gantt",
2131
+ "pie",
2132
+ "gitGraph",
2133
+ "journey",
2134
+ "mindmap",
2135
+ "timeline",
2136
+ "quadrantChart",
2137
+ "sankey",
2138
+ "xychart"
2139
+ ];
2140
+ var PDF_MAGIC = "JVBERi";
2141
+ function detectContentType(value) {
2142
+ if (value === null || value === void 0) {
2143
+ return "text";
2144
+ }
2145
+ if (typeof value === "object" && !Array.isArray(value)) {
2146
+ const obj = value;
2147
+ if (typeof obj["type"] === "string" && CHART_TYPES.has(obj["type"])) {
2148
+ return "chart";
2149
+ }
2150
+ return "json";
2151
+ }
2152
+ if (typeof value === "string") {
2153
+ const trimmed = value.trimStart();
2154
+ if (trimmed.startsWith(PDF_MAGIC)) {
2155
+ return "pdf";
2156
+ }
2157
+ for (const prefix of MERMAID_PREFIXES) {
2158
+ if (trimmed.startsWith(prefix)) {
2159
+ return "mermaid";
455
2160
  }
456
2161
  }
457
- return {
458
- content: [{ type: "text", text: safeStringify(rawOutput) }],
459
- structuredContent: rawOutput,
460
- contentCleared: false,
461
- format: "json-only"
462
- };
2162
+ if (trimmed.includes("<") && trimmed.includes(">")) {
2163
+ return "html";
2164
+ }
2165
+ return "text";
463
2166
  }
464
- const supportsWidgets = platformSupportsWidgets(platformType);
465
- if (supportsWidgets) {
466
- return {
467
- content: [],
468
- contentCleared: true,
469
- format: "widget"
470
- };
2167
+ return "text";
2168
+ }
2169
+
2170
+ // libs/uipack/src/adapters/content-renderers.ts
2171
+ var CHART_JS_CDN = "https://esm.sh/chart.js@4/auto";
2172
+ var MERMAID_CDN = "https://esm.sh/mermaid@11/dist/mermaid.esm.min.mjs";
2173
+ function buildChartHtml(chartJson) {
2174
+ const chartData = safeJsonForScript(chartJson);
2175
+ const content = `
2176
+ <canvas id="chart" style="max-width:100%;max-height:80vh;"></canvas>
2177
+ <script type="module">
2178
+ import Chart from '${CHART_JS_CDN}';
2179
+ const ctx = document.getElementById('chart').getContext('2d');
2180
+ new Chart(ctx, ${chartData});
2181
+ </script>`;
2182
+ const result = buildShell(content, {
2183
+ toolName: "chart",
2184
+ csp: {
2185
+ resourceDomains: ["https://esm.sh"],
2186
+ connectDomains: ["https://esm.sh"]
2187
+ },
2188
+ includeBridge: false
2189
+ });
2190
+ return result.html;
2191
+ }
2192
+ function buildMermaidHtml(mermaidCode) {
2193
+ const escaped = escapeHtml(mermaidCode);
2194
+ const content = `
2195
+ <pre class="mermaid">${escaped}</pre>
2196
+ <script type="module">
2197
+ import mermaid from '${MERMAID_CDN}';
2198
+ mermaid.initialize({ startOnLoad: true, theme: 'default' });
2199
+ </script>`;
2200
+ const result = buildShell(content, {
2201
+ toolName: "mermaid",
2202
+ csp: {
2203
+ resourceDomains: ["https://esm.sh"],
2204
+ connectDomains: ["https://esm.sh"]
2205
+ },
2206
+ includeBridge: false
2207
+ });
2208
+ return result.html;
2209
+ }
2210
+ function buildPdfHtml(base64) {
2211
+ const content = `
2212
+ <div id="pdf-container" style="width:100%;overflow:auto;text-align:center;"></div>
2213
+ <script type="module">
2214
+ import * as pdfjsLib from 'https://esm.sh/pdfjs-dist@4/build/pdf.min.mjs';
2215
+ pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://esm.sh/pdfjs-dist@4/build/pdf.worker.min.mjs';
2216
+
2217
+ const base64 = ${JSON.stringify(base64)};
2218
+ const binaryStr = atob(base64);
2219
+ const bytes = new Uint8Array(binaryStr.length);
2220
+ for (let i = 0; i < binaryStr.length; i++) bytes[i] = binaryStr.charCodeAt(i);
2221
+
2222
+ const pdf = await pdfjsLib.getDocument({ data: bytes }).promise;
2223
+ const container = document.getElementById('pdf-container');
2224
+ for (let i = 1; i <= pdf.numPages; i++) {
2225
+ const page = await pdf.getPage(i);
2226
+ const viewport = page.getViewport({ scale: 1.5 });
2227
+ const canvas = document.createElement('canvas');
2228
+ canvas.width = viewport.width;
2229
+ canvas.height = viewport.height;
2230
+ canvas.style.marginBottom = '8px';
2231
+ container.appendChild(canvas);
2232
+ await page.render({ canvasContext: canvas.getContext('2d'), viewport }).promise;
2233
+ }
2234
+ </script>`;
2235
+ const result = buildShell(content, {
2236
+ toolName: "pdf",
2237
+ csp: {
2238
+ resourceDomains: ["https://esm.sh"],
2239
+ connectDomains: ["https://esm.sh"]
2240
+ },
2241
+ includeBridge: false
2242
+ });
2243
+ return result.html;
2244
+ }
2245
+ function wrapDetectedContent(value) {
2246
+ const contentType = detectContentType(value);
2247
+ switch (contentType) {
2248
+ case "chart":
2249
+ return buildChartHtml(value);
2250
+ case "mermaid":
2251
+ return buildMermaidHtml(value);
2252
+ case "pdf":
2253
+ return buildPdfHtml(value);
2254
+ case "html":
2255
+ return void 0;
2256
+ default:
2257
+ return void 0;
471
2258
  }
472
- if (htmlContent) {
473
- return {
474
- content: [
475
- {
476
- type: "text",
477
- text: `## Data
478
- \`\`\`json
479
- ${safeStringify(
480
- rawOutput,
481
- 2
482
- )}
483
- \`\`\`
484
-
485
- ## Visual Template (for artifact rendering)
486
- \`\`\`html
487
- ${htmlContent}
488
- \`\`\``
2259
+ }
2260
+
2261
+ // libs/uipack/src/adapters/template-renderer.ts
2262
+ function renderToolTemplate(options) {
2263
+ const { toolName, input, output, template, platformType, resolver } = options;
2264
+ const uiType = detectUIType(template);
2265
+ const shellConfig = {
2266
+ toolName,
2267
+ input,
2268
+ output,
2269
+ includeBridge: true,
2270
+ resolver
2271
+ };
2272
+ let html;
2273
+ let hash = "";
2274
+ let size = 0;
2275
+ if (typeof template === "object" && template !== null && "file" in template) {
2276
+ const cspResourceDomains = ["https://esm.sh"];
2277
+ const cspConnectDomains = ["https://esm.sh"];
2278
+ if (resolver && "overrides" in resolver) {
2279
+ const overrides = resolver.overrides;
2280
+ if (overrides) {
2281
+ for (const url of Object.values(overrides)) {
2282
+ try {
2283
+ const origin = new URL(url).origin;
2284
+ if (!cspResourceDomains.includes(origin)) cspResourceDomains.push(origin);
2285
+ if (!cspConnectDomains.includes(origin)) cspConnectDomains.push(origin);
2286
+ } catch {
2287
+ }
489
2288
  }
490
- ],
491
- contentCleared: false,
492
- format: "markdown"
2289
+ }
2290
+ }
2291
+ const cspConfig = {
2292
+ resourceDomains: cspResourceDomains,
2293
+ connectDomains: cspConnectDomains
493
2294
  };
2295
+ const result = renderComponent({ source: template }, { ...shellConfig, csp: cspConfig });
2296
+ html = result.html;
2297
+ hash = result.hash;
2298
+ size = result.size;
2299
+ } else if (typeof template === "function") {
2300
+ if (uiType === "react") {
2301
+ const shellResult = buildShell('<div id="root"></div>', shellConfig);
2302
+ html = shellResult.html;
2303
+ hash = shellResult.hash;
2304
+ size = shellResult.size;
2305
+ } else {
2306
+ const helpers = createTemplateHelpers();
2307
+ const ctx = { input, output, helpers };
2308
+ const rawResult = template(ctx);
2309
+ const wrapped = wrapDetectedContent(rawResult);
2310
+ if (wrapped) {
2311
+ html = wrapped;
2312
+ } else {
2313
+ const textContent = typeof rawResult === "string" ? rawResult : `<pre>${JSON.stringify(rawResult, null, 2)}</pre>`;
2314
+ const shellResult = buildShell(textContent, shellConfig);
2315
+ html = shellResult.html;
2316
+ hash = shellResult.hash;
2317
+ size = shellResult.size;
2318
+ }
2319
+ }
2320
+ } else if (typeof template === "string") {
2321
+ const shellResult = buildShell(template, shellConfig);
2322
+ html = shellResult.html;
2323
+ hash = shellResult.hash;
2324
+ size = shellResult.size;
2325
+ } else {
2326
+ const shellResult = buildShell('<div id="root"></div>', shellConfig);
2327
+ html = shellResult.html;
2328
+ hash = shellResult.hash;
2329
+ size = shellResult.size;
2330
+ }
2331
+ const htmlKey = "ui/html";
2332
+ const meta = {
2333
+ [htmlKey]: html,
2334
+ "ui/type": uiType,
2335
+ "ui/mimeType": MCP_APPS_MIME_TYPE
2336
+ };
2337
+ return {
2338
+ html,
2339
+ uiType,
2340
+ hash,
2341
+ size: size || Buffer.byteLength(html, "utf8"),
2342
+ meta
2343
+ };
2344
+ }
2345
+
2346
+ // libs/uipack/src/adapters/base-template.ts
2347
+ function createDefaultBaseTemplate(options) {
2348
+ const { toolName } = options;
2349
+ const bridgeScript = `<script>${generateBridgeIIFE({ minify: true })}</script>`;
2350
+ const content = `
2351
+ ${bridgeScript}
2352
+ <div id="root">
2353
+ <div style="font-family:system-ui,sans-serif;padding:1rem;color:#374151;">
2354
+ <p style="color:#6b7280;font-size:0.875rem;">Waiting for tool output...</p>
2355
+ <p style="color:#9ca3af;font-size:0.75rem;">Tool: <code>${toolName}</code></p>
2356
+ </div>
2357
+ </div>
2358
+ <script>
2359
+ (function() {
2360
+ var bridge = window.FrontMcpBridge;
2361
+ if (bridge && typeof bridge.onToolResult === 'function') {
2362
+ bridge.onToolResult(function(data) {
2363
+ var root = document.getElementById('root');
2364
+ if (root && data) {
2365
+ root.innerHTML = '<pre style="font-family:monospace;white-space:pre-wrap;padding:1rem;">' +
2366
+ JSON.stringify(data, null, 2).replace(/</g, '&lt;').replace(/>/g, '&gt;') +
2367
+ '</pre>';
2368
+ }
2369
+ });
2370
+ }
2371
+ })();
2372
+ </script>`;
2373
+ const result = buildShell(content, {
2374
+ toolName,
2375
+ includeBridge: false,
2376
+ // Bridge already included via inline script above
2377
+ title: `Widget: ${toolName}`
2378
+ });
2379
+ return result.html;
2380
+ }
2381
+
2382
+ // libs/uipack/src/adapters/cdn-info.ts
2383
+ var ESM_SH_BASE = "https://esm.sh";
2384
+ var REACT_DEPS = [
2385
+ { name: "react", url: `${ESM_SH_BASE}/react@18` },
2386
+ { name: "react-dom", url: `${ESM_SH_BASE}/react-dom@18` }
2387
+ ];
2388
+ function buildCDNInfoForUIType(uiType) {
2389
+ const deps = [];
2390
+ switch (uiType) {
2391
+ case "react":
2392
+ deps.push(...REACT_DEPS);
2393
+ break;
2394
+ case "mdx":
2395
+ deps.push(...REACT_DEPS);
2396
+ deps.push({ name: "marked", url: `${ESM_SH_BASE}/marked@latest` });
2397
+ break;
2398
+ case "markdown":
2399
+ deps.push({ name: "marked", url: `${ESM_SH_BASE}/marked@latest` });
2400
+ break;
2401
+ case "auto":
2402
+ deps.push(...REACT_DEPS);
2403
+ deps.push({ name: "marked", url: `${ESM_SH_BASE}/marked@latest` });
2404
+ break;
2405
+ case "html":
2406
+ default:
2407
+ break;
494
2408
  }
495
2409
  return {
496
- content: [{ type: "text", text: safeStringify(rawOutput, 2) }],
497
- contentCleared: false,
498
- format: "json-only"
2410
+ base: ESM_SH_BASE,
2411
+ dependencies: deps
499
2412
  };
500
2413
  }
501
2414
  export {
502
- buildFrontMCPCSP,
503
- buildOpenAICSP,
504
- buildToolDiscoveryMeta,
2415
+ MCP_APPS_EXTENSION_ID,
2416
+ MCP_APPS_MIME_TYPE,
2417
+ buildCDNInfoForUIType,
2418
+ buildChartHtml,
2419
+ buildMermaidHtml,
2420
+ buildPdfHtml,
505
2421
  buildToolResponseContent,
506
- buildUIMeta,
507
- getDefaultServingMode,
508
- getExtAppsMimeType,
509
- isExtAppsMimeType,
510
- isPlatformModeSupported,
511
- platformSupportsWidgets,
512
- platformUsesStructuredContent,
513
- resolveServingMode
2422
+ createDefaultBaseTemplate,
2423
+ detectContentType,
2424
+ detectUIType,
2425
+ isUIRenderFailure,
2426
+ renderToolTemplate,
2427
+ resolveServingMode,
2428
+ wrapDetectedContent
514
2429
  };