@frontmcp/uipack 1.11.2 → 1.12.0-rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. package/CLAUDE.md +4 -4
  2. package/README.md +20 -15
  3. package/adapters/code-highlight.d.ts +20 -0
  4. package/adapters/code-highlight.d.ts.map +1 -0
  5. package/adapters/index.d.ts +4 -2
  6. package/adapters/index.d.ts.map +1 -1
  7. package/adapters/index.js +36 -16
  8. package/adapters/markdown.d.ts +10 -2
  9. package/adapters/markdown.d.ts.map +1 -1
  10. package/adapters/mdx.d.ts +34 -0
  11. package/adapters/mdx.d.ts.map +1 -0
  12. package/adapters/project-require.d.ts +22 -0
  13. package/adapters/project-require.d.ts.map +1 -0
  14. package/adapters/render-options.d.ts +28 -0
  15. package/adapters/render-options.d.ts.map +1 -0
  16. package/adapters/response-builder.d.ts +9 -0
  17. package/adapters/response-builder.d.ts.map +1 -1
  18. package/adapters/server-react.d.ts +22 -0
  19. package/adapters/server-react.d.ts.map +1 -0
  20. package/adapters/serving-mode.d.ts +4 -1
  21. package/adapters/serving-mode.d.ts.map +1 -1
  22. package/adapters/template-renderer.d.ts +42 -2
  23. package/adapters/template-renderer.d.ts.map +1 -1
  24. package/component/index.js +25 -11
  25. package/component/loader.d.ts +5 -0
  26. package/component/loader.d.ts.map +1 -1
  27. package/component/renderer.d.ts.map +1 -1
  28. package/component/server-render.d.ts +21 -0
  29. package/component/server-render.d.ts.map +1 -0
  30. package/component/transpiler.d.ts +17 -13
  31. package/component/transpiler.d.ts.map +1 -1
  32. package/component/types.d.ts +7 -0
  33. package/component/types.d.ts.map +1 -1
  34. package/esm/adapters/index.mjs +36 -16
  35. package/esm/component/index.mjs +25 -11
  36. package/esm/index.mjs +71 -18
  37. package/esm/package.json +24 -3
  38. package/esm/shell/index.mjs +34 -1
  39. package/index.d.ts +1 -1
  40. package/index.d.ts.map +1 -1
  41. package/index.js +72 -19
  42. package/package.json +24 -3
  43. package/resolver/cdn-registry.d.ts +1 -1
  44. package/resolver/cdn-registry.d.ts.map +1 -1
  45. package/resolver/esm-sh.resolver.d.ts +1 -1
  46. package/resolver/esm-sh.resolver.d.ts.map +1 -1
  47. package/resolver/import-map.d.ts +1 -1
  48. package/resolver/import-map.d.ts.map +1 -1
  49. package/resolver/import-rewriter.d.ts.map +1 -1
  50. package/shell/custom-shell-resolver.d.ts +1 -1
  51. package/shell/custom-shell-resolver.d.ts.map +1 -1
  52. package/shell/custom-shell-validator.d.ts +1 -1
  53. package/shell/custom-shell-validator.d.ts.map +1 -1
  54. package/shell/index.d.ts +2 -0
  55. package/shell/index.d.ts.map +1 -1
  56. package/shell/index.js +35 -2
  57. package/shell/sanitize-css.d.ts +18 -0
  58. package/shell/sanitize-css.d.ts.map +1 -0
  59. package/shell/sanitize-html.d.ts +28 -0
  60. package/shell/sanitize-html.d.ts.map +1 -0
  61. package/shell/url-widget-shell.d.ts +35 -0
  62. package/shell/url-widget-shell.d.ts.map +1 -0
  63. package/types/ui-config.d.ts +207 -198
  64. package/types/ui-config.d.ts.map +1 -1
  65. package/types/ui-runtime.d.ts +11 -12
  66. package/types/ui-runtime.d.ts.map +1 -1
  67. package/typings/cache/memory-cache.d.ts +1 -1
  68. package/typings/cache/memory-cache.d.ts.map +1 -1
  69. package/typings/type-fetcher.d.ts +2 -2
  70. package/typings/type-fetcher.d.ts.map +1 -1
@@ -59,90 +59,90 @@ export interface UIContentSecurityPolicy {
59
59
  resourceDomains?: string[];
60
60
  }
61
61
  /**
62
- * XSS protection and content security settings.
62
+ * Sanitization of widget markup (`ui.contentSecurity`).
63
63
  *
64
- * Controls sanitization of HTML content rendered in widgets.
65
- * By default, strict sanitization is applied to prevent XSS attacks.
64
+ * Without `contentSecurity`, template markup reaches the page as written: a `<script>`, an
65
+ * `onclick` or a `javascript:` link in markup a template builds (`ctx.helpers.html`, `trustedHtml`,
66
+ * an HTML string) is kept, which is what widgets with inline scripts rely on. Values interpolated
67
+ * into `` html`…` `` are escaped either way, and so are plain string results unless
68
+ * `escapeStringResults: false`.
66
69
  *
67
- * ## Platform Isolation Context
70
+ * With `contentSecurity` set, even to `{}`, the markup FrontMCP builds on the server from the
71
+ * template is sanitized before it goes into the page. The sanitizer reads the markup as the
72
+ * browser's HTML tokenizer does and writes every tag back out, each attribute value decoded once,
73
+ * checked, and written back escaped, so the page gets exactly what was checked. It removes:
68
74
  *
69
- * Both OpenAI and Claude render widgets in **double-iframe isolation**:
75
+ * - `<script>` elements and `on*` handler attributes;
76
+ * - `srcdoc`, and `<object>`, `<embed>`, `<applet>`, `<base>` and `<meta>` elements;
77
+ * - SVG animation elements (`<animate>`, `<set>`, `<animateMotion>`, `<animateTransform>`), which
78
+ * can set any attribute of their parent, an `href` included;
79
+ * - `data-tool-call` and `data-tool-args`, with which the bridge calls a tool when the element is
80
+ * clicked;
81
+ * - every URL attribute (`href`, `src`, `action`, `formaction`, `xlink:href`, `srcset`, `ping`, …)
82
+ * whose scheme isn't `http:`, `https:` or `mailto:`; relative URLs stay, and so do raster
83
+ * `data:image/…` URLs in `<img src>`;
84
+ * - `@import` rules in `<style>` elements, `<link rel="stylesheet">` to another origin, and
85
+ * `position: fixed` and `position: sticky` in `style` attributes, which would let the markup lay
86
+ * itself over the widget;
87
+ * - comments, doctypes and processing instructions.
70
88
  *
71
- * ```
72
- * ┌─────────────────────────────────────────────────┐
73
- * │ ChatGPT / Claude Desktop │
74
- * │ ┌─────────────────────────────────────────────┐│
75
- * │ │ Outer Sandbox Iframe ││
76
- * │ │ - sandbox="allow-scripts allow-same-origin"││
77
- * │ │ - No access to parent cookies ││
78
- * │ │ ┌─────────────────────────────────────────┐││
79
- * │ │ │ Inner Widget Iframe │││
80
- * │ │ │ - CSP: script-src 'self' 'unsafe-inline'│││
81
- * │ │ │ - CSP: connect-src based on config │││
82
- * │ │ │ - Your widget HTML renders here │││
83
- * │ │ └─────────────────────────────────────────┘││
84
- * │ └─────────────────────────────────────────────┘│
85
- * └─────────────────────────────────────────────────┘
86
- * ```
89
+ * Other CSS is not filtered: a `<style>` element can still position or hide elements, and
90
+ * `url(…)` in CSS still loads from the origins the widget's Content-Security-Policy allows.
87
91
  *
88
- * This isolation means XSS attacks are **contained** but can still:
89
- * - Access widget data (input/output)
90
- * - Make API calls within CSP-allowed domains
91
- * - Display fake/phishing UI to users
92
+ * It applies to HTML, Markdown and MDX strings, to a template function's result, and to a
93
+ * component rendered on the server with `uiType: 'react'`. The page FrontMCP builds around the
94
+ * markup (the bridge, the call's data) is not touched, and a `{ file }` widget, which renders in
95
+ * the page, can't take it: the server refuses to start such a tool. It must be a plain object whose
96
+ * flags are booleans; anything else fails startup.
92
97
  *
93
- * **Recommendation:** Only disable protection for fully trusted content.
98
+ * Both OpenAI and Claude render widgets in sandboxed iframes, so script in a widget is contained,
99
+ * but it can still read the widget's data, call tools the widget may call and show a fake UI.
100
+ * Sanitize when the markup carries values you don't control and you'd rather not rely on escaping
101
+ * every one of them.
94
102
  */
95
103
  export interface UIContentSecurity {
96
104
  /**
97
- * Allow `javascript:` and other potentially dangerous URL schemes in links.
98
- *
99
- * When `false` (default), URLs are validated to only allow:
100
- * - `http://`, `https://` (web URLs)
101
- * - `/`, `#` (relative paths, anchors)
102
- * - `mailto:` (email links)
103
- *
104
- * When `true`, allows any URL scheme including:
105
- * - `javascript:` (inline script execution)
106
- * - `data:` (data URIs)
107
- * - `vbscript:` (legacy script)
105
+ * Keep any scheme but `data:` in the attributes that navigate when the user follows them: `href`
106
+ * of `<a>` and `<area>`, `action` of `<form>`, and `formaction` of `<button>` and `<input>`. A
107
+ * `javascript:` link then runs when it is clicked. Every other URL attribute, such as `src` of an
108
+ * `<iframe>`, `<frame>` or `<img>`, keeps the `http:` / `https:` / `mailto:` rule.
108
109
  *
109
110
  * @default false
110
111
  */
111
112
  allowUnsafeLinks?: boolean;
112
113
  /**
113
- * Allow inline `<script>` tags and event handlers (onclick, onerror, etc).
114
- *
115
- * When `false` (default), HTML content is sanitized to remove:
116
- * - `<script>...</script>` tags
117
- * - Event handler attributes (`onclick`, `onerror`, `onload`, etc.)
118
- *
119
- * When `true`, these elements are preserved in the output.
120
- *
121
- * **Note:** Even with this enabled, CSP may still block script execution
122
- * depending on the platform's iframe sandbox settings.
114
+ * Keep script written in the markup: `<script>` elements without a `src` (or SVG `href`), and
115
+ * `on*` event handler attributes. A `<script src>`, `<object>`, `<embed>`, `<applet>` and
116
+ * `srcdoc` are still removed. Inline script can do anything script can, loading more script
117
+ * included, so this turns script protection off for whoever controls the markup: set it only for
118
+ * markup you trust. Hosts may still block a script through the widget's Content-Security-Policy.
123
119
  *
124
120
  * @default false
125
121
  */
126
122
  allowInlineScripts?: boolean;
127
123
  /**
128
- * Completely bypass all HTML sanitization.
124
+ * Keep `data-tool-call` and `data-tool-args`. The bridge calls the tool `data-tool-call` names,
125
+ * with the JSON in `data-tool-args`, when the element (or anything inside it) is clicked, so
126
+ * whoever controls the markup chooses which tool a click calls and with what. Set it only when
127
+ * the markup's tool-call attributes are your own.
129
128
  *
130
- * **⚠️ DANGEROUS:** Only use with fully trusted, server-generated content.
131
- *
132
- * When `true`, no sanitization is applied:
133
- * - Script tags are preserved
134
- * - Event handlers are preserved
135
- * - All URL schemes are allowed
136
- * - No HTML escaping is performed
137
- *
138
- * This is useful for:
139
- * - Embedding trusted third-party widgets
140
- * - Complex interactive dashboards from trusted sources
141
- * - Content that was pre-sanitized server-side
129
+ * @default false
130
+ */
131
+ allowToolCallAttributes?: boolean;
132
+ /**
133
+ * Skip sanitization: the markup is used as written, as if `contentSecurity` were unset. Only for
134
+ * markup you fully trust.
142
135
  *
143
136
  * @default false
144
137
  */
145
138
  bypassSanitization?: boolean;
139
+ /**
140
+ * The longest markup, in characters, that is sanitized. Longer markup is not rendered: the render
141
+ * fails, and the tool call returns without its widget.
142
+ *
143
+ * @default 2097152 (2 Mi)
144
+ */
145
+ maxMarkupLength?: number;
146
146
  }
147
147
  /**
148
148
  * Helper functions available in template context.
@@ -222,7 +222,8 @@ export type TemplateBuilderFn<In = unknown, Out = unknown> = (ctx: TemplateConte
222
222
  * Widget serving mode determines how the widget HTML is delivered to the client.
223
223
  *
224
224
  * - `'auto'` (default): Automatically select mode based on client capabilities.
225
- * For OpenAI/ext-apps: uses 'inline'. For Claude: uses 'inline' with dual-payload.
225
+ * For ext-apps and Claude: uses 'inline'. For OpenAI: uses 'static', so no result carries
226
+ * the page: the OpenAI Apps SDK renders the widget from its `ui://` resource.
226
227
  * For unsupported clients (e.g., Gemini): skips UI entirely (returns JSON only).
227
228
  *
228
229
  * - `'inline'`: HTML embedded directly in tool response `_meta['ui/html']`.
@@ -236,9 +237,13 @@ export type TemplateBuilderFn<In = unknown, Out = unknown> = (ctx: TemplateConte
236
237
  * - `'hybrid'`: Shell (React runtime + bridge) pre-compiled at startup.
237
238
  * Component code transpiled per-request and delivered in `_meta['ui/component']`.
238
239
  *
239
- * - `'direct-url'`: HTTP endpoint on MCP server.
240
+ * - `'direct-url'`: Compiled at startup, as with `'static'`, and served on a route of the server
241
+ * (`directPath`). The `ui://` resource is a small page that frames that URL and relays the host's
242
+ * bridge messages. Results carry no page.
240
243
  *
241
- * - `'custom-url'`: Custom URL (CDN or external hosting).
244
+ * - `'custom-url'`: The page at `customWidgetUrl` (a CDN, a separate deployment) is the widget; the
245
+ * `ui://` resource frames it, as for `'direct-url'`. FrontMCP doesn't render `template` for it.
246
+ * Results carry no page.
242
247
  *
243
248
  * ## Claude target caveat (issue #447)
244
249
  *
@@ -293,8 +298,10 @@ export interface UITemplateConfig<In = unknown, Out = unknown> {
293
298
  * Supports multiple formats (auto-detected by renderer):
294
299
  * - Template builder function: `(ctx) => string` - receives input/output/helpers, returns HTML
295
300
  * - Static HTML/MDX string: `"<div>...</div>"` or `"# Title\n<Card />"`
296
- * - React component: `MyWidget` - receives props with input/output/helpers
301
+ * - React component: `MyWidget` - rendered on the server with `uiType: 'react'`, with props output/input/loading
297
302
  * - FileSource object: `{ file: './my-widget.tsx' }` - transpiled client-side via esm.sh CDN
303
+ *
304
+ * Not rendered for `servingMode: 'custom-url'`, whose page is at `customWidgetUrl`.
298
305
  */
299
306
  template: TemplateBuilderFn<In, Out> | string | ((props: any) => any) | FileSource;
300
307
  /**
@@ -303,47 +310,17 @@ export interface UITemplateConfig<In = unknown, Out = unknown> {
303
310
  */
304
311
  csp?: UIContentSecurityPolicy;
305
312
  /**
306
- * Content security and XSS protection settings.
307
- *
308
- * By default, FrontMCP sanitizes HTML content to prevent XSS attacks:
309
- * - Removes `<script>` tags and event handlers (onclick, onerror, etc.)
310
- * - Validates URL schemes in links (blocks `javascript:`, `data:`, `vbscript:`)
311
- *
312
- * These protections can be disabled for trusted content that requires
313
- * inline scripts, custom event handlers, or special URL schemes.
314
- *
315
- * ## Security Context
316
- *
317
- * **OpenAI/Claude run widgets in double-iframe isolation:**
318
- * - Outer sandbox iframe with restricted permissions
319
- * - Inner content iframe with CSP headers
320
- * - No access to parent window or cookies
321
- * - Network requests restricted by CSP
322
- *
323
- * This isolation provides defense-in-depth, making XSS less impactful.
324
- * However, XSS can still:
325
- * - Steal widget state/data
326
- * - Make unauthorized API calls (within CSP limits)
327
- * - Phish users with fake UI
328
- *
329
- * **Best Practice:** Keep XSS protection enabled unless you have a specific
330
- * need for inline scripts/handlers AND trust your content source completely.
313
+ * Sanitize the markup FrontMCP builds on the server from the template. Unset (the default), it is
314
+ * used as written; set, even to `{}`, scripts, event handlers and unsafe links are removed unless
315
+ * the flags allow them. See {@link UIContentSecurity}.
331
316
  *
332
317
  * @example
333
318
  * ```typescript
334
- * // Default: Full XSS protection
335
- * ui: { template: MyWidget }
336
- *
337
- * // Allow inline scripts for trusted dashboard
338
- * ui: {
339
- * template: TrustedDashboard,
340
- * contentSecurity: { allowInlineScripts: true }
341
- * }
342
- *
343
- * // Bypass all sanitization for fully trusted HTML
319
+ * // Render the HTML a tool returns, without its scripts, handlers or unsafe links
344
320
  * ui: {
345
- * template: FullyTrustedContent,
346
- * contentSecurity: { bypassSanitization: true }
321
+ * template: (ctx) => ctx.output.summaryHtml,
322
+ * escapeStringResults: false,
323
+ * contentSecurity: {},
347
324
  * }
348
325
  * ```
349
326
  */
@@ -451,8 +428,9 @@ export interface UITemplateConfig<In = unknown, Out = unknown> {
451
428
  * How the widget HTML should be served to the client.
452
429
  *
453
430
  * - `'auto'` (default): Automatically select mode based on client capabilities.
454
- * For OpenAI/ext-apps clients: uses `'inline'` with `_meta`.
455
- * For Claude clients: uses `'inline'` with dual-payload format.
431
+ * For ext-apps and Claude clients: uses `'inline'` with `_meta`.
432
+ * For OpenAI clients: uses `'static'`, so no result carries the page, not even the one a widget's
433
+ * own `window.openai.callTool()` gets: the OpenAI Apps SDK renders the widget from its `ui://` resource.
456
434
  * For unsupported clients (Gemini, unknown): skips UI (returns JSON only).
457
435
  *
458
436
  * - `'inline'`: HTML embedded directly in tool response `_meta['ui/html']`.
@@ -465,11 +443,16 @@ export interface UITemplateConfig<In = unknown, Out = unknown> {
465
443
  * - `'hybrid'`: Shell (React runtime + bridge) pre-compiled at startup.
466
444
  * Component code transpiled per-request and delivered in `_meta['ui/component']`.
467
445
  *
468
- * - `'direct-url'`: Served from MCP server's HTTP endpoint.
469
- * Avoids third-party cookie issues since widget loads from same domain.
446
+ * - `'direct-url'`: Compiled at startup, as with `'static'`, and served from the server's own
447
+ * HTTP route (`directPath`). The `ui://` resource is a small page that frames that URL, so the
448
+ * widget runs on the server's origin, and relays the host's bridge messages to it. The resource
449
+ * declares the URL's origin as a frame: `_meta.ui.csp.frameDomains` (MCP Apps) and
450
+ * `_meta['openai/widgetCSP'].frame_domains` (OpenAI Apps SDK). Results carry no page; the host
451
+ * sends each call's data to the widget.
470
452
  *
471
- * - `'custom-url'`: Served from a custom URL (CDN, external hosting).
472
- * Requires `customWidgetUrl` to be set.
453
+ * - `'custom-url'`: The page at `customWidgetUrl`, such as a CDN's, is the widget. The `ui://`
454
+ * resource frames it and declares its origin as a frame, as for `'direct-url'`. FrontMCP doesn't
455
+ * render `template` for this mode. Results carry no page.
473
456
  *
474
457
  * When unset, the FrontMCP SDK uses the app's `@App({ ui: { servingMode } })` default, else the
475
458
  * server's `@FrontMcp({ ui: { servingMode } })`, else `'auto'`.
@@ -478,39 +461,64 @@ export interface UITemplateConfig<In = unknown, Out = unknown> {
478
461
  */
479
462
  servingMode?: WidgetServingMode;
480
463
  /**
481
- * Custom URL for widget serving when `servingMode: 'custom-url'`.
482
- * The URL can include `{token}` placeholder which will be replaced with
483
- * the widget access token.
464
+ * The URL of the widget page when `servingMode: 'custom-url'`: an `https://` URL (or `http://` on
465
+ * localhost), such as a CDN's. The `ui://` resource frames it, declares its origin as a frame
466
+ * (`_meta.ui.csp.frameDomains`, `_meta['openai/widgetCSP'].frame_domains`) and relays the host's
467
+ * bridge messages to it.
468
+ *
469
+ * The page is yours: FrontMCP doesn't render `template` for it, and it must answer the host over
470
+ * the MCP Apps bridge (JSON-RPC over `postMessage` to its parent): a page built with `buildShell()`
471
+ * from `@frontmcp/uipack/shell` has FrontMCP's bridge, and `@modelcontextprotocol/ext-apps` works
472
+ * too. It must allow framing (no `X-Frame-Options: DENY`).
473
+ *
474
+ * The URL is used as written: FrontMCP adds no token or call data to it, and the page gets each
475
+ * call's result from the host. Startup refuses a URL with a `{…}` placeholder or credentials, a
476
+ * `'custom-url'` widget without this option, and this option on a widget not served `'custom-url'`.
484
477
  *
485
478
  * @example
486
479
  * ```typescript
487
- * customWidgetUrl: 'https://cdn.example.com/widgets/weather.html?token={token}'
480
+ * ui: { template: '', servingMode: 'custom-url', customWidgetUrl: 'https://cdn.example.com/widgets/weather.html' }
488
481
  * ```
489
482
  */
490
483
  customWidgetUrl?: string;
491
484
  /**
492
- * Path for direct URL serving when `servingMode: 'direct-url'`.
493
- * This path is appended to the MCP server's base URL.
485
+ * The route a `servingMode: 'direct-url'` widget is served on, from the server's root. Default:
486
+ * `/widgets/` and the tool name (characters other than letters, digits, `.`, `_`, `~` and `-`
487
+ * become `-`).
488
+ *
489
+ * The route answers `GET` without auth, in every auth mode, because a host loads it in a frame,
490
+ * which sends no credentials. It serves the page compiled at startup, which holds no call's data.
491
+ * The URL the `ui://` resource frames starts with `FRONTMCP_PUBLIC_URL` when set, else with the
492
+ * scheme and host the `resources/read` request came to. Startup refuses a path that collides with
493
+ * another route of the server or another widget, and a `directPath` on a widget not served
494
+ * `'direct-url'`.
494
495
  *
495
496
  * @example
496
497
  * ```typescript
497
- * directPath: '/widgets/weather'
498
- * // Results in: https://mcp-server.example.com/widgets/weather?token=...
498
+ * ui: { template: WeatherWidget, servingMode: 'direct-url', directPath: '/widgets/weather' }
499
+ * // ui://widget/get_weather.html frames https://mcp.example.com/widgets/weather
499
500
  * ```
500
501
  */
501
502
  directPath?: string;
502
503
  /**
503
- * Custom React components available in MDX templates.
504
- * These components can be used directly in MDX content without importing.
504
+ * React components for an MDX template. With `mdxComponents` set, a string template is MDX: it is
505
+ * compiled once with `@mdx-js/mdx` and rendered to HTML on the server with React for each render,
506
+ * with these components for the tags it names (and for HTML elements it overrides, such as `h1`).
507
+ * Its expressions read the call's data as `input`, `output` and `helpers`; the data is rendered as
508
+ * escaped text, never compiled. The page carries the rendered HTML, not React, so event handlers
509
+ * in the template don't run in the page.
510
+ *
511
+ * Needs `@mdx-js/mdx`, `react` and `react-dom` installed (and `remark-gfm` for
512
+ * `runtimeOptions.markdown.gfm`); `runtimeOptions.markdown.highlightTheme` highlights its fenced
513
+ * code. The template compiles at startup, so a syntax error or a missing package fails it. Only a
514
+ * string template can be MDX: a template function's result carries the call's data, and compiling
515
+ * it would run that data as code, so `mdxComponents` on a function fails startup.
505
516
  *
506
517
  * @example
507
518
  * ```typescript
508
519
  * ui: {
509
- * template: `# Weather\n<Alert type="info">Data loaded</Alert>`,
510
- * mdxComponents: {
511
- * Alert: ({ type, children }) => <div className={type}>{children}</div>,
512
- * Card: MyCardComponent,
513
- * }
520
+ * template: `# {output.title}\n\n<Alert type="info">{output.items.length} items</Alert>`,
521
+ * mdxComponents: { Alert },
514
522
  * }
515
523
  * ```
516
524
  */
@@ -561,72 +569,62 @@ export interface UITemplateConfig<In = unknown, Out = unknown> {
561
569
  */
562
570
  sandboxDomain?: string;
563
571
  /**
564
- * Enable client-side React hydration after server-side rendering (SSR).
565
- *
566
- * **Default: `false`**
567
- *
568
- * When `false` (default):
569
- * - SSR output is static HTML
570
- * - No React runtime is loaded on the client
571
- * - All interactivity is handled by the FrontMCP Bridge IIFE
572
- * - This prevents React Hydration Error #418 in MCP clients
572
+ * Render a React widget on the server and hydrate it in the page.
573
573
  *
574
- * When `true`:
575
- * - React/ReactDOM CDN scripts are included
576
- * - Client attempts to hydrate React components
577
- * - May cause hydration mismatch errors in some MCP clients (Claude, ChatGPT)
574
+ * Applies to a `{ file }` `.tsx` / `.jsx` widget. FrontMCP bundles the widget for the server too,
575
+ * renders it there with the call's data (the props the page's mount gives it: `output`, `input`,
576
+ * `loading`) inside `McpBridgeProvider`, and puts that HTML in the page's `#root`; the page's mount
577
+ * then calls `hydrateRoot` with the same props instead of rendering into an empty root. The widget
578
+ * shows its content before its script runs, and stays interactive.
578
579
  *
579
- * **Why disabled by default?**
580
- * MCP clients like Claude and ChatGPT render HTML in sandboxed iframes.
581
- * React hydration expects the client-rendered HTML to exactly match the
582
- * server-rendered HTML. Due to timing differences, random data, and
583
- * different execution contexts, mismatches frequently occur causing
584
- * React Error #418 ("Hydration failed...").
580
+ * Hydration expects the page to render what the server did: a widget whose first render reads
581
+ * `window`, the time or random values, or a host-specific hook value, may not match, and React then
582
+ * renders it again in the page (logging a recoverable error). A widget compiled at startup
583
+ * (`servingMode: 'static'`) is rendered with no data, in its `loading` state.
585
584
  *
586
- * The FrontMCP Bridge IIFE already provides all necessary interactivity
587
- * (tool calls via `data-tool-call`, state updates, loading states) without
588
- * requiring React hydration.
589
- *
590
- * **Only enable if:**
591
- * - You need React state management on the client
592
- * - You're sure your template produces deterministic HTML
593
- * - You've tested hydration works in your target MCP client
594
- *
595
- * @example
596
- * ```typescript
597
- * // Disable hydration (default behavior - recommended)
598
- * ui: {
599
- * template: MyReactComponent,
600
- * hydrate: false
601
- * }
585
+ * A component reference (`template: MyWidget`) has no source file to bundle for the page, so it
586
+ * can't be hydrated: render it on the server only with `uiType: 'react'`, or use `{ file }`. Any
587
+ * other template fails startup with `hydrate: true`.
602
588
  *
603
- * // Enable hydration (use with caution)
604
- * ui: {
605
- * template: MyStatefulComponent,
606
- * hydrate: true
607
- * }
608
- * ```
589
+ * @default false
609
590
  */
610
591
  hydrate?: boolean;
611
592
  /**
612
- * UI renderer type.
593
+ * The renderer, instead of the one detected from the template.
594
+ *
595
+ * - `'auto'` (default): detected. A string with both `<` and `>` is HTML, any other string is
596
+ * Markdown (MDX with `mdxComponents`), a function is an HTML builder unless it is a React
597
+ * component, and `{ file: '….tsx' }` is a React widget.
598
+ * - `'html'`: a string is used as written, even without `<` and `>`, and a function is called as
599
+ * an HTML builder, even with a capitalized name.
600
+ * - `'markdown'`: a string is converted from Markdown even with `<` and `>` (raw HTML is escaped),
601
+ * and so is a template function's string result.
602
+ * - `'mdx'`: a string is rendered as MDX on the server, with `mdxComponents` if set.
603
+ * - `'react'`: a component reference (function, class, `memo` / `forwardRef`) is rendered to
604
+ * static HTML on the server with React, with the widget props (`output`, `input`, `loading`);
605
+ * it can't be bundled for the page, so its handlers don't run there. A `{ file }` `.tsx` /
606
+ * `.jsx` widget is bundled for the page as with `'auto'`.
613
607
  *
614
- * - `'html'`: HTML with optional Handlebars
615
- * - `'react'`: React component (SSR)
616
- * - `'mdx'`: MDX template
617
- * - `'markdown'`: Pure Markdown
618
- * - `'auto'`: Auto-detect from template (default)
608
+ * A template the renderer can't take fails startup, such as `'react'` on a string or `'mdx'` on a
609
+ * function. `'react'`, `'mdx'` and `hydrate` load `react` and `react-dom` from the server's project.
619
610
  *
620
611
  * @default 'auto'
621
612
  */
622
613
  uiType?: 'html' | 'react' | 'mdx' | 'markdown' | 'auto';
623
614
  /**
624
- * Bundling mode.
615
+ * When the widget page is built.
616
+ *
617
+ * - `'static'`: once, without a call's data; the host sends each call's result to the page.
618
+ * - `'dynamic'`: for each call, with that call's data in the page.
625
619
  *
626
- * - `'static'`: Pre-compile widget shell, inject data at runtime (default)
627
- * - `'dynamic'`: Generate fresh HTML per tool invocation
620
+ * It picks the serving mode where `servingMode` leaves the choice open: with `servingMode` unset
621
+ * (an app or server default included) or `'auto'`, `'dynamic'` serves `'inline'` on every
622
+ * platform, and `'static'` serves `'static'`, or keeps an inherited `'hybrid'`, `'direct-url'` or
623
+ * `'custom-url'`, which build the page once as well. Startup refuses a tool's own `servingMode`
624
+ * that contradicts it: `'dynamic'` goes with `'inline'` only, and `'static'` with anything but
625
+ * `'inline'`.
628
626
  *
629
- * @default 'static'
627
+ * Unset (the default): `servingMode` alone decides, as before this option worked.
630
628
  */
631
629
  bundlingMode?: 'static' | 'dynamic';
632
630
  /**
@@ -657,43 +655,54 @@ export interface UITemplateConfig<In = unknown, Out = unknown> {
657
655
  * Runtime options for specific renderers.
658
656
  */
659
657
  runtimeOptions?: {
660
- /** Enable React hydration after SSR */
658
+ /** Same as {@link UITemplateConfig.hydrate}, which wins when both are set. */
661
659
  hydrate?: boolean;
662
- /** Markdown rendering options */
660
+ /**
661
+ * How a Markdown template (a string template without both `<` and `>`) is converted to HTML.
662
+ * Unset, the converter writes headings, paragraphs, lists, fenced code, emphasis, inline code
663
+ * and links. Setting `gfm` or `highlightTheme` on a template that isn't Markdown fails startup.
664
+ *
665
+ * @example
666
+ * ```typescript
667
+ * ui: {
668
+ * template: '| Region | Sales |\n|---|---:|\n| EU | 12 |\n\n```ts\nconst total = 12;\n```',
669
+ * runtimeOptions: { markdown: { gfm: true, highlightTheme: 'github' } },
670
+ * }
671
+ * ```
672
+ */
663
673
  markdown?: {
664
- /** Enable GitHub-flavored markdown */
674
+ /**
675
+ * GitHub-flavored Markdown: tables (with `:---:` alignment), task list items (`- [x]`),
676
+ * `~~strikethrough~~`, and links for bare `https://` and `www.` URLs. Raw HTML stays escaped.
677
+ */
665
678
  gfm?: boolean;
666
- /** Syntax highlighting theme */
679
+ /**
680
+ * Highlight fenced code that names its language (```` ```ts ````) on the server with highlight.js,
681
+ * an optional peer to install, and put this theme's stylesheet in the page: the name of a
682
+ * stylesheet in `highlight.js/styles`, such as `'github'`, `'github-dark'` or
683
+ * `'base16/dracula'`. A missing highlight.js or theme fails startup.
684
+ */
667
685
  highlightTheme?: string;
668
686
  };
669
687
  };
670
688
  /**
671
- * Prefix text shown before HTML in dual-payload responses (Claude).
689
+ * Turns on the "dual payload" for clients that render no widget, and is the text it opens with.
672
690
  *
673
- * When the output mode is `'dual-payload'` (auto-detected for Claude clients),
674
- * the tool response contains two TextContent blocks:
675
- * 1. Pure JSON data: `{"temperature": 72, ...}`
676
- * 2. Markdown-wrapped HTML: `{prefix}:\n\n```html\n<!DOCTYPE html>...\n```
691
+ * A `tools/call` from a client FrontMCP detects as `'claude'` (a Claude client that declares no
692
+ * MCP Apps support, so it renders no widget) gets two text blocks:
693
+ * 1. the JSON of the result, as without this option;
694
+ * 2. `{htmlResponsePrefix}:` and the page rendered with this call's data, in an `html` code fence,
695
+ * which such a client can show (an Artifact in Claude).
677
696
  *
678
- * This field controls the prefix text in block 2.
679
- *
680
- * @default 'Here is the visual result'
697
+ * The page is rendered for the call whatever `servingMode` is, and goes to that caller only. MCP
698
+ * Apps hosts, ChatGPT and every other client get the result as without this option, and so do
699
+ * calls a widget or an agent makes. Unset (the default): no client gets the second block. Startup
700
+ * refuses an empty value or one longer than 500 characters.
681
701
  *
682
702
  * @example
683
703
  * ```typescript
684
- * // Weather tool
685
- * ui: {
686
- * template: WeatherWidget,
687
- * htmlResponsePrefix: 'Here is the weather dashboard',
688
- * }
689
- * // Output block 2: "Here is the weather dashboard:\n\n```html\n..."
690
- *
691
- * // Stock tool
692
- * ui: {
693
- * template: StockWidget,
694
- * htmlResponsePrefix: 'Here is the stock information',
695
- * }
696
- * // Output block 2: "Here is the stock information:\n\n```html\n..."
704
+ * ui: { template: WeatherWidget, htmlResponsePrefix: 'Here is the weather dashboard' }
705
+ * // Block 2: "Here is the weather dashboard:\n\n```html\n<!DOCTYPE html>…\n```"
697
706
  * ```
698
707
  */
699
708
  htmlResponsePrefix?: string;
@@ -1 +1 @@
1
- {"version":3,"file":"ui-config.d.ts","sourceRoot":"","sources":["../../src/types/ui-config.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ui-config.d.ts","sourceRoot":"","sources":["../../src/types/ui-config.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AACvD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,6BAA6B,CAAC;AACrE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAEzD;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,gCAAgC;IAChC,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,yCAAyC;IACzC,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,gCAAgC;IAChC,MAAM,CAAC,EAAE,QAAQ,GAAG,QAAQ,GAAG,QAAQ,GAAG,QAAQ,GAAG,QAAQ,GAAG,QAAQ,CAAC;IACzE,0BAA0B;IAC1B,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,2BAA2B;IAC3B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,2BAA2B;IAC3B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,8CAA8C;IAC9C,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAMD;;;;;;;;;;;;;GAaG;AACH,MAAM,WAAW,uBAAuB;IACtC;;;;OAIG;IACH,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAE1B;;;;OAIG;IACH,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;CAC5B;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyCG;AACH,MAAM,WAAW,iBAAiB;IAChC;;;;;;;OAOG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAE3B;;;;;;;;OAQG;IACH,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAE7B;;;;;;;OAOG;IACH,uBAAuB,CAAC,EAAE,OAAO,CAAC;IAElC;;;;;OAKG;IACH,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAE7B;;;;;OAKG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAMD;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B;;;;OAIG;IACH,UAAU,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,MAAM,CAAC;IAErC;;;;OAIG;IACH,UAAU,EAAE,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,KAAK,MAAM,CAAC;IAE7D;;;;OAIG;IACH,cAAc,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,KAAK,MAAM,CAAC;IAE9D;;;OAGG;IACH,QAAQ,EAAE,CAAC,MAAM,CAAC,EAAE,MAAM,KAAK,MAAM,CAAC;IAEtC;;OAEG;IACH,SAAS,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,MAAM,CAAC;IAErC;;;;;;OAMG;IACH,IAAI,EAAE,CAAC,OAAO,EAAE,oBAAoB,EAAE,GAAG,MAAM,EAAE,OAAO,EAAE,KAAK,WAAW,CAAC;IAE3E;;;OAGG;IACH,WAAW,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,WAAW,CAAC;CAC9C;AAED;;;GAGG;AACH,MAAM,WAAW,eAAe,CAAC,EAAE,GAAG,OAAO,EAAE,GAAG,GAAG,OAAO;IAC1D;;OAEG;IACH,KAAK,EAAE,EAAE,CAAC;IAEV;;OAEG;IACH,MAAM,EAAE,GAAG,CAAC;IAEZ;;;OAGG;IACH,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAE5B;;OAEG;IACH,OAAO,EAAE,eAAe,CAAC;CAC1B;AAED;;;;GAIG;AACH,MAAM,MAAM,iBAAiB,CAAC,EAAE,GAAG,OAAO,EAAE,GAAG,GAAG,OAAO,IAAI,CAAC,GAAG,EAAE,eAAe,CAAC,EAAE,EAAE,GAAG,CAAC,KAAK,MAAM,GAAG,WAAW,CAAC;AAMrH;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AACH,MAAM,MAAM,iBAAiB,GACzB,MAAM,GACN,QAAQ,GACR,QAAQ,GACR,QAAQ,GACR,YAAY,GACZ,YAAY,CAAC;AAMjB;;GAEG;AACH,MAAM,MAAM,iBAAiB,GAAG,QAAQ,GAAG,YAAY,GAAG,KAAK,CAAC;AAEhE;;;;;GAKG;AACH,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,MAAM,CAAC;AAK9C;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,MAAM,WAAW,gBAAgB,CAAC,EAAE,GAAG,OAAO,EAAE,GAAG,GAAG,OAAO;IAC3D;;;;;;;;;;OAUG;IAEH,QAAQ,EAAE,iBAAiB,CAAC,EAAE,EAAE,GAAG,CAAC,GAAG,MAAM,GAAG,CAAC,CAAC,KAAK,EAAE,GAAG,KAAK,GAAG,CAAC,GAAG,UAAU,CAAC;IAEnF;;;OAGG;IACH,GAAG,CAAC,EAAE,uBAAuB,CAAC;IAE9B;;;;;;;;;;;;;;OAcG;IACH,eAAe,CAAC,EAAE,iBAAiB,CAAC;IAEpC;;;;;;;;;;;OAWG;IACH,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAE9B;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAE3B;;;;;;;;OAQG;IACH,WAAW,CAAC,EAAE,iBAAiB,CAAC;IAEhC;;;;;;;;OAQG;IACH,eAAe,CAAC,EAAE,eAAe,CAAC;IAElC;;;OAGG;IACH,SAAS,CAAC,EAAE,eAAe,CAAC;IAE5B;;;OAGG;IACH,SAAS,CAAC,EAAE,eAAe,CAAC;IAE5B;;;;;OAKG;IACH,WAAW,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAE9B;;;;;;;;;;;;;;;;;;OAkBG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IAErB;;;OAGG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAE3B;;;;;;;;;;;OAWG;IACH,gBAAgB,CAAC,EAAE;QACjB,gDAAgD;QAChD,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,uDAAuD;QACvD,OAAO,CAAC,EAAE,MAAM,CAAC;KAClB,CAAC;IAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAkCG;IACH,WAAW,CAAC,EAAE,iBAAiB,CAAC;IAEhC;;;;;;;;;;;;;;;;;;;OAmBG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IAEzB;;;;;;;;;;;;;;;;;OAiBG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB;;;;;;;;;;;;;;;;;;;;;OAqBG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAMpC;;;;;;;;;OASG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB;;;;;OAKG;IACH,kBAAkB,CAAC,EAAE;QACnB,oEAAoE;QACpE,eAAe,CAAC,EAAE,OAAO,CAAC;QAC1B,qDAAqD;QACrD,oBAAoB,CAAC,EAAE,OAAO,CAAC;KAChC,CAAC;IAEF;;;;;;;;;OASG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;IAExB;;;;;;;;;OASG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IAMvB;;;;;;;;;;;;;;;;;;;OAmBG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;IAMlB;;;;;;;;;;;;;;;;;;;;OAoBG;IACH,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,GAAG,KAAK,GAAG,UAAU,GAAG,MAAM,CAAC;IAExD;;;;;;;;;;;;;;OAcG;IACH,YAAY,CAAC,EAAE,QAAQ,GAAG,SAAS,CAAC;IAEpC;;;;;;;;;;;;;;;;;;;;;;OAsBG;IACH,YAAY,CAAC,EAAE,KAAK,GAAG,QAAQ,CAAC;IAEhC;;OAEG;IACH,cAAc,CAAC,EAAE;QACf,8EAA8E;QAC9E,OAAO,CAAC,EAAE,OAAO,CAAC;QAClB;;;;;;;;;;;;WAYG;QACH,QAAQ,CAAC,EAAE;YACT;;;eAGG;YACH,GAAG,CAAC,EAAE,OAAO,CAAC;YACd;;;;;eAKG;YACH,cAAc,CAAC,EAAE,MAAM,CAAC;SACzB,CAAC;KACH,CAAC;IAMF;;;;;;;;;;;;;;;;;;;OAmBG;IACH,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAM5B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA4CG;IACH,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IAErB;;;;;;;;;;;;;;;;;;;;;;;;;;;OA2BG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;IAE7C;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA4BG;IACH,iBAAiB,CAAC,EAAE,iBAAiB,CAAC;IAEtC;;;;;;;;;;;;;;;;;;;;OAoBG;IACH,WAAW,CAAC,EAAE,iBAAiB,CAAC;CACjC;AAED;;;GAGG;AACH,MAAM,MAAM,UAAU,CAAC,EAAE,GAAG,OAAO,EAAE,GAAG,GAAG,OAAO,IAAI,gBAAgB,CAAC,EAAE,EAAE,GAAG,CAAC,CAAC"}
@@ -45,24 +45,25 @@ type WidgetSizeValue = _WidgetSizeValue;
45
45
  */
46
46
  export type UIType = 'html' | 'react' | 'mdx' | 'markdown' | 'auto';
47
47
  /**
48
- * Widget bundling strategy.
48
+ * Widget bundling strategy (a tool's `ui.bundlingMode`).
49
49
  *
50
- * - `'static'`: Widget shell is pre-compiled at startup and cached.
51
- * Tool responses reference the cached widget via `ui://widget/{toolName}`.
52
- * Data is injected at runtime via FrontMCP Bridge. **Default mode.**
50
+ * - `'static'`: Widget page is built once, at startup, and served from `ui://widget/{toolName}`.
51
+ * Data reaches it at runtime via the FrontMCP Bridge.
53
52
  *
54
- * - `'dynamic'`: Fresh HTML is generated for each tool invocation.
55
- * Use for debugging or when template logic depends on invocation context.
53
+ * - `'dynamic'`: Fresh HTML is generated for each tool invocation, with that call's data
54
+ * (served `'inline'`).
55
+ *
56
+ * Unset, the tool's `servingMode` alone decides. See `UITemplateConfig.bundlingMode`.
56
57
  *
57
58
  * @example
58
59
  * ```typescript
59
- * // Static mode (default, recommended)
60
+ * // Built once at startup; the host sends each call's data
60
61
  * ui: {
61
62
  * template: WeatherWidget,
62
63
  * bundlingMode: 'static'
63
64
  * }
64
65
  *
65
- * // Dynamic mode (debugging, fresh per call)
66
+ * // Built for each call, with its data
66
67
  * ui: {
67
68
  * template: (ctx) => `<div>${ctx.output.debug}</div>`,
68
69
  * bundlingMode: 'dynamic'
@@ -110,7 +111,7 @@ export type ResourceMode = 'cdn' | 'inline';
110
111
  * - `'dual-payload'`: Return TWO TextContent blocks for Claude Artifacts.
111
112
  * Block 0: Pure JSON stringified data (for programmatic parsing).
112
113
  * Block 1: Markdown-wrapped HTML (```html...```) for visual rendering.
113
- * Uses cloudflare CDN for all resources (Claude sandbox restriction).
114
+ * The FrontMCP SDK sends it to a `'claude'` client when a tool sets `ui.htmlResponsePrefix`.
114
115
  *
115
116
  * @example
116
117
  * ```typescript
@@ -620,10 +621,8 @@ export interface WidgetConfig<Input = Record<string, unknown>, Output = unknown>
620
621
  /**
621
622
  * Bundling mode.
622
623
  *
623
- * - `'static'`: Pre-compile widget shell, inject data at runtime (default)
624
+ * - `'static'`: Pre-compile widget shell, inject data at runtime
624
625
  * - `'dynamic'`: Generate fresh HTML per tool invocation
625
- *
626
- * @default 'static'
627
626
  */
628
627
  bundlingMode?: BundlingMode;
629
628
  /**