@frontmcp/uipack 1.11.1 → 1.11.3

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
package/CLAUDE.md CHANGED
@@ -8,7 +8,7 @@ This is the React-free core package. For React components and hooks, use `@front
8
8
 
9
9
  **Key Principles:**
10
10
 
11
- - Zero React dependency
11
+ - Zero React dependency: uipack never imports React. Rendering a template on the server (`mdxComponents`, `uiType: 'react'`, `hydrate`) loads `react`, `react-dom` and `@mdx-js/mdx`, optional peers, from the server's project with `requireFromProject()`
12
12
  - HTML shell generation with CSP, data injection, bridge runtime
13
13
  - Pluggable import resolution (esm.sh default, custom resolvers)
14
14
  - NPM component loading (npm, file, URL, or inline function)
@@ -51,7 +51,7 @@ libs/uipack/src/
51
51
  ## Shell Builder
52
52
 
53
53
  ```typescript
54
- import { buildShell, buildCSPMetaTag } from '@frontmcp/uipack/shell';
54
+ import { buildCSPMetaTag, buildShell } from '@frontmcp/uipack/shell';
55
55
 
56
56
  // Build an HTML shell with CSP, data injection, and bridge
57
57
  const result = buildShell({
@@ -65,7 +65,7 @@ const result = buildShell({
65
65
  ## Import Resolver
66
66
 
67
67
  ```typescript
68
- import { resolveImports, createEsmShResolver } from '@frontmcp/uipack/resolver';
68
+ import { createEsmShResolver, resolveImports } from '@frontmcp/uipack/resolver';
69
69
 
70
70
  // Resolve NPM imports to CDN URLs
71
71
  const resolver = createEsmShResolver();
@@ -87,7 +87,7 @@ const component = await loadComponent({ source: 'url', href: 'https://cdn.exampl
87
87
  ## Custom Shell Templates
88
88
 
89
89
  ```typescript
90
- import { resolveShellTemplate, applyShellTemplate } from '@frontmcp/uipack/shell';
90
+ import { applyShellTemplate, resolveShellTemplate } from '@frontmcp/uipack/shell';
91
91
 
92
92
  // Resolve a custom shell from npm, URL, or inline source
93
93
  const template = await resolveShellTemplate({ source: 'npm', package: '@my-org/shell' });
package/README.md CHANGED
@@ -10,8 +10,10 @@
10
10
  npm install @frontmcp/uipack
11
11
  ```
12
12
 
13
- - No React. The one runtime dependency is `zod`; npm 7 and later also install the peer `@frontmcp/lazy-zod`, at the same version.
13
+ - No React dependency. The runtime dependencies are `zod` and `entities` (the HTML character reference decoder `sanitizeHtml()` uses); npm 7 and later also install the peer `@frontmcp/lazy-zod`, at the same version.
14
14
  - `esbuild` is an optional peer, needed only to bundle `.tsx` and `.jsx` widget files. Install it as a dependency, not a dev dependency, since it's loaded when a widget is rendered.
15
+ - `highlight.js` is an optional peer, needed only for `markdown.highlightTheme` (`ui.runtimeOptions.markdown.highlightTheme`). It's loaded from your project when a template uses it.
16
+ - `@mdx-js/mdx`, `react` and `react-dom` are optional peers, needed only to render an MDX template (`mdxComponents`) on the server, and `remark-gfm` for one with `markdown.gfm`. `react` and `react-dom` also render a component on the server (`uiType: 'react'`) and a widget to hydrate (`hydrate`). They're loaded from your project, so the template's components and the renderer share one React.
15
17
  - It runs on Node.js 24 or later.
16
18
 
17
19
  ## Usage
@@ -167,23 +169,26 @@ It fetches the `.d.ts` files of each import from esm.sh, following their own imp
167
169
 
168
170
  ### Adapters
169
171
 
170
- | Export | Description |
171
- | ----------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
172
- | `renderToolTemplate(options)` | Renders a template as a call does: `{ html, uiType, hash, size, meta }`. Options: `toolName`, `input`, `output`, `template`, `platformType?`, `resourceMode?`, `sizing?`, `csp?`, `escapeStringResults?`. |
173
- | `MCP_APPS_MIME_TYPE` | `"text/html;profile=mcp-app"`. |
172
+ | Export | Description |
173
+ | ------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
174
+ | `renderToolTemplate(options)` | Renders a template as a call does: `{ html, uiType, hash, size, meta }`. Options: `toolName`, `input`, `output`, `template`, `platformType?`, `resourceMode?`, `sizing?`, `csp?`, `escapeStringResults?`, `contentSecurity?` (sanitize the template's markup with `sanitizeHtml()`), `markdown?` (`{ gfm?, highlightTheme? }` for a Markdown or MDX template), `mdxComponents?` (a string template is MDX, rendered on the server with these components), `uiType?` (force the renderer: `'html'`, `'markdown'`, `'mdx'`, `'react'`), `hydrate?` (render a `{ file }` widget on the server and hydrate it). Throws for an option the template can't take; under tsx, `await prepareRenderOptions()` before rendering MDX. |
175
+ | `describeRenderOptionProblems(template, options)` | The rendering options set for a template that it can't take, one sentence each; `[]` when there are none. |
176
+ | `MCP_APPS_MIME_TYPE` | `"text/html;profile=mcp-app"`. |
174
177
 
175
178
  ### Shell
176
179
 
177
- | Export | Description |
178
- | ---------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
179
- | `buildShell(content, config)` | A page around `content`: `{ html, hash, size }`. `config`: `toolName` (required), `input`, `output`, `structuredContent`, `csp`, `includeBridge` (default `true`), `withShell` (default `true`; `false` returns the data script and the content only), `title`, `sizing`, `displayMode`, `customShell`. |
180
- | `` html`…` ``, `trustedHtml(markup)`, `isTrustedHtml(value)` | Build, mark and detect trusted markup. Values interpolated into `html` are escaped. |
181
- | `createTemplateHelpers()` | A new set of template helpers: `escapeHtml`, `formatDate`, `formatCurrency`, `uniqueId`, `jsonEmbed`, `html`, `trustedHtml`. |
182
- | `buildCSPDirectives(csp?)`, `buildCSPMetaTag(csp?)` | The page's policy: the default CDN origins plus `connectDomains` and `resourceDomains`. |
183
- | `buildDataInjectionScript({ toolName, input?, output?, structuredContent?, sizing?, displayMode? })` | The `<script>` that sets `window.__mcpToolName`, `__mcpToolInput`, `__mcpToolOutput` and `__mcpStructuredContent`. |
184
- | `resolveShellTemplate(source, options?)` | Async. Loads a custom shell from `{ inline }`, `{ url, timeout? }` or `{ npm, exportName?, version? }`, and validates it. |
185
- | `validateShellTemplate(template)`, `applyShellTemplate(template, values)` | Check and fill a custom shell's placeholders: `{{CONTENT}}` (required), `{{CSP}}`, `{{DATA}}`, `{{BRIDGE}}`, `{{TITLE}}`. |
186
- | `DEFAULT_CDN_DOMAINS` | The origins every page allows: jsDelivr, cdnjs, Google Fonts and esm.sh. |
180
+ | Export | Description |
181
+ | ---------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
182
+ | `buildShell(content, config)` | A page around `content`: `{ html, hash, size }`. `config`: `toolName` (required), `input`, `output`, `structuredContent`, `csp`, `includeBridge` (default `true`), `withShell` (default `true`; `false` returns the data script and the content only), `title`, `sizing`, `displayMode`, `customShell`. |
183
+ | `` html`…` ``, `trustedHtml(markup)`, `isTrustedHtml(value)` | Build, mark and detect trusted markup. Values interpolated into `html` are escaped. |
184
+ | `sanitizeHtml(markup, policy?)` | What `ui.contentSecurity` does. It reads markup as the browser's tokenizer does and writes every tag back out, each attribute value decoded once, checked and written back escaped. It removes `<script>`, `on*` handlers, `srcdoc`, `<object>`/`<embed>`/`<applet>`, `<base>`, `<meta>`, SVG animations, `data-tool-call`/`data-tool-args`, comments, URL attributes whose scheme isn't `http:`, `https:` or `mailto:`, `@import` in `<style>`, cross-origin `<link rel=stylesheet>`, and `position: fixed`/`sticky` in `style` attributes. `policy`: `allowInlineScripts`, `allowUnsafeLinks`, `allowToolCallAttributes`, `bypassSanitization`, `maxMarkupLength` (default `DEFAULT_MAX_MARKUP_LENGTH`, 2 Mi characters; longer markup throws). Linear time. |
185
+ | `describeContentSecurityProblems(policy)` | Why a value can't be a `ui.contentSecurity` policy (not a plain object, a flag that isn't boolean, an unknown key), one sentence each. |
186
+ | `createTemplateHelpers()` | A new set of template helpers: `escapeHtml`, `formatDate`, `formatCurrency`, `uniqueId`, `jsonEmbed`, `html`, `trustedHtml`. |
187
+ | `buildCSPDirectives(csp?)`, `buildCSPMetaTag(csp?)` | The page's policy: the default CDN origins plus `connectDomains` and `resourceDomains`. |
188
+ | `buildDataInjectionScript({ toolName, input?, output?, structuredContent?, sizing?, displayMode? })` | The `<script>` that sets `window.__mcpToolName`, `__mcpToolInput`, `__mcpToolOutput` and `__mcpStructuredContent`. |
189
+ | `resolveShellTemplate(source, options?)` | Async. Loads a custom shell from `{ inline }`, `{ url, timeout? }` or `{ npm, exportName?, version? }`, and validates it. |
190
+ | `validateShellTemplate(template)`, `applyShellTemplate(template, values)` | Check and fill a custom shell's placeholders: `{{CONTENT}}` (required), `{{CSP}}`, `{{DATA}}`, `{{BRIDGE}}`, `{{TITLE}}`. |
191
+ | `DEFAULT_CDN_DOMAINS` | The origins every page allows: jsDelivr, cdnjs, Google Fonts and esm.sh. |
187
192
 
188
193
  ### Resolver
189
194
 
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Syntax highlighting for `ui.runtimeOptions.markdown.highlightTheme`, with highlight.js.
3
+ *
4
+ * @packageDocumentation
5
+ */
6
+ /** A highlighter for one theme. */
7
+ export interface CodeHighlighter {
8
+ /** The code as highlighted HTML, or `undefined` for a language highlight.js doesn't know. */
9
+ highlight(code: string, language: string): string | undefined;
10
+ /** The theme's stylesheet. */
11
+ css: string;
12
+ }
13
+ /**
14
+ * The highlighter for a highlight.js theme, such as `'github'`, `'github-dark'` or
15
+ * `'base16/dracula'`: the name of a stylesheet in `highlight.js/styles`.
16
+ *
17
+ * @throws When highlight.js isn't installed or has no such theme
18
+ */
19
+ export declare function loadCodeHighlighter(theme: string): CodeHighlighter;
20
+ //# sourceMappingURL=code-highlight.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"code-highlight.d.ts","sourceRoot":"","sources":["../../src/adapters/code-highlight.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAUH,mCAAmC;AACnC,MAAM,WAAW,eAAe;IAC9B,6FAA6F;IAC7F,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;IAC9D,8BAA8B;IAC9B,GAAG,EAAE,MAAM,CAAC;CACb;AAMD;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,eAAe,CAYlE"}
@@ -9,9 +9,11 @@
9
9
  */
10
10
  export { MCP_APPS_MIME_TYPE, MCP_APPS_EXTENSION_ID } from './constants';
11
11
  export { resolveServingMode, type ResolveServingModeOptions, type ServingModeResult, type AdapterPlatformType, } from './serving-mode';
12
- export { buildToolResponseContent, type ToolResponseContent, type BuildToolResponseContentOptions, } from './response-builder';
12
+ export { buildToolResponseContent, buildHtmlResponseText, type ToolResponseContent, type BuildToolResponseContentOptions, } from './response-builder';
13
13
  export { isUIRenderFailure, type UIRenderFailure } from './render-failure';
14
- export { renderToolTemplate, warnIfComponentReference, type RenderToolTemplateOptions, type RenderToolTemplateResult, } from './template-renderer';
14
+ export { renderToolTemplate, warnIfComponentReference, type RenderToolTemplateOptions, type RenderToolTemplateResult, type MarkdownRenderOptions, } from './template-renderer';
15
+ export { describeRenderOptionProblems, prepareRenderOptions } from './render-options';
16
+ export { loadCodeHighlighter, type CodeHighlighter } from './code-highlight';
15
17
  export { detectContentType, type DetectedContentType } from './content-detector';
16
18
  export { buildChartHtml, buildMermaidHtml, buildPdfHtml, wrapDetectedContent } from './content-renderers';
17
19
  export { createDefaultBaseTemplate, type DefaultBaseTemplateOptions } from './base-template';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/adapters/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAKH,OAAO,EAAE,kBAAkB,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAKxE,OAAO,EACL,kBAAkB,EAClB,KAAK,yBAAyB,EAC9B,KAAK,iBAAiB,EACtB,KAAK,mBAAmB,GACzB,MAAM,gBAAgB,CAAC;AAKxB,OAAO,EACL,wBAAwB,EACxB,KAAK,mBAAmB,EACxB,KAAK,+BAA+B,GACrC,MAAM,oBAAoB,CAAC;AAK5B,OAAO,EAAE,iBAAiB,EAAE,KAAK,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAK3E,OAAO,EACL,kBAAkB,EAClB,wBAAwB,EACxB,KAAK,yBAAyB,EAC9B,KAAK,wBAAwB,GAC9B,MAAM,qBAAqB,CAAC;AAK7B,OAAO,EAAE,iBAAiB,EAAE,KAAK,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AACjF,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,YAAY,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAK1G,OAAO,EAAE,yBAAyB,EAAE,KAAK,0BAA0B,EAAE,MAAM,iBAAiB,CAAC;AAK7F,OAAO,EAAE,qBAAqB,EAAE,KAAK,OAAO,EAAE,KAAK,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAKzF,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/adapters/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAKH,OAAO,EAAE,kBAAkB,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAKxE,OAAO,EACL,kBAAkB,EAClB,KAAK,yBAAyB,EAC9B,KAAK,iBAAiB,EACtB,KAAK,mBAAmB,GACzB,MAAM,gBAAgB,CAAC;AAKxB,OAAO,EACL,wBAAwB,EACxB,qBAAqB,EACrB,KAAK,mBAAmB,EACxB,KAAK,+BAA+B,GACrC,MAAM,oBAAoB,CAAC;AAK5B,OAAO,EAAE,iBAAiB,EAAE,KAAK,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAK3E,OAAO,EACL,kBAAkB,EAClB,wBAAwB,EACxB,KAAK,yBAAyB,EAC9B,KAAK,wBAAwB,EAC7B,KAAK,qBAAqB,GAC3B,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,4BAA4B,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AACtF,OAAO,EAAE,mBAAmB,EAAE,KAAK,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAK7E,OAAO,EAAE,iBAAiB,EAAE,KAAK,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AACjF,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,YAAY,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAK1G,OAAO,EAAE,yBAAyB,EAAE,KAAK,0BAA0B,EAAE,MAAM,iBAAiB,CAAC;AAK7F,OAAO,EAAE,qBAAqB,EAAE,KAAK,OAAO,EAAE,KAAK,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAKzF,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC"}
package/adapters/index.js CHANGED
@@ -1,4 +1,8 @@
1
- "use strict";var __defProp=Object.defineProperty;var __getOwnPropDesc=Object.getOwnPropertyDescriptor;var __getOwnPropNames=Object.getOwnPropertyNames;var __hasOwnProp=Object.prototype.hasOwnProperty;var __export=(target,all)=>{for(var name in all)__defProp(target,name,{get:all[name],enumerable:true})};var __copyProps=(to,from,except,desc)=>{if(from&&typeof from==="object"||typeof from==="function"){for(let key of __getOwnPropNames(from))if(!__hasOwnProp.call(to,key)&&key!==except)__defProp(to,key,{get:()=>from[key],enumerable:!(desc=__getOwnPropDesc(from,key))||desc.enumerable})}return to};var __toCommonJS=mod=>__copyProps(__defProp({},"__esModule",{value:true}),mod);var adapters_exports={};__export(adapters_exports,{MCP_APPS_EXTENSION_ID:()=>MCP_APPS_EXTENSION_ID,MCP_APPS_MIME_TYPE:()=>MCP_APPS_MIME_TYPE,buildCDNInfoForUIType:()=>buildCDNInfoForUIType,buildChartHtml:()=>buildChartHtml,buildMermaidHtml:()=>buildMermaidHtml,buildPdfHtml:()=>buildPdfHtml,buildToolResponseContent:()=>buildToolResponseContent,createDefaultBaseTemplate:()=>createDefaultBaseTemplate,detectContentType:()=>detectContentType,detectUIType:()=>detectUIType,isUIRenderFailure:()=>isUIRenderFailure,renderToolTemplate:()=>renderToolTemplate,resolveServingMode:()=>resolveServingMode,warnIfComponentReference:()=>warnIfComponentReference,wrapDetectedContent:()=>wrapDetectedContent});module.exports=__toCommonJS(adapters_exports);var MCP_APPS_MIME_TYPE="text/html;profile=mcp-app";var MCP_APPS_EXTENSION_ID="io.modelcontextprotocol/ui";function resolveServingMode(options){const{configuredMode,platformType}=options;if(platformType==="gemini"){return{mode:configuredMode,supportsUI:false,effectiveMode:null,useStructuredContent:false,reason:"Gemini does not support MCP Apps UI"}}const effectiveMode=configuredMode==="auto"?"inline":configuredMode;if(effectiveMode==="hybrid"){const hybridCapable=platformType==="openai"||platformType==="ext-apps"||platformType==="cursor";if(!hybridCapable){return{mode:configuredMode,supportsUI:false,effectiveMode:null,useStructuredContent:false,reason:`Platform ${platformType} does not support hybrid serving mode`}}}const useStructuredContent=platformType==="openai"||platformType==="ext-apps"||platformType==="generic-mcp"||platformType==="codex"||platformType==="unknown";return{mode:configuredMode,supportsUI:true,effectiveMode,useStructuredContent,reason:`Platform ${platformType} supports UI, mode: ${effectiveMode}`}}function buildToolResponseContent(options){const{rawOutput,htmlContent,servingMode,useStructuredContent,platformType}=options;const textSummary=typeof rawOutput==="string"?rawOutput:rawOutput!==null&&rawOutput!==void 0?JSON.stringify(rawOutput):"";const result={content:[{type:"text",text:textSummary}]};if(useStructuredContent&&rawOutput!==null&&rawOutput!==void 0){const structured=typeof rawOutput==="object"&&!Array.isArray(rawOutput)?rawOutput:{value:rawOutput};result.structuredContent=structured}if(htmlContent&&servingMode==="inline"){const htmlKey="ui/html";result._meta={[htmlKey]:htmlContent};result.format="html"}return result}function isUIRenderFailure(result){if(typeof result!=="object"||result===null){return false}const rec=result;return typeof rec["reason"]==="string"&&!("meta"in rec)}var DEFAULT_CDN_REGISTRY={react:{packageName:"react",defaultVersion:"19.2.4",providers:{cloudflare:{url:"https://cdnjs.cloudflare.com/ajax/libs/react/19.2.4/umd/react.production.min.js",global:"React",crossorigin:"anonymous"},jsdelivr:{url:"https://cdn.jsdelivr.net/npm/react@19.2.4/umd/react.production.min.js",global:"React",crossorigin:"anonymous"},unpkg:{url:"https://unpkg.com/react@19.2.4/umd/react.production.min.js",global:"React",crossorigin:"anonymous"},"esm.sh":{url:"https://esm.sh/react@19.2.4",esm:true,crossorigin:"anonymous"}},preferredProviders:["esm.sh","cloudflare","jsdelivr","unpkg"],metadata:{description:"A JavaScript library for building user interfaces",homepage:"https://react.dev",license:"MIT"}},"react-dom":{packageName:"react-dom",defaultVersion:"19.2.4",providers:{cloudflare:{url:"https://cdnjs.cloudflare.com/ajax/libs/react-dom/19.2.4/umd/react-dom.production.min.js",global:"ReactDOM",crossorigin:"anonymous",peerDependencies:["react"]},jsdelivr:{url:"https://cdn.jsdelivr.net/npm/react-dom@19.2.4/umd/react-dom.production.min.js",global:"ReactDOM",crossorigin:"anonymous",peerDependencies:["react"]},unpkg:{url:"https://unpkg.com/react-dom@19.2.4/umd/react-dom.production.min.js",global:"ReactDOM",crossorigin:"anonymous",peerDependencies:["react"]},"esm.sh":{url:"https://esm.sh/react-dom@19.2.4",esm:true,crossorigin:"anonymous",peerDependencies:["react"]}},preferredProviders:["esm.sh","cloudflare","jsdelivr","unpkg"],metadata:{description:"React package for working with the DOM",homepage:"https://react.dev",license:"MIT"}},"chart.js":{packageName:"chart.js",defaultVersion:"4.5.1",providers:{cloudflare:{url:"https://cdnjs.cloudflare.com/ajax/libs/Chart.js/4.5.1/chart.umd.min.js",global:"Chart",crossorigin:"anonymous"},jsdelivr:{url:"https://cdn.jsdelivr.net/npm/chart.js@4.5.1/dist/chart.umd.min.js",global:"Chart",crossorigin:"anonymous"},unpkg:{url:"https://unpkg.com/chart.js@4.5.1/dist/chart.umd.min.js",global:"Chart",crossorigin:"anonymous"}},preferredProviders:["esm.sh","cloudflare","jsdelivr","unpkg"],metadata:{description:"Simple yet flexible JavaScript charting library",homepage:"https://www.chartjs.org",license:"MIT"}},d3:{packageName:"d3",defaultVersion:"7.9.0",providers:{cloudflare:{url:"https://cdnjs.cloudflare.com/ajax/libs/d3/7.9.0/d3.min.js",integrity:"sha512-vc58qvvBdrDR4etbxMdlTt4GBQk1qjvyORR2nrsPsFPyrs+/u5c3+1Ct6upOgdZoIl7eq6k3a1UPDSNAQi/32A==",global:"d3",crossorigin:"anonymous"},jsdelivr:{url:"https://cdn.jsdelivr.net/npm/d3@7.9.0/dist/d3.min.js",global:"d3",crossorigin:"anonymous"},unpkg:{url:"https://unpkg.com/d3@7.9.0/dist/d3.min.js",global:"d3",crossorigin:"anonymous"}},preferredProviders:["esm.sh","cloudflare","jsdelivr","unpkg"],metadata:{description:"Data-Driven Documents",homepage:"https://d3js.org",license:"ISC"}},lodash:{packageName:"lodash",defaultVersion:"4.17.21",providers:{cloudflare:{url:"https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.21/lodash.min.js",integrity:"sha512-WFN04846sdKMIP5LKNphMaWzU7YpMyCU245etK3g/2ARYbPK9Ub18eG+ljU96qKRCWh+quCY7yefSmlkQw1ANQ==",global:"_",crossorigin:"anonymous"},jsdelivr:{url:"https://cdn.jsdelivr.net/npm/lodash@4.17.21/lodash.min.js",global:"_",crossorigin:"anonymous"},unpkg:{url:"https://unpkg.com/lodash@4.17.21/lodash.min.js",global:"_",crossorigin:"anonymous"}},preferredProviders:["esm.sh","cloudflare","jsdelivr","unpkg"],metadata:{description:"A modern JavaScript utility library",homepage:"https://lodash.com",license:"MIT"}}};function lookupPackage(packageName,registry=DEFAULT_CDN_REGISTRY){return registry[packageName]}var IMPORT_PATTERNS={named:/import\s*\{([^}]+)\}\s*from\s*['"]([^'"]+)['"]/g,default:/import\s+(\w+)\s+from\s*['"]([^'"]+)['"]/g,namespace:/import\s*\*\s*as\s+(\w+)\s+from\s*['"]([^'"]+)['"]/g,sideEffect:/import\s*['"]([^'"]+)['"]/g,dynamic:/(?:await\s+)?import\s*\(\s*['"]([^'"]+)['"]\s*\)/g,defaultAndNamed:/import\s+(\w+)\s*,\s*\{([^}]+)\}\s*from\s*['"]([^'"]+)['"]/g,defaultAndNamespace:/import\s+(\w+)\s*,\s*\*\s*as\s+(\w+)\s+from\s*['"]([^'"]+)['"]/g,reExport:/export\s*\{[^}]+\}\s*from\s*['"]([^'"]+)['"]/g,reExportAll:/export\s*\*\s*from\s*['"]([^'"]+)['"]/g};function parseNamedImports(namedString){return namedString.split(",").map(s=>s.trim()).filter(s=>s.length>0).map(s=>{const asMatch=s.match(/^(\w+)\s+as\s+\w+$/);return asMatch?asMatch[1]:s})}function isRelativeImport(specifier){return specifier.startsWith("./")||specifier.startsWith("../")||specifier.startsWith("/")}function isExternalImport(specifier){return!isRelativeImport(specifier)&&!specifier.startsWith("#")}function getPackageName(specifier){if(specifier.startsWith("@")){const parts=specifier.split("/");if(parts.length>=2){return`${parts[0]}/${parts[1]}`}return specifier}const firstSlash=specifier.indexOf("/");return firstSlash===-1?specifier:specifier.slice(0,firstSlash)}function getLineNumber(source,index){let line=1;for(let i=0;i<index&&i<source.length;i++){if(source[i]==="\n"){line++}}return line}function getColumnNumber(source,index){let column=0;for(let i=index-1;i>=0&&source[i]!=="\n";i--){column++}return column}function parseImports(source){const imports=[];const seenStatements=new Set;const addImport=imp=>{const key=`${imp.type}:${imp.specifier}:${imp.statement}`;if(!seenStatements.has(key)){seenStatements.add(key);imports.push(imp)}};let match;const defaultAndNamedRegex=new RegExp(IMPORT_PATTERNS.defaultAndNamed.source,"g");while((match=defaultAndNamedRegex.exec(source))!==null){const[statement,defaultName,namedString,specifier]=match;addImport({statement,specifier,type:"default",defaultImport:defaultName,namedImports:parseNamedImports(namedString),line:getLineNumber(source,match.index),column:getColumnNumber(source,match.index)})}const defaultAndNamespaceRegex=new RegExp(IMPORT_PATTERNS.defaultAndNamespace.source,"g");while((match=defaultAndNamespaceRegex.exec(source))!==null){const[statement,defaultName,namespaceName,specifier]=match;addImport({statement,specifier,type:"default",defaultImport:defaultName,namespaceImport:namespaceName,line:getLineNumber(source,match.index),column:getColumnNumber(source,match.index)})}const namedRegex=new RegExp(IMPORT_PATTERNS.named.source,"g");while((match=namedRegex.exec(source))!==null){const[statement,namedString,specifier]=match;addImport({statement,specifier,type:"named",namedImports:parseNamedImports(namedString),line:getLineNumber(source,match.index),column:getColumnNumber(source,match.index)})}const defaultRegex=new RegExp(IMPORT_PATTERNS.default.source,"g");while((match=defaultRegex.exec(source))!==null){const[statement,defaultName,specifier]=match;const afterMatch=source.slice(match.index+match[0].length-specifier.length-2);if(afterMatch.startsWith(","))continue;addImport({statement,specifier,type:"default",defaultImport:defaultName,line:getLineNumber(source,match.index),column:getColumnNumber(source,match.index)})}const namespaceRegex=new RegExp(IMPORT_PATTERNS.namespace.source,"g");while((match=namespaceRegex.exec(source))!==null){const[statement,namespaceName,specifier]=match;addImport({statement,specifier,type:"namespace",namespaceImport:namespaceName,line:getLineNumber(source,match.index),column:getColumnNumber(source,match.index)})}const sideEffectRegex=new RegExp(IMPORT_PATTERNS.sideEffect.source,"g");while((match=sideEffectRegex.exec(source))!==null){const[statement,specifier]=match;const beforeMatch=source.slice(Math.max(0,match.index-50),match.index);if(beforeMatch.includes("from"))continue;addImport({statement,specifier,type:"side-effect",line:getLineNumber(source,match.index),column:getColumnNumber(source,match.index)})}const dynamicRegex=new RegExp(IMPORT_PATTERNS.dynamic.source,"g");while((match=dynamicRegex.exec(source))!==null){const[statement,specifier]=match;addImport({statement,specifier,type:"dynamic",line:getLineNumber(source,match.index),column:getColumnNumber(source,match.index)})}const reExportRegex=new RegExp(IMPORT_PATTERNS.reExport.source,"g");while((match=reExportRegex.exec(source))!==null){const[statement,specifier]=match;addImport({statement,specifier,type:"named",line:getLineNumber(source,match.index),column:getColumnNumber(source,match.index)})}const reExportAllRegex=new RegExp(IMPORT_PATTERNS.reExportAll.source,"g");while((match=reExportAllRegex.exec(source))!==null){const[statement,specifier]=match;addImport({statement,specifier,type:"namespace",line:getLineNumber(source,match.index),column:getColumnNumber(source,match.index)})}const externalImports=imports.filter(imp=>isExternalImport(imp.specifier));const relativeImports=imports.filter(imp=>isRelativeImport(imp.specifier));const externalPackages=[...new Set(externalImports.map(imp=>getPackageName(imp.specifier)))];return{imports,externalImports,relativeImports,externalPackages}}var DEFAULT_FALLBACK_CDN="https://esm.sh";function createEsmShResolver(options={}){const{fallbackCdnBase=DEFAULT_FALLBACK_CDN,registry=DEFAULT_CDN_REGISTRY,providerOrder=["esm.sh","cloudflare","jsdelivr","unpkg"]}=options;return{resolve(specifier,_context){if(specifier.startsWith("./")||specifier.startsWith("../")||specifier.startsWith("/")){return null}if(specifier.startsWith("node:")||specifier.startsWith("#")){return null}const pkgName=getPackageName(specifier);const entry=lookupPackage(pkgName,registry);if(entry){for(const provider of providerOrder){const config=entry.providers[provider];if(config?.url){const subpath=specifier.slice(pkgName.length);if(subpath&&config.url.includes("esm.sh/")){return{value:config.url+subpath,type:"url",integrity:config.integrity}}if(subpath){return{value:`${fallbackCdnBase}/${specifier}`,type:"url"}}if(config.global&&!config.esm){return{value:config.url,type:"url",integrity:config.integrity}}return{value:config.url,type:"url",integrity:config.integrity}}}}return{value:`${fallbackCdnBase}/${specifier}`,type:"url"}}}}function escapeHtml(str){if(str===null||str===void 0){return""}const s=String(str);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")}var SCRIPT_UNSAFE_CHARACTERS=/[<>&\u2028\u2029]/g;function toUnicodeEscape(character){return`\\u${character.charCodeAt(0).toString(16).padStart(4,"0")}`}function safeJsonForScript(value){if(value===void 0){return"null"}try{const jsonString=JSON.stringify(value,(_key,val)=>{if(typeof val==="bigint"){return val.toString()}return val});if(jsonString===void 0){return"null"}return jsonString.replace(SCRIPT_UNSAFE_CHARACTERS,toUnicodeEscape)}catch{return'{"error":"Value could not be serialized"}'}}function createImportMapFromResolved(resolved){const imports={};const integrity={};for(const[specifier,res]of Object.entries(resolved)){if(res.type==="url"){imports[specifier]=res.value;if(res.integrity){integrity[res.value]=res.integrity}}}return{imports,integrity:Object.keys(integrity).length>0?integrity:void 0}}function generateImportMapScriptTag(map){const json=JSON.stringify(map,null,2).replace(/<\//g,"<\\/");return`<script type="importmap">
1
+ "use strict";var __defProp=Object.defineProperty;var __getOwnPropDesc=Object.getOwnPropertyDescriptor;var __getOwnPropNames=Object.getOwnPropertyNames;var __hasOwnProp=Object.prototype.hasOwnProperty;var __export=(target,all)=>{for(var name in all)__defProp(target,name,{get:all[name],enumerable:true})};var __copyProps=(to,from,except,desc)=>{if(from&&typeof from==="object"||typeof from==="function"){for(let key of __getOwnPropNames(from))if(!__hasOwnProp.call(to,key)&&key!==except)__defProp(to,key,{get:()=>from[key],enumerable:!(desc=__getOwnPropDesc(from,key))||desc.enumerable})}return to};var __toCommonJS=mod=>__copyProps(__defProp({},"__esModule",{value:true}),mod);var adapters_exports={};__export(adapters_exports,{MCP_APPS_EXTENSION_ID:()=>MCP_APPS_EXTENSION_ID,MCP_APPS_MIME_TYPE:()=>MCP_APPS_MIME_TYPE,buildCDNInfoForUIType:()=>buildCDNInfoForUIType,buildChartHtml:()=>buildChartHtml,buildHtmlResponseText:()=>buildHtmlResponseText,buildMermaidHtml:()=>buildMermaidHtml,buildPdfHtml:()=>buildPdfHtml,buildToolResponseContent:()=>buildToolResponseContent,createDefaultBaseTemplate:()=>createDefaultBaseTemplate,describeRenderOptionProblems:()=>describeRenderOptionProblems,detectContentType:()=>detectContentType,detectUIType:()=>detectUIType,isUIRenderFailure:()=>isUIRenderFailure,loadCodeHighlighter:()=>loadCodeHighlighter,prepareRenderOptions:()=>prepareRenderOptions,renderToolTemplate:()=>renderToolTemplate,resolveServingMode:()=>resolveServingMode,warnIfComponentReference:()=>warnIfComponentReference,wrapDetectedContent:()=>wrapDetectedContent});module.exports=__toCommonJS(adapters_exports);var MCP_APPS_MIME_TYPE="text/html;profile=mcp-app";var MCP_APPS_EXTENSION_ID="io.modelcontextprotocol/ui";function resolveServingMode(options){const{configuredMode,platformType}=options;if(platformType==="gemini"){return{mode:configuredMode,supportsUI:false,effectiveMode:null,useStructuredContent:false,reason:"Gemini does not support MCP Apps UI"}}const autoMode=platformType==="openai"?"static":"inline";const effectiveMode=configuredMode==="auto"?autoMode:configuredMode;if(effectiveMode==="hybrid"){const hybridCapable=platformType==="openai"||platformType==="ext-apps"||platformType==="cursor";if(!hybridCapable){return{mode:configuredMode,supportsUI:false,effectiveMode:null,useStructuredContent:false,reason:`Platform ${platformType} does not support hybrid serving mode`}}}const useStructuredContent=platformType==="openai"||platformType==="ext-apps"||platformType==="generic-mcp"||platformType==="codex"||platformType==="unknown";return{mode:configuredMode,supportsUI:true,effectiveMode,useStructuredContent,reason:`Platform ${platformType} supports UI, mode: ${effectiveMode}`}}function buildToolResponseContent(options){const{rawOutput,htmlContent,servingMode,useStructuredContent,platformType}=options;const textSummary=typeof rawOutput==="string"?rawOutput:rawOutput!==null&&rawOutput!==void 0?JSON.stringify(rawOutput):"";const result={content:[{type:"text",text:textSummary}]};if(useStructuredContent&&rawOutput!==null&&rawOutput!==void 0){const structured=typeof rawOutput==="object"&&!Array.isArray(rawOutput)?rawOutput:{value:rawOutput};result.structuredContent=structured}if(htmlContent&&servingMode==="inline"){const htmlKey="ui/html";result._meta={[htmlKey]:htmlContent};result.format="html"}return result}function buildHtmlResponseText(prefix,html2){const longestRun=Math.max(0,...(html2.match(/`+/g)??[]).map(run=>run.length));const fence="`".repeat(Math.max(3,longestRun+1));return`${prefix}:
2
+
3
+ ${fence}html
4
+ ${html2}
5
+ ${fence}`}function isUIRenderFailure(result){if(typeof result!=="object"||result===null){return false}const rec=result;return typeof rec["reason"]==="string"&&!("meta"in rec)}var DEFAULT_CDN_REGISTRY={react:{packageName:"react",defaultVersion:"19.2.4",providers:{cloudflare:{url:"https://cdnjs.cloudflare.com/ajax/libs/react/19.2.4/umd/react.production.min.js",global:"React",crossorigin:"anonymous"},jsdelivr:{url:"https://cdn.jsdelivr.net/npm/react@19.2.4/umd/react.production.min.js",global:"React",crossorigin:"anonymous"},unpkg:{url:"https://unpkg.com/react@19.2.4/umd/react.production.min.js",global:"React",crossorigin:"anonymous"},"esm.sh":{url:"https://esm.sh/react@19.2.4",esm:true,crossorigin:"anonymous"}},preferredProviders:["esm.sh","cloudflare","jsdelivr","unpkg"],metadata:{description:"A JavaScript library for building user interfaces",homepage:"https://react.dev",license:"MIT"}},"react-dom":{packageName:"react-dom",defaultVersion:"19.2.4",providers:{cloudflare:{url:"https://cdnjs.cloudflare.com/ajax/libs/react-dom/19.2.4/umd/react-dom.production.min.js",global:"ReactDOM",crossorigin:"anonymous",peerDependencies:["react"]},jsdelivr:{url:"https://cdn.jsdelivr.net/npm/react-dom@19.2.4/umd/react-dom.production.min.js",global:"ReactDOM",crossorigin:"anonymous",peerDependencies:["react"]},unpkg:{url:"https://unpkg.com/react-dom@19.2.4/umd/react-dom.production.min.js",global:"ReactDOM",crossorigin:"anonymous",peerDependencies:["react"]},"esm.sh":{url:"https://esm.sh/react-dom@19.2.4",esm:true,crossorigin:"anonymous",peerDependencies:["react"]}},preferredProviders:["esm.sh","cloudflare","jsdelivr","unpkg"],metadata:{description:"React package for working with the DOM",homepage:"https://react.dev",license:"MIT"}},"chart.js":{packageName:"chart.js",defaultVersion:"4.5.1",providers:{cloudflare:{url:"https://cdnjs.cloudflare.com/ajax/libs/Chart.js/4.5.1/chart.umd.min.js",global:"Chart",crossorigin:"anonymous"},jsdelivr:{url:"https://cdn.jsdelivr.net/npm/chart.js@4.5.1/dist/chart.umd.min.js",global:"Chart",crossorigin:"anonymous"},unpkg:{url:"https://unpkg.com/chart.js@4.5.1/dist/chart.umd.min.js",global:"Chart",crossorigin:"anonymous"}},preferredProviders:["esm.sh","cloudflare","jsdelivr","unpkg"],metadata:{description:"Simple yet flexible JavaScript charting library",homepage:"https://www.chartjs.org",license:"MIT"}},d3:{packageName:"d3",defaultVersion:"7.9.0",providers:{cloudflare:{url:"https://cdnjs.cloudflare.com/ajax/libs/d3/7.9.0/d3.min.js",integrity:"sha512-vc58qvvBdrDR4etbxMdlTt4GBQk1qjvyORR2nrsPsFPyrs+/u5c3+1Ct6upOgdZoIl7eq6k3a1UPDSNAQi/32A==",global:"d3",crossorigin:"anonymous"},jsdelivr:{url:"https://cdn.jsdelivr.net/npm/d3@7.9.0/dist/d3.min.js",global:"d3",crossorigin:"anonymous"},unpkg:{url:"https://unpkg.com/d3@7.9.0/dist/d3.min.js",global:"d3",crossorigin:"anonymous"}},preferredProviders:["esm.sh","cloudflare","jsdelivr","unpkg"],metadata:{description:"Data-Driven Documents",homepage:"https://d3js.org",license:"ISC"}},lodash:{packageName:"lodash",defaultVersion:"4.17.21",providers:{cloudflare:{url:"https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.21/lodash.min.js",integrity:"sha512-WFN04846sdKMIP5LKNphMaWzU7YpMyCU245etK3g/2ARYbPK9Ub18eG+ljU96qKRCWh+quCY7yefSmlkQw1ANQ==",global:"_",crossorigin:"anonymous"},jsdelivr:{url:"https://cdn.jsdelivr.net/npm/lodash@4.17.21/lodash.min.js",global:"_",crossorigin:"anonymous"},unpkg:{url:"https://unpkg.com/lodash@4.17.21/lodash.min.js",global:"_",crossorigin:"anonymous"}},preferredProviders:["esm.sh","cloudflare","jsdelivr","unpkg"],metadata:{description:"A modern JavaScript utility library",homepage:"https://lodash.com",license:"MIT"}}};function lookupPackage(packageName2,registry=DEFAULT_CDN_REGISTRY){return registry[packageName2]}var IMPORT_PATTERNS={named:/import\s*\{([^}]+)\}\s*from\s*['"]([^'"]+)['"]/g,default:/import\s+(\w+)\s+from\s*['"]([^'"]+)['"]/g,namespace:/import\s*\*\s*as\s+(\w+)\s+from\s*['"]([^'"]+)['"]/g,sideEffect:/import\s*['"]([^'"]+)['"]/g,dynamic:/(?:await\s+)?import\s*\(\s*['"]([^'"]+)['"]\s*\)/g,defaultAndNamed:/import\s+(\w+)\s*,\s*\{([^}]+)\}\s*from\s*['"]([^'"]+)['"]/g,defaultAndNamespace:/import\s+(\w+)\s*,\s*\*\s*as\s+(\w+)\s+from\s*['"]([^'"]+)['"]/g,reExport:/export\s*\{[^}]+\}\s*from\s*['"]([^'"]+)['"]/g,reExportAll:/export\s*\*\s*from\s*['"]([^'"]+)['"]/g};function parseNamedImports(namedString){return namedString.split(",").map(s=>s.trim()).filter(s=>s.length>0).map(s=>{const asMatch=s.match(/^(\w+)\s+as\s+\w+$/);return asMatch?asMatch[1]:s})}function isRelativeImport(specifier){return specifier.startsWith("./")||specifier.startsWith("../")||specifier.startsWith("/")}function isExternalImport(specifier){return!isRelativeImport(specifier)&&!specifier.startsWith("#")}function getPackageName(specifier){if(specifier.startsWith("@")){const parts=specifier.split("/");if(parts.length>=2){return`${parts[0]}/${parts[1]}`}return specifier}const firstSlash=specifier.indexOf("/");return firstSlash===-1?specifier:specifier.slice(0,firstSlash)}function getLineNumber(source,index){let line=1;for(let i=0;i<index&&i<source.length;i++){if(source[i]==="\n"){line++}}return line}function getColumnNumber(source,index){let column=0;for(let i=index-1;i>=0&&source[i]!=="\n";i--){column++}return column}function parseImports(source){const imports=[];const seenStatements=new Set;const addImport=imp=>{const key=`${imp.type}:${imp.specifier}:${imp.statement}`;if(!seenStatements.has(key)){seenStatements.add(key);imports.push(imp)}};let match;const defaultAndNamedRegex=new RegExp(IMPORT_PATTERNS.defaultAndNamed.source,"g");while((match=defaultAndNamedRegex.exec(source))!==null){const[statement,defaultName,namedString,specifier]=match;addImport({statement,specifier,type:"default",defaultImport:defaultName,namedImports:parseNamedImports(namedString),line:getLineNumber(source,match.index),column:getColumnNumber(source,match.index)})}const defaultAndNamespaceRegex=new RegExp(IMPORT_PATTERNS.defaultAndNamespace.source,"g");while((match=defaultAndNamespaceRegex.exec(source))!==null){const[statement,defaultName,namespaceName,specifier]=match;addImport({statement,specifier,type:"default",defaultImport:defaultName,namespaceImport:namespaceName,line:getLineNumber(source,match.index),column:getColumnNumber(source,match.index)})}const namedRegex=new RegExp(IMPORT_PATTERNS.named.source,"g");while((match=namedRegex.exec(source))!==null){const[statement,namedString,specifier]=match;addImport({statement,specifier,type:"named",namedImports:parseNamedImports(namedString),line:getLineNumber(source,match.index),column:getColumnNumber(source,match.index)})}const defaultRegex=new RegExp(IMPORT_PATTERNS.default.source,"g");while((match=defaultRegex.exec(source))!==null){const[statement,defaultName,specifier]=match;const afterMatch=source.slice(match.index+match[0].length-specifier.length-2);if(afterMatch.startsWith(","))continue;addImport({statement,specifier,type:"default",defaultImport:defaultName,line:getLineNumber(source,match.index),column:getColumnNumber(source,match.index)})}const namespaceRegex=new RegExp(IMPORT_PATTERNS.namespace.source,"g");while((match=namespaceRegex.exec(source))!==null){const[statement,namespaceName,specifier]=match;addImport({statement,specifier,type:"namespace",namespaceImport:namespaceName,line:getLineNumber(source,match.index),column:getColumnNumber(source,match.index)})}const sideEffectRegex=new RegExp(IMPORT_PATTERNS.sideEffect.source,"g");while((match=sideEffectRegex.exec(source))!==null){const[statement,specifier]=match;const beforeMatch=source.slice(Math.max(0,match.index-50),match.index);if(beforeMatch.includes("from"))continue;addImport({statement,specifier,type:"side-effect",line:getLineNumber(source,match.index),column:getColumnNumber(source,match.index)})}const dynamicRegex=new RegExp(IMPORT_PATTERNS.dynamic.source,"g");while((match=dynamicRegex.exec(source))!==null){const[statement,specifier]=match;addImport({statement,specifier,type:"dynamic",line:getLineNumber(source,match.index),column:getColumnNumber(source,match.index)})}const reExportRegex=new RegExp(IMPORT_PATTERNS.reExport.source,"g");while((match=reExportRegex.exec(source))!==null){const[statement,specifier]=match;addImport({statement,specifier,type:"named",line:getLineNumber(source,match.index),column:getColumnNumber(source,match.index)})}const reExportAllRegex=new RegExp(IMPORT_PATTERNS.reExportAll.source,"g");while((match=reExportAllRegex.exec(source))!==null){const[statement,specifier]=match;addImport({statement,specifier,type:"namespace",line:getLineNumber(source,match.index),column:getColumnNumber(source,match.index)})}const externalImports=imports.filter(imp=>isExternalImport(imp.specifier));const relativeImports=imports.filter(imp=>isRelativeImport(imp.specifier));const externalPackages=[...new Set(externalImports.map(imp=>getPackageName(imp.specifier)))];return{imports,externalImports,relativeImports,externalPackages}}var DEFAULT_FALLBACK_CDN="https://esm.sh";function createEsmShResolver(options={}){const{fallbackCdnBase=DEFAULT_FALLBACK_CDN,registry=DEFAULT_CDN_REGISTRY,providerOrder=["esm.sh","cloudflare","jsdelivr","unpkg"]}=options;return{resolve(specifier,_context){if(specifier.startsWith("./")||specifier.startsWith("../")||specifier.startsWith("/")){return null}if(specifier.startsWith("node:")||specifier.startsWith("#")){return null}const pkgName=getPackageName(specifier);const entry=lookupPackage(pkgName,registry);if(entry){for(const provider of providerOrder){const config=entry.providers[provider];if(config?.url){const subpath=specifier.slice(pkgName.length);if(subpath&&config.url.includes("esm.sh/")){return{value:config.url+subpath,type:"url",integrity:config.integrity}}if(subpath){return{value:`${fallbackCdnBase}/${specifier}`,type:"url"}}if(config.global&&!config.esm){return{value:config.url,type:"url",integrity:config.integrity}}return{value:config.url,type:"url",integrity:config.integrity}}}}return{value:`${fallbackCdnBase}/${specifier}`,type:"url"}}}}function escapeHtml(str){if(str===null||str===void 0){return""}const s=String(str);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")}var SCRIPT_UNSAFE_CHARACTERS=/[<>&\u2028\u2029]/g;function toUnicodeEscape(character){return`\\u${character.charCodeAt(0).toString(16).padStart(4,"0")}`}function safeJsonForScript(value){if(value===void 0){return"null"}try{const jsonString=JSON.stringify(value,(_key,val)=>{if(typeof val==="bigint"){return val.toString()}return val});if(jsonString===void 0){return"null"}return jsonString.replace(SCRIPT_UNSAFE_CHARACTERS,toUnicodeEscape)}catch{return'{"error":"Value could not be serialized"}'}}function createImportMapFromResolved(resolved){const imports={};const integrity={};for(const[specifier,res]of Object.entries(resolved)){if(res.type==="url"){imports[specifier]=res.value;if(res.integrity){integrity[res.value]=res.integrity}}}return{imports,integrity:Object.keys(integrity).length>0?integrity:void 0}}function generateImportMapScriptTag(map){const json=JSON.stringify(map,null,2).replace(/<\//g,"<\\/");return`<script type="importmap">
2
6
  ${json}
3
7
  </script>`}var WIDGET_CALL_META_KEY="frontmcp/widgetCall";var MCP_APPS_DISPLAY_MODES=["inline","fullscreen","pip"];function generateBridgeIIFE(options={}){const{debug=false,trustedOrigins=[],minify=false}=options;const adapters=options.adapters||["openai","ext-apps","claude","gemini","generic"];const parts=[];parts.push("(function() {");parts.push('"use strict";');parts.push("");if(debug){parts.push('function log(msg) { console.log("[FrontMcpBridge] " + msg); }')}else{parts.push("function log() {}")}parts.push("");parts.push("var DEFAULT_SAFE_AREA = { top: 0, bottom: 0, left: 0, right: 0 };");parts.push("");parts.push(generateContextDetection());parts.push("");parts.push(generateBaseCapabilities());parts.push("");if(adapters.includes("openai")){parts.push(generateOpenAIAdapter());parts.push("")}if(adapters.includes("ext-apps")){parts.push(generateExtAppsAdapter(trustedOrigins));parts.push("")}if(adapters.includes("claude")){parts.push(generateClaudeAdapter());parts.push("")}if(adapters.includes("gemini")){parts.push(generateGeminiAdapter());parts.push("")}if(adapters.includes("generic")){parts.push(generateGenericAdapter());parts.push("")}parts.push(generatePlatformDetection(adapters));parts.push("");parts.push(generateBridgeClass());parts.push("");parts.push("var bridge = new FrontMcpBridge();");parts.push("bridge.initialize().then(function() {");parts.push(' log("Bridge initialized with adapter: " + bridge.adapterId);');parts.push(' window.dispatchEvent(new CustomEvent("bridge:ready", { detail: { adapter: bridge.adapterId } }));');parts.push(" if (window.__mcpDisplayMode) bridge.requestDisplayMode(window.__mcpDisplayMode).catch(function() {});");parts.push("}).catch(function(err) {");parts.push(' console.error("[FrontMcpBridge] Init failed:", err);');parts.push(' window.dispatchEvent(new CustomEvent("bridge:error", { detail: { error: err } }));');parts.push("});");parts.push("");parts.push("window.FrontMcpBridge = bridge;");parts.push("");parts.push(generateAutoResize());parts.push("function __showLoading() {");parts.push(' var root = document.getElementById("root");');parts.push(" if (root && !root.hasChildNodes()) {");parts.push(` root.innerHTML = '<div style="display:flex;align-items:center;justify-content:center;padding:2rem;color:#6b7280;font-family:system-ui,sans-serif"><div style="text-align:center"><div style="width:24px;height:24px;border:2px solid #e5e7eb;border-top-color:#3b82f6;border-radius:50%;animation:__spin 0.6s linear infinite;margin:0 auto 12px"></div><div style="font-size:0.875rem">Loading widget...</div></div></div><style>@keyframes __spin{to{transform:rotate(360deg)}}</style>';`);parts.push(" }");parts.push("}");parts.push('if (document.readyState === "loading") {');parts.push(' document.addEventListener("DOMContentLoaded", __showLoading);');parts.push("} else {");parts.push(" __showLoading();");parts.push("}");parts.push("})();");const code=parts.join("\n");if(minify){return minifyJS(code)}return code}function generateAutoResize(){return`
4
8
  function __applySizingCss(sizing) {
@@ -1208,14 +1212,25 @@ ${headParts.map(p=>` ${p}`).join("\n")}
1208
1212
  ${content}
1209
1213
  </body>
1210
1214
  </html>`;return{html:html2,hash:simpleHash(html2),size:Buffer.byteLength(html2,"utf-8")}}function buildCustomShell(content,customShell,ctx){let template;if(typeof customShell==="string"){const validation=validateShellTemplate(customShell);if(!validation.valid){throw new Error(`Custom shell template is missing required placeholder(s): ${validation.missingRequired.map(n=>`{{${n}}}`).join(", ")}`)}template=customShell}else{template=customShell.template}const cspTag=buildCSPMetaTag(ctx.csp);const dataScript=resolveDataInjectionScript({dataInjection:ctx.dataInjection,toolName:ctx.toolName,input:ctx.input,output:ctx.output,structuredContent:ctx.structuredContent,sizing:ctx.sizing,displayMode:ctx.displayMode});const sizingStyle=buildSizingStyleTag(ctx.sizing);const dataWithSizing=sizingStyle?`${dataScript}
1211
- ${sizingStyle}`:dataScript;let bridgeHtml="";if(ctx.includeBridge){const bridgeScript=generateBridgeIIFE({minify:true});bridgeHtml=`<script>${bridgeScript}</script>`}const html2=applyShellTemplate(template,{csp:cspTag,data:dataWithSizing,bridge:bridgeHtml,content,title:ctx.title?escapeHtmlForTag(ctx.title):""});return{html:html2,hash:simpleHash(html2),size:Buffer.byteLength(html2,"utf-8")}}function simpleHash(str){let hash=0;for(let i=0;i<str.length;i++){const char=str.charCodeAt(i);hash=(hash<<5)-hash+char|0}return Math.abs(hash).toString(16).padStart(8,"0")}function escapeHtmlForTag(str){return str.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;")}function isFrontmcpUiResolvable(...candidatePaths){try{const nodeFs=require("fs");const nodePath=require("path");const ORG="@frontmcp";const PKG="ui";for(const candidate of candidatePaths){let dir=candidate;while(true){const pkgJson=nodePath.join(dir,"node_modules",ORG,PKG,"package.json");if(nodeFs.existsSync(pkgJson))return true;const parent=nodePath.dirname(dir);if(parent===dir)break;dir=parent}}return false}catch{return false}}function loadEsbuild(filename){try{return require("esbuild")}catch(err){if(!isMissingEsbuild(err))throw err;const subject=filename?`FileSource widget "${filename}"`:"A FileSource widget";throw new Error(`${subject} needs esbuild, which is not installed. @frontmcp/uipack loads esbuild on demand to bundle FileSource widgets (.tsx/.jsx) when the tool is called, so it must be installed where the server runs: \`npm install esbuild\` (in "dependencies", not "devDependencies"). Projects created with \`frontmcp create\` already get it through the \`frontmcp\` package.`,{cause:err})}}function isMissingEsbuild(err){if(!(err instanceof Error))return false;return err.code==="MODULE_NOT_FOUND"&&/Cannot find module ['"]esbuild['"]/.test(err.message)}function transpileReactSource(source,filename){const esbuild=loadEsbuild(filename);const loader=filename?.endsWith(".tsx")?"tsx":"jsx";const result=esbuild.transformSync(source,{loader,jsx:"transform",jsxFactory:"React.createElement",jsxFragment:"React.Fragment",format:"esm",target:"es2020",define:{"process.env.NODE_ENV":'"production"'}});return result.code}function bundleFileSource(source,filename,resolveDir,componentName,options={}){if(!isFrontmcpUiResolvable(resolveDir,process.cwd())){throw new Error(`FileSource widget "${filename}" requires the @frontmcp/ui package, which provides the React bridge mount that's injected at bundle time. Install it (e.g. \`npm install @frontmcp/ui\` or \`yarn add @frontmcp/ui\`) and try again.`)}const esbuild=loadEsbuild(filename);const mountCode=`
1215
+ ${sizingStyle}`:dataScript;let bridgeHtml="";if(ctx.includeBridge){const bridgeScript=generateBridgeIIFE({minify:true});bridgeHtml=`<script>${bridgeScript}</script>`}const html2=applyShellTemplate(template,{csp:cspTag,data:dataWithSizing,bridge:bridgeHtml,content,title:ctx.title?escapeHtmlForTag(ctx.title):""});return{html:html2,hash:simpleHash(html2),size:Buffer.byteLength(html2,"utf-8")}}function simpleHash(str){let hash=0;for(let i=0;i<str.length;i++){const char=str.charCodeAt(i);hash=(hash<<5)-hash+char|0}return Math.abs(hash).toString(16).padStart(8,"0")}function escapeHtmlForTag(str){return str.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;")}function isFrontmcpUiResolvable(...candidatePaths){try{const nodeFs=require("fs");const nodePath=require("path");const ORG="@frontmcp";const PKG="ui";for(const candidate of candidatePaths){let dir=candidate;while(true){const pkgJson=nodePath.join(dir,"node_modules",ORG,PKG,"package.json");if(nodeFs.existsSync(pkgJson))return true;const parent=nodePath.dirname(dir);if(parent===dir)break;dir=parent}}return false}catch{return false}}function loadEsbuild(filename){try{return require("esbuild")}catch(err){if(!isMissingEsbuild(err))throw err;const subject=filename?`FileSource widget "${filename}"`:"A FileSource widget";throw new Error(`${subject} needs esbuild, which is not installed. @frontmcp/uipack loads esbuild on demand to bundle FileSource widgets (.tsx/.jsx) when the tool is called, so it must be installed where the server runs: \`npm install esbuild\` (in "dependencies", not "devDependencies"). Projects created with \`frontmcp create\` already get it through the \`frontmcp\` package.`,{cause:err})}}function isMissingEsbuild(err){if(!(err instanceof Error))return false;return err.code==="MODULE_NOT_FOUND"&&/Cannot find module ['"]esbuild['"]/.test(err.message)}function transpileReactSource(source,filename){const esbuild=loadEsbuild(filename);const loader=filename?.endsWith(".tsx")?"tsx":"jsx";const result=esbuild.transformSync(source,{loader,jsx:"transform",jsxFactory:"React.createElement",jsxFragment:"React.Fragment",format:"esm",target:"es2020",define:{"process.env.NODE_ENV":'"production"'}});return result.code}var FIRST_RENDER=` // Render immediately (component shows loading state until data arrives)
1216
+ __render(undefined);
1217
+ `;var INJECTED_DATA_RENDER=` // 2. Try injected data globals (null: the page carries no call's data, e.g. compiled at startup)
1218
+ if (__hasData(window.__mcpToolOutput) && window.__mcpToolOutput !== null) { __render(window.__mcpToolOutput); }
1219
+ `;function hydrateFirstRender(componentName){return` // Hydrate the markup rendered on the server, with the data the page carries
1220
+ var __initial = window.__mcpToolOutput !== null ? window.__mcpToolOutput : undefined;
1221
+ __reactRoot = hydrateRoot(__root,
1222
+ __h(McpBridgeProvider, null,
1223
+ __h(${componentName}, { output: __hasData(__initial) ? __initial : null, input: window.__mcpToolInput, loading: !__hasData(__initial) })
1224
+ )
1225
+ );
1226
+ `}function bundleFileSource(source,filename,resolveDir,componentName,options={}){if(!isFrontmcpUiResolvable(resolveDir,process.cwd())){throw new Error(`FileSource widget "${filename}" requires the @frontmcp/ui package, which provides the React bridge mount that's injected at bundle time. Install it (e.g. \`npm install @frontmcp/ui\` or \`yarn add @frontmcp/ui\`) and try again.`)}const esbuild=loadEsbuild(filename);const hydrate=options.hydrate===true;const mountCode=`
1212
1227
  // --- Auto-generated mount ---
1213
1228
  import { createElement as __h } from 'react';
1214
- import { createRoot } from 'react-dom/client';
1229
+ import { ${hydrate?"hydrateRoot":"createRoot"} } from 'react-dom/client';
1215
1230
  import { McpBridgeProvider } from '@frontmcp/ui/react';
1216
1231
  var __root = document.getElementById('root');
1217
1232
  if (__root) {
1218
- var __reactRoot = createRoot(__root);
1233
+ var __reactRoot${hydrate?"":" = createRoot(__root)"};
1219
1234
  function __hasData(v) { return v !== undefined; }
1220
1235
  function __render(output) {
1221
1236
  __reactRoot.render(
@@ -1224,9 +1239,7 @@ if (__root) {
1224
1239
  )
1225
1240
  );
1226
1241
  }
1227
- // Render immediately (component shows loading state until data arrives)
1228
- __render(undefined);
1229
- // 1. Try OpenAI SDK (toolOutput set synchronously or after load)
1242
+ ${hydrate?hydrateFirstRender(componentName):FIRST_RENDER} // 1. Try OpenAI SDK (toolOutput set synchronously or after load)
1230
1243
  if (typeof window !== 'undefined') {
1231
1244
  if (!window.openai) window.openai = {};
1232
1245
  var __cur = window.openai.toolOutput;
@@ -1237,9 +1250,7 @@ if (__root) {
1237
1250
  configurable: true, enumerable: true
1238
1251
  });
1239
1252
  }
1240
- // 2. Try injected data globals (null: the page carries no call's data, e.g. compiled at startup)
1241
- if (__hasData(window.__mcpToolOutput) && window.__mcpToolOutput !== null) { __render(window.__mcpToolOutput); }
1242
- // 3. Listen for bridge tool-result (ext-apps / MCP Inspector)
1253
+ ${hydrate?"":INJECTED_DATA_RENDER} // 3. Listen for bridge tool-result (ext-apps / MCP Inspector)
1243
1254
  var __bridge = window.FrontMcpBridge;
1244
1255
  if (__bridge && typeof __bridge.onToolResult === 'function') {
1245
1256
  __bridge.onToolResult(function(data) { __render(data); });
@@ -1250,7 +1261,14 @@ if (__root) {
1250
1261
  if (d) __render(d.structuredContent !== undefined ? d.structuredContent : d.content !== undefined ? d.content : d);
1251
1262
  });
1252
1263
  }
1253
- }`;const loader={".tsx":"tsx",".jsx":"jsx"};const stdinLoader=filename.endsWith(".tsx")?"tsx":"jsx";const alias={};try{const nodePath=require("path");const nodeFs=require("fs");const candidates=[nodePath.join(process.cwd(),"node_modules","@frontmcp","ui","dist","esm"),nodePath.join(resolveDir,"node_modules","@frontmcp","ui","dist","esm")];for(const uiEsmBase of candidates){if(!nodeFs.existsSync(uiEsmBase))continue;const subpaths=["components","react","theme","bridge","runtime"];for(const sub of subpaths){const mjs=nodePath.join(uiEsmBase,sub,"index.mjs");if(nodeFs.existsSync(mjs))alias[`@frontmcp/ui/${sub}`]=mjs}if(Object.keys(alias).length>0)break}}catch{}try{const result=esbuild.buildSync({stdin:{contents:source+"\n"+mountCode,loader:stdinLoader,resolveDir,sourcefile:filename},bundle:true,write:false,format:"esm",target:"es2020",jsx:"automatic",external:options.bundleReact?[]:["react","react-dom","react/jsx-runtime","react/jsx-dev-runtime"],alias,define:{"process.env.NODE_ENV":'"production"'},platform:"browser",treeShaking:true,logLevel:"warning",loader});return{code:result.outputFiles[0].text}}catch(err){const message=err instanceof Error?err.message:String(err);throw new Error(`Failed to bundle FileSource "${filename}": ${message}. If the error mentions @frontmcp/ui or @frontmcp/uipack, ensure both packages are installed in the consuming project.`,{cause:err})}}function extractDefaultExportName(code){let match=code.match(/export\s+default\s+(?:function|class)\s+(\w+)/);if(match)return match[1];match=code.match(/export\s+default\s+(\w+)\s*;/);if(match)return match[1];return null}function isNpmSource(source){return typeof source==="object"&&source!==null&&"npm"in source}function isFileSource(source){return typeof source==="object"&&source!==null&&"file"in source}function isImportSource(source){return typeof source==="object"&&source!==null&&"import"in source}function isFunctionSource(source){return typeof source==="function"}var DEFAULT_META={mcpAware:false,renderer:"auto"};function resolveUISource(source,options){if(options?.inlineReact&&options?.transformOnly){throw new Error("resolveUISource: `inlineReact` and `transformOnly` are mutually exclusive \u2014 set at most one.")}const resolver=options?.resolver??createEsmShResolver();if(isNpmSource(source)){return resolveNpmSource(source,resolver)}if(isImportSource(source)){return resolveImportSource(source)}if(isFileSource(source)){return resolveFileSource(source,{inlineReact:options?.inlineReact,transformOnly:options?.transformOnly})}if(isFunctionSource(source)){return resolveFunctionSource(source,options?.input,options?.output)}throw new Error("Unknown UISource type")}function resolveNpmSource(source,resolver){const specifier=source.version?`${source.npm}@${source.version}`:source.npm;const pkgName=getPackageName(source.npm);const resolved=resolver.resolve(specifier);const url=resolved?.value??`https://esm.sh/${specifier}`;const peerDeps=[];if(pkgName!=="react"&&pkgName!=="react-dom"){peerDeps.push("react","react-dom")}return{mode:"module",url,exportName:source.exportName??"default",meta:{...DEFAULT_META,renderer:"react"},peerDependencies:peerDeps}}function resolveImportSource(source){return{mode:"module",url:source.import,exportName:source.exportName??"default",meta:{...DEFAULT_META},peerDependencies:[]}}var BUILD_DIR_SEGMENTS=new Set(["dist","build","out"]);function missingWidgetError(filePath,cause){const sourceWidget=findSourceWidget(filePath);const error=new Error(`FileSource widget not found (ENOENT): no file at "${filePath}". The widget source is read from this path when the tool is called, so it has to exist where the compiled tool runs \u2014 a path built from \`__dirname\` or \`import.meta.url\` points into the build output once the tool is compiled. tsc does not copy \`*.widget.tsx\` / \`*.widget.jsx\` files into its output; \`frontmcp build\` does, and any other build needs a step that copies them next to the compiled tool.`+(sourceWidget?` The source widget exists at "${sourceWidget}" \u2014 copy it to "${filePath}", or build with \`frontmcp build\`.`:""),{cause});error.code="ENOENT";error.path=filePath;return error}function findSourceWidget(filePath){const path=require("path");const fs=require("fs");const parts=filePath.split(path.sep);for(let i=parts.length-2;i>=0;i--){if(!BUILD_DIR_SEGMENTS.has(parts[i]))continue;const candidates=[[...parts.slice(0,i),"src",...parts.slice(i+1)]];if(parts.length-i>2)candidates.push([...parts.slice(0,i),"src",...parts.slice(i+2)]);candidates.push([...parts.slice(0,i),...parts.slice(i+1)]);for(const candidate of candidates){const candidatePath=candidate.join(path.sep);try{if(fs.existsSync(candidatePath))return candidatePath}catch{}}return void 0}return void 0}function resolveFileSource(source,options={}){const path=require("path");const ext=path.extname(source.file).toLowerCase();if(ext===".tsx"||ext===".jsx"){const fs=require("fs");const wasRelative=!path.isAbsolute(source.file);const filePath=wasRelative?path.resolve(process.cwd(),source.file):source.file;let rawSource;try{rawSource=fs.readFileSync(filePath,"utf-8")}catch(err){const isNotFound=err?.code==="ENOENT";if(isNotFound&&wasRelative){throw new Error(`FileSource widget "${source.file}" not found at "${filePath}". Relative paths are resolved against process.cwd() ("${process.cwd()}"), not the tool file's directory. To anchor the path to the tool file, pass an absolute path \u2014 e.g. \`{ file: fileURLToPath(new URL('./widget.tsx', import.meta.url)) }\` from \`node:url\` (see issue #444).`,{cause:err})}if(isNotFound)throw missingWidgetError(filePath,err);throw err}const componentName=source.exportName||extractDefaultExportName(rawSource)||"Component";if(options.transformOnly===true){const code=transpileReactSource(rawSource,path.basename(filePath));const parsed2=parseImports(code);return{mode:"module",code,imports:[...new Set(parsed2.externalImports.map(i=>i.specifier))],exportName:componentName,meta:{mcpAware:true,renderer:"react"},peerDependencies:source.peerDependencies||["react","react-dom"],bundled:false}}const bundled=bundleFileSource(rawSource,source.file,path.dirname(filePath),componentName,{bundleReact:options.inlineReact===true});const parsed=parseImports(bundled.code);return{mode:"module",code:bundled.code,imports:[...new Set(parsed.externalImports.map(i=>i.specifier))],exportName:componentName,meta:{mcpAware:true,renderer:"react"},peerDependencies:source.peerDependencies||["react","react-dom"],bundled:true}}if(source.inline){return{mode:"inline",html:`<!-- File source: ${source.file} (inline mode - content to be resolved at build time) -->`,exportName:"default",meta:{...DEFAULT_META,renderer:"html"},peerDependencies:[]}}return{mode:"module",url:source.file,exportName:"default",meta:{...DEFAULT_META},peerDependencies:[]}}function resolveFunctionSource(source,input,output){const html2=source(input,output);return{mode:"inline",html:html2,exportName:"default",meta:{mcpAware:false,renderer:"html"},peerDependencies:[]}}function generateMountScript(resolved,propsMapping){if(resolved.mode!=="module"||!resolved.url){return""}const componentImport=resolved.exportName==="default"?`import Component from '${resolved.url}';`:`import { ${resolved.exportName} as Component } from '${resolved.url}';`;if(resolved.meta.mcpAware){return`${componentImport}
1264
+ }`;const loader={".tsx":"tsx",".jsx":"jsx"};const stdinLoader=filename.endsWith(".tsx")?"tsx":"jsx";const alias={};try{const nodePath=require("path");const nodeFs=require("fs");const candidates=[nodePath.join(process.cwd(),"node_modules","@frontmcp","ui","dist","esm"),nodePath.join(resolveDir,"node_modules","@frontmcp","ui","dist","esm")];for(const uiEsmBase of candidates){if(!nodeFs.existsSync(uiEsmBase))continue;const subpaths=["components","react","theme","bridge","runtime"];for(const sub of subpaths){const mjs=nodePath.join(uiEsmBase,sub,"index.mjs");if(nodeFs.existsSync(mjs))alias[`@frontmcp/ui/${sub}`]=mjs}if(Object.keys(alias).length>0)break}}catch{}try{const result=esbuild.buildSync({stdin:{contents:source+"\n"+mountCode,loader:stdinLoader,resolveDir,sourcefile:filename},bundle:true,write:false,format:"esm",target:"es2020",jsx:"automatic",external:options.bundleReact?[]:["react","react-dom","react/jsx-runtime","react/jsx-dev-runtime"],alias,define:{"process.env.NODE_ENV":'"production"'},platform:"browser",treeShaking:true,logLevel:"warning",loader});return{code:result.outputFiles[0].text}}catch(err){const message=err instanceof Error?err.message:String(err);throw new Error(`Failed to bundle FileSource "${filename}": ${message}. If the error mentions @frontmcp/ui or @frontmcp/uipack, ensure both packages are installed in the consuming project.`,{cause:err})}}function bundleFileSourceForServer(filePath,exportName){const path=require("path");const resolveDir=path.dirname(filePath);if(!isFrontmcpUiResolvable(resolveDir,process.cwd())){throw new Error(`FileSource widget "${filePath}" requires the @frontmcp/ui package, whose McpBridgeProvider it renders in. Install it (e.g. \`npm install @frontmcp/ui\`) and try again.`)}const esbuild=loadEsbuild(filePath);const component=JSON.stringify(exportName??"default");const entry=`import * as __widget from ${JSON.stringify(`./${path.basename(filePath)}`)};
1265
+ import { createElement } from 'react';
1266
+ import { renderToString } from 'react-dom/server';
1267
+ import { McpBridgeProvider } from '@frontmcp/ui/react';
1268
+ var Component = __widget[${component}] ?? __widget.default;
1269
+ export function render(props) {
1270
+ return renderToString(createElement(McpBridgeProvider, null, createElement(Component, props)));
1271
+ }`;try{const result=esbuild.buildSync({stdin:{contents:entry,loader:"jsx",resolveDir,sourcefile:"widget-server-entry.jsx"},bundle:true,write:false,format:"cjs",platform:"node",target:"node20",jsx:"automatic",external:["react","react-dom","react/*","react-dom/*"],define:{"process.env.NODE_ENV":'"production"'},logLevel:"silent",loader:{".tsx":"tsx",".jsx":"jsx"}});return{code:result.outputFiles[0].text}}catch(err){const message=err instanceof Error?err.message:String(err);throw new Error(`Failed to bundle FileSource "${filePath}" for the server: ${message}`,{cause:err})}}function extractDefaultExportName(code){let match=code.match(/export\s+default\s+(?:function|class)\s+(\w+)/);if(match)return match[1];match=code.match(/export\s+default\s+(\w+)\s*;/);if(match)return match[1];return null}function widgetProps(input,output){const data=output===void 0||output===null?void 0:output;return{input:input??null,output:data??null,loading:data===void 0}}var renderers=new Map;function renderFileSourceOnServer(filePath,source,exportName,props){const key=`${filePath}#${exportName??"default"}`;let renderer=renderers.get(key);if(renderer?.source!==source){renderer={source,render:loadServerBundle(filePath,exportName)};renderers.set(key,renderer)}return renderer.render(props)}function loadServerBundle(filePath,exportName){const{code}=bundleFileSourceForServer(filePath,exportName);const{createRequire}=process.getBuiltinModule("node:module");const path=process.getBuiltinModule("node:path");const module2={exports:{}};const evaluate=new Function("require","module","exports","__filename","__dirname",code);evaluate(createRequire(filePath),module2,module2.exports,filePath,path.dirname(filePath));const{render}=module2.exports;if(typeof render!=="function")throw new Error(`The server bundle of FileSource "${filePath}" has no render()`);return render}function isNpmSource(source){return typeof source==="object"&&source!==null&&"npm"in source}function isFileSource(source){return typeof source==="object"&&source!==null&&"file"in source}function isImportSource(source){return typeof source==="object"&&source!==null&&"import"in source}function isFunctionSource(source){return typeof source==="function"}var DEFAULT_META={mcpAware:false,renderer:"auto"};function resolveUISource(source,options){if(options?.inlineReact&&options?.transformOnly){throw new Error("resolveUISource: `inlineReact` and `transformOnly` are mutually exclusive \u2014 set at most one.")}const resolver=options?.resolver??createEsmShResolver();if(isNpmSource(source)){return resolveNpmSource(source,resolver)}if(isImportSource(source)){return resolveImportSource(source)}if(isFileSource(source)){return resolveFileSource(source,{inlineReact:options?.inlineReact,transformOnly:options?.transformOnly,hydrate:options?.hydrate,input:options?.input,output:options?.output})}if(isFunctionSource(source)){return resolveFunctionSource(source,options?.input,options?.output)}throw new Error("Unknown UISource type")}function resolveNpmSource(source,resolver){const specifier=source.version?`${source.npm}@${source.version}`:source.npm;const pkgName=getPackageName(source.npm);const resolved=resolver.resolve(specifier);const url=resolved?.value??`https://esm.sh/${specifier}`;const peerDeps=[];if(pkgName!=="react"&&pkgName!=="react-dom"){peerDeps.push("react","react-dom")}return{mode:"module",url,exportName:source.exportName??"default",meta:{...DEFAULT_META,renderer:"react"},peerDependencies:peerDeps}}function resolveImportSource(source){return{mode:"module",url:source.import,exportName:source.exportName??"default",meta:{...DEFAULT_META},peerDependencies:[]}}var BUILD_DIR_SEGMENTS=new Set(["dist","build","out"]);function missingWidgetError(filePath,cause){const sourceWidget=findSourceWidget(filePath);const error=new Error(`FileSource widget not found (ENOENT): no file at "${filePath}". The widget source is read from this path when the tool is called, so it has to exist where the compiled tool runs \u2014 a path built from \`__dirname\` or \`import.meta.url\` points into the build output once the tool is compiled. tsc does not copy \`*.widget.tsx\` / \`*.widget.jsx\` files into its output; \`frontmcp build\` does, and any other build needs a step that copies them next to the compiled tool.`+(sourceWidget?` The source widget exists at "${sourceWidget}" \u2014 copy it to "${filePath}", or build with \`frontmcp build\`.`:""),{cause});error.code="ENOENT";error.path=filePath;return error}function findSourceWidget(filePath){const path=require("path");const fs=require("fs");const parts=filePath.split(path.sep);for(let i=parts.length-2;i>=0;i--){if(!BUILD_DIR_SEGMENTS.has(parts[i]))continue;const candidates=[[...parts.slice(0,i),"src",...parts.slice(i+1)]];if(parts.length-i>2)candidates.push([...parts.slice(0,i),"src",...parts.slice(i+2)]);candidates.push([...parts.slice(0,i),...parts.slice(i+1)]);for(const candidate of candidates){const candidatePath=candidate.join(path.sep);try{if(fs.existsSync(candidatePath))return candidatePath}catch{}}return void 0}return void 0}function resolveFileSource(source,options={}){const path=require("path");const ext=path.extname(source.file).toLowerCase();if(ext===".tsx"||ext===".jsx"){const fs=require("fs");const wasRelative=!path.isAbsolute(source.file);const filePath=wasRelative?path.resolve(process.cwd(),source.file):source.file;let rawSource;try{rawSource=fs.readFileSync(filePath,"utf-8")}catch(err){const isNotFound2=err?.code==="ENOENT";if(isNotFound2&&wasRelative){throw new Error(`FileSource widget "${source.file}" not found at "${filePath}". Relative paths are resolved against process.cwd() ("${process.cwd()}"), not the tool file's directory. To anchor the path to the tool file, pass an absolute path \u2014 e.g. \`{ file: fileURLToPath(new URL('./widget.tsx', import.meta.url)) }\` from \`node:url\` (see issue #444).`,{cause:err})}if(isNotFound2)throw missingWidgetError(filePath,err);throw err}const componentName=source.exportName||extractDefaultExportName(rawSource)||"Component";if(options.transformOnly===true){const code=transpileReactSource(rawSource,path.basename(filePath));const parsed2=parseImports(code);return{mode:"module",code,imports:[...new Set(parsed2.externalImports.map(i=>i.specifier))],exportName:componentName,meta:{mcpAware:true,renderer:"react"},peerDependencies:source.peerDependencies||["react","react-dom"],bundled:false}}const hydrate=options.hydrate===true;const bundled=bundleFileSource(rawSource,source.file,path.dirname(filePath),componentName,{bundleReact:options.inlineReact===true,hydrate});const serverHtml=hydrate?renderFileSourceOnServer(filePath,rawSource,source.exportName,widgetProps(options.input,options.output)):void 0;const parsed=parseImports(bundled.code);return{mode:"module",code:bundled.code,imports:[...new Set(parsed.externalImports.map(i=>i.specifier))],exportName:componentName,meta:{mcpAware:true,renderer:"react"},peerDependencies:source.peerDependencies||["react","react-dom"],bundled:true,serverHtml}}if(source.inline){return{mode:"inline",html:`<!-- File source: ${source.file} (inline mode - content to be resolved at build time) -->`,exportName:"default",meta:{...DEFAULT_META,renderer:"html"},peerDependencies:[]}}return{mode:"module",url:source.file,exportName:"default",meta:{...DEFAULT_META},peerDependencies:[]}}function resolveFunctionSource(source,input,output){const html2=source(input,output);return{mode:"inline",html:html2,exportName:"default",meta:{mcpAware:false,renderer:"html"},peerDependencies:[]}}function generateMountScript(resolved,propsMapping){if(resolved.mode!=="module"||!resolved.url){return""}const componentImport=resolved.exportName==="default"?`import Component from '${resolved.url}';`:`import { ${resolved.exportName} as Component } from '${resolved.url}';`;if(resolved.meta.mcpAware){return`${componentImport}
1254
1272
  import React from 'react';
1255
1273
  import { createRoot } from 'react-dom/client';
1256
1274
  createRoot(document.getElementById('root'))
@@ -1260,7 +1278,7 @@ import { createRoot } from 'react-dom/client';
1260
1278
  const output = window.__mcpToolOutput || {};
1261
1279
  const props = ${propsCode};
1262
1280
  createRoot(document.getElementById('root'))
1263
- .render(React.createElement(Component, props));`}function generateMappedPropsCode(mapping){const entries=Object.entries(mapping).map(([prop,path])=>{const accessPath=path.split(".").map(part=>`[${safeJsonForScript(part)}]`).join("");return`${safeJsonForScript(prop)}: output${accessPath}`}).join(", ");return`({ ${entries} })`}var DEFAULT_WIDGET_MOUNT={moduleSpecifier:"@frontmcp/ui/react",wrapperImportName:"McpBridgeProvider"};function renderComponent(config,shellConfig){const resolver=shellConfig.resolver??createEsmShResolver();const resolved=resolveUISource(config.source,{resolver,input:shellConfig.input,output:shellConfig.output,inlineReact:config.inlineReact===true,transformOnly:config.transformOnly===true});const mergedShellConfig={...shellConfig,withShell:config.withShell??shellConfig.withShell,includeBridge:config.includeBridge??shellConfig.includeBridge,customShell:config.customShell??shellConfig.customShell};if(resolved.mode==="inline"){return buildShell(resolved.html??"",mergedShellConfig)}const content=buildModuleContent(resolved,resolver,config.props,shellConfig.mount);return buildShell(content,mergedShellConfig)}function buildModuleContent(resolved,resolver,propsMapping,mount=DEFAULT_WIDGET_MOUNT){const parts=[];if(resolved.code&&resolved.bundled){const importEntries={};for(const spec of resolved.imports||[]){const depResolved=resolver.resolve(spec);if(depResolved){importEntries[spec]=depResolved}}if(Object.keys(importEntries).length>0){const importMap=createImportMapFromResolved(importEntries);parts.push(generateImportMapScriptTag(importMap))}parts.push('<div id="root"></div>');parts.push(`<script type="module">
1281
+ .render(React.createElement(Component, props));`}function generateMappedPropsCode(mapping){const entries=Object.entries(mapping).map(([prop,path])=>{const accessPath=path.split(".").map(part=>`[${safeJsonForScript(part)}]`).join("");return`${safeJsonForScript(prop)}: output${accessPath}`}).join(", ");return`({ ${entries} })`}var DEFAULT_WIDGET_MOUNT={moduleSpecifier:"@frontmcp/ui/react",wrapperImportName:"McpBridgeProvider"};function renderComponent(config,shellConfig){const resolver=shellConfig.resolver??createEsmShResolver();const resolved=resolveUISource(config.source,{resolver,input:shellConfig.input,output:shellConfig.output,inlineReact:config.inlineReact===true,transformOnly:config.transformOnly===true,hydrate:config.hydrate===true});const mergedShellConfig={...shellConfig,withShell:config.withShell??shellConfig.withShell,includeBridge:config.includeBridge??shellConfig.includeBridge,customShell:config.customShell??shellConfig.customShell};if(resolved.mode==="inline"){return buildShell(resolved.html??"",mergedShellConfig)}const content=buildModuleContent(resolved,resolver,config.props,shellConfig.mount);return buildShell(content,mergedShellConfig)}function buildModuleContent(resolved,resolver,propsMapping,mount=DEFAULT_WIDGET_MOUNT){const parts=[];if(resolved.code&&resolved.bundled){const importEntries={};for(const spec of resolved.imports||[]){const depResolved=resolver.resolve(spec);if(depResolved){importEntries[spec]=depResolved}}if(Object.keys(importEntries).length>0){const importMap=createImportMapFromResolved(importEntries);parts.push(generateImportMapScriptTag(importMap))}parts.push(`<div id="root">${resolved.serverHtml??""}</div>`);parts.push(`<script type="module">
1264
1282
  ${resolved.code}
1265
1283
  </script>`)}else if(resolved.code){const importEntries={};const allSpecifiers=new Set([...resolved.imports||[],"react-dom/client",mount.moduleSpecifier,"react/jsx-runtime","react/jsx-dev-runtime"]);if(mount===DEFAULT_WIDGET_MOUNT){allSpecifiers.add("react-dom/server");allSpecifiers.add("react-dom/static")}const coreDeps=new Set(["react","react-dom","react-dom/client","react/jsx-runtime","react/jsx-dev-runtime","react-dom/server","react-dom/static"]);for(const spec of allSpecifiers){const depResolved=resolver.resolve(spec);if(depResolved){if(!coreDeps.has(spec)&&depResolved.value?.includes("esm.sh")){importEntries[spec]={...depResolved,value:addExternalParam(depResolved.value,["react","react-dom"])}}else{importEntries[spec]=depResolved}}}if(importEntries["react-dom"]&&!importEntries["react-dom/client"]){const rdc=resolver.resolve("react-dom/client");if(rdc)importEntries["react-dom/client"]=rdc}if(Object.keys(importEntries).length>0){const importMap=createImportMapFromResolved(importEntries);parts.push(generateImportMapScriptTag(importMap))}const mountNodeId=mount.mountNodeId??"root";parts.push(`<div id="${mountNodeId}">${mount.mountNodeInnerHtml??""}</div>`);const mountCode=generateInlineMountCode(resolved.exportName,mount);parts.push(`<script type="module">
1266
1284
  ${resolved.code}
@@ -1278,7 +1296,7 @@ if (__root) {
1278
1296
  React.createElement(${componentName})
1279
1297
  )
1280
1298
  );
1281
- }`}var CHART_TYPES=new Set(["bar","line","pie","area","scatter","doughnut","radar","polarArea","bubble"]);var MERMAID_PREFIXES=["flowchart","graph","sequenceDiagram","classDiagram","stateDiagram","erDiagram","gantt","pie","gitGraph","journey","mindmap","timeline","quadrantChart","sankey","xychart"];var PDF_MAGIC="JVBERi";var BASE64_PATTERN=/^[A-Za-z0-9+/=\r\n]+$/;function detectContentType(value){if(value===null||value===void 0){return"text"}if(typeof value==="object"&&!Array.isArray(value)){const obj=value;if(typeof obj["type"]==="string"&&CHART_TYPES.has(obj["type"])){return"chart"}return"json"}if(typeof value==="string"){const trimmed=value.trimStart();if(trimmed.startsWith(PDF_MAGIC)){return BASE64_PATTERN.test(trimmed.trimEnd())?"pdf":"text"}for(const prefix of MERMAID_PREFIXES){if(trimmed.startsWith(prefix)){return"mermaid"}}if(trimmed.includes("<")&&trimmed.includes(">")){return"html"}return"text"}return"text"}var CHART_JS_CDN="https://esm.sh/chart.js@4/auto";var MERMAID_CDN="https://esm.sh/mermaid@11/dist/mermaid.esm.min.mjs";function buildChartHtml(chartJson){const chartData=safeJsonForScript(chartJson);const content=`
1299
+ }`}var import_entities=require("entities");var CSS_ESCAPE=/\\(?:([0-9a-fA-F]{1,6})[ \t\n\r\f]?|([^\n\r\f0-9a-fA-F]))/g;var CSS_COMMENT=/\/\*[\s\S]*?(?:\*\/|$)/g;function unescapeCss(text){return text.replace(CSS_ESCAPE,(_match,hex,character)=>{if(hex===void 0)return character??"";const codePoint=parseInt(hex,16);const valid=codePoint>0&&codePoint<=1114111&&(codePoint<55296||codePoint>57343);return valid?String.fromCodePoint(codePoint):"\uFFFD"})}function sanitizeStyleText(css){return removeImports(css.replace(/</g,"\\3c ").replace(/&(?=[#A-Za-z0-9])/g,"\\26 "))}function removeImports(css){const kept=[];let start=0;let index=css.indexOf("@");while(index!==-1){const nameEnd=identEnd(css,index+1);if(unescapeCss(css.slice(index+1,nameEnd)).toLowerCase()==="import"){kept.push(css.slice(start,index)," ");start=statementEnd(css,nameEnd);index=css.indexOf("@",start)}else{index=css.indexOf("@",index+1)}}kept.push(css.slice(start));return kept.join("")}function commentEnd(css,start){const end=css.indexOf("*/",start+2);return end===-1?css.length:end+2}function stringEnd(css,start){const quote=css[start];let index=start+1;while(index<css.length&&css[index]!==quote&&css[index]!=="\n"){index+=css[index]==="\\"?2:1}return Math.min(index+1,css.length)}function isIdentCharacter(character){return character==="-"||/\w/.test(character)||character.charCodeAt(0)>=128}function identEnd(css,start){let index=start;while(index<css.length){if(css[index]==="\\"&&css[index+1]!==void 0&&css[index+1]!=="\n"){index+=2}else if(isIdentCharacter(css[index])){index++}else{break}}return index}function statementEnd(css,start){let depth=0;let index=start;while(index<css.length){const character=css[index];if(character==="/"&&css[index+1]==="*"){index=commentEnd(css,index);continue}if(character==='"'||character==="'"){index=stringEnd(css,index);continue}if(character==="("||character==="["||character==="{")depth++;if(character===")"||character==="]")depth=Math.max(0,depth-1);if(character==="}"){if(depth===0)return index;depth--;if(depth===0)return index+1}if(character===";"&&depth===0)return index+1;index++}return css.length}function sanitizeStyleAttribute(style){const declarations=splitDeclarations(style);const kept=declarations.filter(declaration=>!isFixedPosition(declaration));return kept.length===declarations.length?style:kept.join(";")}function splitDeclarations(style){const declarations=[];let depth=0;let start=0;let index=0;while(index<style.length){const character=style[index];if(character==="/"&&style[index+1]==="*"){index=commentEnd(style,index);continue}if(character==='"'||character==="'"){index=stringEnd(style,index);continue}if(character==="(")depth++;if(character===")")depth=Math.max(0,depth-1);if(character===";"&&depth===0){declarations.push(style.slice(start,index));start=index+1}index++}declarations.push(style.slice(start));return declarations}function isFixedPosition(declaration){const text=declaration.replace(CSS_COMMENT,"");const colon=text.indexOf(":");if(colon===-1)return false;const property=unescapeCss(text.slice(0,colon)).trim().toLowerCase();const value=unescapeCss(text.slice(colon+1)).toLowerCase();return property==="position"&&/fixed|sticky|var\(/.test(value)}var DEFAULT_MAX_MARKUP_LENGTH=2*1024*1024;var POLICY_FLAGS=["allowUnsafeLinks","allowInlineScripts","allowToolCallAttributes","bypassSanitization"];var WHITESPACE=/[\t\n\f\r ]/;var TAG_NAME=/^[A-Za-z][A-Za-z0-9-]*$/;var ATTRIBUTE_NAME_BREAKERS=/[\s"'<>/=]/;var COMMENT_END=/--!?>/g;var RAW_TEXT_CONTENT={script:content=>content,style:sanitizeStyleText,textarea:content=>content.replace(/</g,"&lt;"),title:content=>content.replace(/</g,"&lt;"),xmp:content=>content.replace(/</g,"&lt;"),iframe:()=>"",noembed:()=>"",noframes:()=>"",noscript:()=>""};var RAW_TEXT_END=Object.fromEntries(Object.keys(RAW_TEXT_CONTENT).map(name=>[name,new RegExp(`</${name}(?=[\\t\\n\\f\\r />])`,"gi")]));var DROPPED_TAGS=new Set(["base","meta","object","embed","applet","animate","animatecolor","animatemotion","animatetransform","set"]);var URL_ATTRIBUTES=new Set(["href","xlink:href","src","action","formaction","poster","background","cite","data","codebase","classid","archive","longdesc","lowsrc","dynsrc","manifest","icon","profile"]);var URL_LIST_ATTRIBUTES=new Set(["srcset","imagesrcset","ping"]);var SCRIPT_SOURCE_ATTRIBUTES=new Set(["src","href","xlink:href"]);var TOOL_CALL_ATTRIBUTES=new Set(["data-tool-call","data-tool-args"]);var NAVIGATION_ATTRIBUTES={a:new Set(["href","xlink:href"]),area:new Set(["href","xlink:href"]),form:new Set(["action"]),button:new Set(["formaction"]),input:new Set(["formaction"])};var SAFE_SCHEMES=new Set(["http","https","mailto"]);var RASTER_IMAGE_DATA_URL=/^data:image\/(png|gif|jpeg|webp|avif)[;,]/i;var URL_SCHEME=/^([A-Za-z][A-Za-z0-9+.-]*):/;function sanitizeHtml(markup,policy={}){const problems=describeContentSecurityProblems(policy);if(problems.length>0)throw new TypeError(problems.join(" "));if(policy.bypassSanitization)return markup;const maxLength=policy.maxMarkupLength??DEFAULT_MAX_MARKUP_LENGTH;if(markup.length>maxLength){throw new RangeError(`The markup is ${markup.length} characters, over the ${maxLength} that \`ui.contentSecurity\` sanitizes (\`maxMarkupLength\`), so it is not rendered.`)}const output=[];let index=0;while(index<markup.length){const open=markup.indexOf("<",index);if(open===-1){output.push(markup.slice(index));break}output.push(markup.slice(index,open));const next=markup[open+1]??"";if(next==="!"||next==="?"){index=skipDeclaration(markup,open)}else if(next==="/"){index=writeEndTag(markup,open,output)}else if(isAsciiLetter(next)){index=writeElement(markup,open,output,policy)}else{output.push("&lt;");index=open+1}}return output.join("")}function describeContentSecurityProblems(policy){if(!isPlainObject(policy)){return[`\`ui.contentSecurity\` must be an object of flags, and it is ${describeValue(policy)}.`]}const problems=[];for(const[key,value]of Object.entries(policy)){if(value===void 0)continue;if(POLICY_FLAGS.includes(key)){if(typeof value!=="boolean"){problems.push(`\`ui.contentSecurity.${key}\` must be true or false, and it is ${describeValue(value)}.`)}}else if(key==="maxMarkupLength"){if(!Number.isSafeInteger(value)||value<=0){problems.push(`\`ui.contentSecurity.maxMarkupLength\` must be a positive integer, and it is ${describeValue(value)}.`)}}else{problems.push(`\`ui.contentSecurity\` has no \`${key}\` option; its options are \`${POLICY_FLAGS.join("`, `")}\` and \`maxMarkupLength\`.`)}}return problems}function isPlainObject(value){if(typeof value!=="object"||value===null)return false;const prototype=Object.getPrototypeOf(value);return prototype===Object.prototype||prototype===null}function describeValue(value){if(value===null||Array.isArray(value))return JSON.stringify(value);return typeof value==="object"||typeof value==="function"?`a ${typeof value}`:`${typeof value} ${JSON.stringify(value)}`}function isAsciiLetter(character){return/^[A-Za-z]$/.test(character)}function asciiLowerCase(text){return text.replace(/[A-Z]/g,letter=>letter.toLowerCase())}function skipDeclaration(markup,open){if(markup.startsWith("<!--",open)){COMMENT_END.lastIndex=open+2;const close2=COMMENT_END.exec(markup);return close2?close2.index+close2[0].length:markup.length}const close=markup.indexOf(">",open);return close===-1?markup.length:close+1}function writeEndTag(markup,open,output){if(!isAsciiLetter(markup[open+2]??"")){return markup.startsWith("</>",open)?open+3:skipDeclaration(markup,open)}const tag=parseTag(markup,open+2);if(!tag)return markup.length;if(TAG_NAME.test(tag.name)&&!DROPPED_TAGS.has(asciiLowerCase(tag.name)))output.push(`</${tag.name}>`);return tag.end}function writeElement(markup,open,output,policy){const tag=parseTag(markup,open+1);if(!tag)return markup.length;const name=asciiLowerCase(tag.name);if(name==="plaintext"){output.push(markup.slice(tag.end).replace(/</g,"&lt;"));return markup.length}const keepContent=RAW_TEXT_CONTENT[name];if(keepContent){const{content,end}=readRawText(markup,tag.end,name);if(name==="script"&&!isAllowedScript(tag,policy))return end;output.push(`<${tag.name}${writeAttributes(name,tag.attributes,policy)}>${keepContent(content)}</${tag.name}>`);return end}if(TAG_NAME.test(tag.name)&&!DROPPED_TAGS.has(name)&&!isCrossOriginStylesheet(name,tag.attributes)){output.push(`<${tag.name}${writeAttributes(name,tag.attributes,policy)}${tag.selfClosing?" /":""}>`)}return tag.end}function isAllowedScript(tag,policy){return policy.allowInlineScripts===true&&!tag.attributes.some(({name})=>SCRIPT_SOURCE_ATTRIBUTES.has(asciiLowerCase(name)))}function isCrossOriginStylesheet(tagName,attributes){if(tagName!=="link")return false;const rel=attributeValue(attributes,"rel");const href=attributeValue(attributes,"href");if(rel===void 0||href===void 0||!/(^|[\t\n\f\r ])stylesheet($|[\t\n\f\r ])/i.test(rel))return false;const url=urlAsParsed(href);return URL_SCHEME.test(url)||/^[\\/]{2}/.test(url)}function attributeValue(attributes,name){const attribute=attributes.find(candidate=>asciiLowerCase(candidate.name)===name);return attribute?.value===void 0?void 0:decodeAttributeValue(attribute.value)}function readRawText(markup,start,name){const endTag=RAW_TEXT_END[name];endTag.lastIndex=start;const match=endTag.exec(markup);if(!match)return{content:markup.slice(start),end:markup.length};const tag=parseTag(markup,match.index+2);return{content:markup.slice(start,match.index),end:tag?tag.end:markup.length}}function parseTag(markup,start){let index=start;while(index<markup.length&&!/[\t\n\f\r />]/.test(markup[index]))index++;const tag={name:markup.slice(start,index),attributes:[],selfClosing:false,end:0};while(index<markup.length){const character=markup[index];if(WHITESPACE.test(character)){index++}else if(character===">"){tag.end=index+1;return tag}else if(character==="/"){index++;if(markup[index]===">"){tag.selfClosing=true;tag.end=index+1;return tag}}else{index=readAttribute(markup,index,tag.attributes)}}return void 0}function readAttribute(markup,start,attributes){let index=start+1;while(index<markup.length&&!/[\t\n\f\r />=]/.test(markup[index]))index++;const attribute={name:markup.slice(start,index),value:void 0};attributes.push(attribute);let afterName=index;while(afterName<markup.length&&WHITESPACE.test(markup[afterName]))afterName++;if(markup[afterName]!=="=")return afterName;index=afterName+1;while(index<markup.length&&WHITESPACE.test(markup[index]))index++;const quote=markup[index];if(quote==='"'||quote==="'"){const close=markup.indexOf(quote,index+1);if(close===-1)return markup.length;attribute.value=markup.slice(index+1,close);return close+1}if(quote===">"){attribute.value="";return index}const valueStart=index;while(index<markup.length&&!/[\t\n\f\r >]/.test(markup[index]))index++;attribute.value=markup.slice(valueStart,index);return index}function decodeAttributeValue(raw){return(0,import_entities.decodeHTMLAttribute)(raw).split("\0").join("\uFFFD")}function escapeAttributeValue(value){return value.replace(/&/g,"&amp;").replace(/"/g,"&quot;").replace(/</g,"&lt;").replace(/>/g,"&gt;")}function writeAttributes(tagName,attributes,policy){const seen=new Set;let written="";for(const attribute of attributes){const name=asciiLowerCase(attribute.name);if(seen.has(name))continue;seen.add(name);if(!isWritableAttributeName(attribute.name)||name==="srcdoc")continue;if(name.startsWith("on")&&!policy.allowInlineScripts)continue;if(TOOL_CALL_ATTRIBUTES.has(name)&&!policy.allowToolCallAttributes)continue;if(attribute.value===void 0){written+=` ${attribute.name}`;continue}const value=decodeAttributeValue(attribute.value);if(!isAllowedValue(tagName,name,value,policy))continue;written+=` ${attribute.name}="${escapeAttributeValue(name==="style"?sanitizeStyleAttribute(value):value)}"`}return written}function isWritableAttributeName(name){if(name.length===0||ATTRIBUTE_NAME_BREAKERS.test(name))return false;return[...name].every(character=>character.charCodeAt(0)>31&&character.charCodeAt(0)!==127)}function isAllowedValue(tagName,attributeName,value,policy){if(URL_ATTRIBUTES.has(attributeName)){return isAllowedUrl(value,{imageData:tagName==="img"&&attributeName==="src",navigation:policy.allowUnsafeLinks===true&&NAVIGATION_ATTRIBUTES[tagName]?.has(attributeName)===true})}if(URL_LIST_ATTRIBUTES.has(attributeName)){const separator=attributeName==="ping"?/\s+/:/,/;return value.split(separator).every(candidate=>isAllowedUrl(candidate.trim().split(/\s+/)[0]??"",{}))}return true}function urlAsParsed(value){const url=value.replace(/[\t\n\r]/g,"");let start=0;while(start<url.length&&(url.charCodeAt(start)<=32||/\s/.test(url[start])))start++;return url.slice(start)}function isAllowedUrl(value,allowed){const url=urlAsParsed(value);const scheme=URL_SCHEME.exec(url)?.[1].toLowerCase();if(scheme===void 0||SAFE_SCHEMES.has(scheme))return true;if(scheme==="data")return allowed.imageData===true&&RASTER_IMAGE_DATA_URL.test(url);return allowed.navigation===true}function projectRequires(){const{createRequire}=process.getBuiltinModule("node:module");const path=process.getBuiltinModule("node:path");const bases=[process.cwd()];if(process.argv[1])bases.push(path.dirname(process.argv[1]));return bases.map(base=>createRequire(path.join(base,"package.json")))}function packageName(specifier){const parts=specifier.split("/");return specifier.startsWith("@")?parts.slice(0,2).join("/"):parts[0]}function isNotFound(error,specifier){const{code,message}=error??{};return(code==="MODULE_NOT_FOUND"||code==="ERR_MODULE_NOT_FOUND")&&typeof message==="string"&&message.includes(`'${packageName(specifier)}`)}function fromProject(specifier,purpose,use){let notFound;for(const projectRequire of projectRequires()){try{return use(projectRequire)}catch(error){if(!isNotFound(error,specifier))throw error;notFound=error}}const name=packageName(specifier);throw new Error(`${purpose} needs ${name}, which is not installed. Install it where the server runs: \`npm install ${name}\`.`,{cause:notFound})}var preloaded=new Map;function requireFromProject(specifier,purpose){if(preloaded.has(specifier))return preloaded.get(specifier);return fromProject(specifier,purpose,projectRequire=>projectRequire(specifier))}async function preloadFromProject(specifier,purpose){if(preloaded.has(specifier))return;const{pathToFileURL}=process.getBuiltinModule("node:url");const url=pathToFileURL(resolveFromProject(specifier,purpose)).href;let loaded;try{loaded=await import(url)}catch{loaded=requireFromProject(specifier,purpose)}preloaded.set(specifier,loaded)}function resolveFromProject(specifier,purpose){return fromProject(specifier,purpose,projectRequire=>projectRequire.resolve(specifier))}var PURPOSE="`ui.runtimeOptions.markdown.highlightTheme`";var THEME_NAME=/^[a-z0-9-]+(\/[a-z0-9-]+)?$/i;var highlighters=new Map;function loadCodeHighlighter(theme){const loaded=highlighters.get(theme);if(loaded)return loaded;const hljs=requireFromProject("highlight.js",PURPOSE);const highlighter={highlight:(code,language)=>hljs.getLanguage(language)?hljs.highlight(code,{language,ignoreIllegals:true}).value:void 0,css:readTheme(theme)};highlighters.set(theme,highlighter);return highlighter}function readTheme(theme){const fs=require("fs");const path=require("path");const styles=path.join(path.dirname(resolveFromProject("highlight.js/package.json",PURPOSE)),"styles");const file=path.join(styles,`${theme}.min.css`);if(!THEME_NAME.test(theme)||!fs.existsSync(file)){throw new Error(`${PURPOSE} is ${JSON.stringify(theme)}, which is not a highlight.js theme: name a stylesheet in highlight.js/styles without its extension, such as "github", "github-dark" or "base16/dracula".`)}return fs.readFileSync(file,"utf-8")}var CHART_TYPES=new Set(["bar","line","pie","area","scatter","doughnut","radar","polarArea","bubble"]);var MERMAID_PREFIXES=["flowchart","graph","sequenceDiagram","classDiagram","stateDiagram","erDiagram","gantt","pie","gitGraph","journey","mindmap","timeline","quadrantChart","sankey","xychart"];var PDF_MAGIC="JVBERi";var BASE64_PATTERN=/^[A-Za-z0-9+/=\r\n]+$/;function detectContentType(value){if(value===null||value===void 0){return"text"}if(typeof value==="object"&&!Array.isArray(value)){const obj=value;if(typeof obj["type"]==="string"&&CHART_TYPES.has(obj["type"])){return"chart"}return"json"}if(typeof value==="string"){const trimmed=value.trimStart();if(trimmed.startsWith(PDF_MAGIC)){return BASE64_PATTERN.test(trimmed.trimEnd())?"pdf":"text"}for(const prefix of MERMAID_PREFIXES){if(trimmed.startsWith(prefix)){return"mermaid"}}if(trimmed.includes("<")&&trimmed.includes(">")){return"html"}return"text"}return"text"}var CHART_JS_CDN="https://esm.sh/chart.js@4/auto";var MERMAID_CDN="https://esm.sh/mermaid@11/dist/mermaid.esm.min.mjs";function buildChartHtml(chartJson){const chartData=safeJsonForScript(chartJson);const content=`
1282
1300
  <canvas id="chart" style="max-width:100%;max-height:80vh;"></canvas>
1283
1301
  <script type="module">
1284
1302
  import Chart from '${CHART_JS_CDN}';
@@ -1312,8 +1330,10 @@ for (let i = 1; i <= pdf.numPages; i++) {
1312
1330
  container.appendChild(canvas);
1313
1331
  await page.render({ canvasContext: canvas.getContext('2d'), viewport }).promise;
1314
1332
  }
1315
- </script>`;const result=buildShell(content,{toolName:"pdf",csp:{resourceDomains:["https://esm.sh"],connectDomains:["https://esm.sh"]},includeBridge:false});return result.html}function wrapDetectedContent(value){const contentType=detectContentType(value);switch(contentType){case"chart":return buildChartHtml(value);case"mermaid":return buildMermaidHtml(value);case"pdf":return buildPdfHtml(value);case"html":return void 0;default:return void 0}}var SAFE_HREF=/^(https?:|mailto:|\/|#)/i;function emphasis(text){return text.replace(/\*\*([^*]+)\*\*/g,"<strong>$1</strong>").replace(/\*([^*]+)\*/g,"<em>$1</em>")}function inline(text){const stash=[];const hold=html2=>`\uE000${stash.push(html2)-1}\uE000`;let out=escapeHtml(text.replace(/\uE000/g,"")).replace(/`([^`]+)`/g,(_m,code)=>hold(`<code>${code}</code>`));out=out.replace(/\[([^\]]+)\]\(([^)\s]+)\)/g,(_m,label,href)=>SAFE_HREF.test(href)?hold(`<a href="${href}" rel="noopener noreferrer">${emphasis(label)}</a>`):label);out=emphasis(out);for(let i=0;i<2;i++)out=out.replace(/\uE000(\d+)\uE000/g,(_m,n)=>stash[Number(n)]);return out}function markdownToHtml(source){const lines=source.replace(/\r\n?/g,"\n").split("\n");const blocks=[];let paragraph=[];let list;const flushParagraph=()=>{if(paragraph.length>0)blocks.push(`<p>${inline(paragraph.join(" "))}</p>`);paragraph=[]};const flushList=()=>{if(list)blocks.push(`<${list.tag}>${list.items.map(item=>`<li>${inline(item)}</li>`).join("")}</${list.tag}>`);list=void 0};for(let i=0;i<lines.length;i++){const line=lines[i];const fence=/^\s*(`{3,})/.exec(line);if(fence){flushParagraph();flushList();const fenceLength=fence[1].length;const isClosing=l=>{const m=/^\s*(`{3,})\s*$/.exec(l);return m!==null&&m[1].length>=fenceLength};const code=[];for(i++;i<lines.length&&!isClosing(lines[i]);i++)code.push(lines[i]);blocks.push(`<pre><code>${escapeHtml(code.join("\n"))}
1316
- </code></pre>`);continue}const heading=/^(#{1,6})\s+(.*)$/.exec(line);if(heading){flushParagraph();flushList();blocks.push(`<h${heading[1].length}>${inline(heading[2].trim())}</h${heading[1].length}>`);continue}const item=/^\s*([-*+]|\d+\.)\s+(.*)$/.exec(line);if(item){flushParagraph();const tag=/\d/.test(item[1])?"ol":"ul";if(list&&list.tag!==tag)flushList();list??={tag,items:[]};list.items.push(item[2]);continue}if(line.trim()===""){flushParagraph();flushList();continue}flushList();paragraph.push(line.trim())}flushParagraph();flushList();return blocks.join("\n")}function detectUIType(template){if(template===null||template===void 0){return"auto"}if(typeof template==="object"&&template!==null&&"file"in template){const file=template.file;if(/\.(tsx|jsx)$/i.test(file))return"react"}if(typeof template==="function"){const proto=template.prototype;if(proto&&typeof proto.render==="function"){return"react"}const asRecord=template;if(asRecord["$$typeof"]!==void 0){return"react"}if(template.name&&/^[A-Z]/.test(template.name)){return"react"}return"html"}if(typeof template==="string"){if(template.includes("<")&&template.includes(">")){return"html"}return"markdown"}return"auto"}function buildCspConfig(resolver,extra){const cspResourceDomains=["https://esm.sh"];const cspConnectDomains=["https://esm.sh"];for(const domain of extra?.resourceDomains??[]){if(!cspResourceDomains.includes(domain))cspResourceDomains.push(domain)}for(const domain of extra?.connectDomains??[]){if(!cspConnectDomains.includes(domain))cspConnectDomains.push(domain)}if(resolver&&"overrides"in resolver){const overrides=resolver.overrides;if(overrides){for(const url of Object.values(overrides)){try{const origin=new URL(url).origin;if(!cspResourceDomains.includes(origin))cspResourceDomains.push(origin);if(!cspConnectDomains.includes(origin))cspConnectDomains.push(origin)}catch{}}}}return{resourceDomains:cspResourceDomains,connectDomains:cspConnectDomains}}var STRING_RESULT_DOCS="https://frontmcp.dev/learn/your-first-widget";var noticedStringResultTools=new Set;function noticeStringResult(toolName,logger){if(noticedStringResultTools.has(toolName))return;noticedStringResultTools.add(toolName);logger.warn(`[frontmcp] The UI template of tool "${toolName}" returned a plain string containing markup. Since 1.9.2, FrontMCP HTML-escapes plain string results by default, so it is shown as text. Build the markup with ctx.helpers.html\`\u2026\` (interpolated values are escaped) or wrap safe markup with ctx.helpers.trustedHtml(). Set ui.escapeStringResults: false to render plain strings as markup, or true to silence this notice. See ${STRING_RESULT_DOCS}`)}function renderUnwrappedResult(rawResult,options){if(isTrustedHtml(rawResult)){return String(rawResult)}if(typeof rawResult!=="string"){return`<pre>${escapeHtml(JSON.stringify(rawResult,null,2))}</pre>`}const looksLikeMarkup=detectContentType(rawResult)==="html";if(looksLikeMarkup&&options.escapeStringResults===false){return rawResult}if(looksLikeMarkup&&options.escapeStringResults===void 0){noticeStringResult(options.toolName,options.logger??console)}return escapeHtml(rawResult)}var warnedComponentReferences=new WeakMap;function warnComponentReference(template,toolName,logger){const warned=warnedComponentReferences.get(template)??new Set;if(warned.has(toolName))return;warned.add(toolName);warnedComponentReferences.set(template,warned);logger.warn(`[FrontMCP] Tool "${toolName}": \`ui.template\` is a React component reference, which cannot be bundled (there is no source file to compile), so the widget would render an empty page. Use \`template: { file: './widget.tsx' }\` instead.`)}var COMPILED_JSX_PATTERN=/\b(?:React\.createElement|_?jsx(?:s|DEV)?)\)?\s*\(/;function isComponentReference(template){if(isReactTypedObject(template))return true;if(typeof template!=="function"||detectUIType(template)!=="react")return false;return isDefinitelyReactComponent(template)||COMPILED_JSX_PATTERN.test(Function.prototype.toString.call(template))}function warnIfComponentReference(toolName,template,logger=console){if(!isComponentReference(template))return false;warnComponentReference(template,toolName,logger);return true}function isReactTypedObject(value){return typeof value==="object"&&value!==null&&"$$typeof"in value}function isDefinitelyReactComponent(template){if(typeof template!=="function")return false;const proto=template.prototype;return typeof proto?.render==="function"||template["$$typeof"]!==void 0}function renderToolTemplate(options){const{toolName,input,output,template,resolver,platformType,sizing}=options;const uiType=detectUIType(template);const isClaude=platformType==="claude"||platformType==="claude-code";const resourceMode=options.resourceMode??(isClaude?"inline":"cdn");const shellConfig={toolName,input,output,includeBridge:true,resolver,sizing,displayMode:options.displayMode,csp:options.csp?buildCspConfig(resolver,options.csp):void 0};let html2;let hash="";let size=0;if(typeof template==="object"&&template!==null&&"file"in template){const result=renderComponent({source:template,inlineReact:resourceMode==="inline"},{...shellConfig,csp:buildCspConfig(resolver,options.csp)});html2=result.html;hash=result.hash;size=result.size}else if(typeof template==="function"){const helpers=createTemplateHelpers();const ctx={input:input??{},output:output??{},helpers};let rawResult;let isHtmlBuilder=uiType!=="react"||!isDefinitelyReactComponent(template);if(isHtmlBuilder){try{rawResult=template(ctx);isHtmlBuilder=!isReactTypedObject(rawResult)}catch(error){if(uiType!=="react")throw error;isHtmlBuilder=false}}if(!isHtmlBuilder){warnComponentReference(template,toolName,options.logger??console);const shellResult=buildShell('<div id="root"></div>',shellConfig);html2=shellResult.html;hash=shellResult.hash;size=shellResult.size}else{const wrapped=isTrustedHtml(rawResult)?void 0:wrapDetectedContent(rawResult);if(wrapped){html2=wrapped}else{const shellResult=buildShell(renderUnwrappedResult(rawResult,options),shellConfig);html2=shellResult.html;hash=shellResult.hash;size=shellResult.size}}}else if(typeof template==="string"){const shellResult=buildShell(uiType==="markdown"?markdownToHtml(template):template,shellConfig);html2=shellResult.html;hash=shellResult.hash;size=shellResult.size}else{if(isReactTypedObject(template))warnComponentReference(template,toolName,options.logger??console);const shellResult=buildShell('<div id="root"></div>',shellConfig);html2=shellResult.html;hash=shellResult.hash;size=shellResult.size}const htmlKey="ui/html";const meta={[htmlKey]:html2,"ui/type":uiType,"ui/mimeType":MCP_APPS_MIME_TYPE};if(hasSizing(sizing)){if(sizing.preferredHeight!==void 0)meta["ui/preferredHeight"]=sizing.preferredHeight;if(sizing.minHeight!==void 0)meta["ui/minHeight"]=sizing.minHeight;if(sizing.maxHeight!==void 0)meta["ui/maxHeight"]=sizing.maxHeight;if(sizing.aspectRatio!==void 0)meta["ui/aspectRatio"]=sizing.aspectRatio}return{html:html2,uiType,hash,size:size||Buffer.byteLength(html2,"utf8"),meta}}function createDefaultBaseTemplate(options){const{toolName}=options;const safeToolName=escapeHtml(toolName);const bridgeScript=`<script>${generateBridgeIIFE({minify:true})}</script>`;const content=`
1333
+ </script>`;const result=buildShell(content,{toolName:"pdf",csp:{resourceDomains:["https://esm.sh"],connectDomains:["https://esm.sh"]},includeBridge:false});return result.html}function wrapDetectedContent(value){const contentType=detectContentType(value);switch(contentType){case"chart":return buildChartHtml(value);case"mermaid":return buildMermaidHtml(value);case"pdf":return buildPdfHtml(value);case"html":return void 0;default:return void 0}}var SAFE_HREF=/^(https?:|mailto:|\/|#)/i;var PLACEHOLDER="\uE000";var BARE_URL=/(?<![\w/@.])(?:https?:\/\/|www\.)(?:(?!&lt;)[^\s<])+/gi;var AUTOLINK_TRAILER=/(?:[?!.,:*_~]|&[a-z0-9#]+;)$/i;var TABLE_DELIMITER=/^\s*\|?\s*:?-+:?\s*(\|\s*:?-+:?\s*)*\|?\s*$/;var TASK_ITEM=/^\[([ xX])\]\s+(.*)$/;function emphasis(text){return text.replace(/\*\*([^*]+)\*\*/g,"<strong>$1</strong>").replace(/\*([^*]+)\*/g,"<em>$1</em>")}function trimAutolink(url){let trimmed=url;for(;;){const trailer=AUTOLINK_TRAILER.exec(trimmed);if(trailer){trimmed=trimmed.slice(0,-trailer[0].length)}else if(trimmed.endsWith(")")&&trimmed.split("(").length<trimmed.split(")").length){trimmed=trimmed.slice(0,-1)}else{return trimmed}}}function inline(text,gfm=false){const stash=[];const hold=html2=>PLACEHOLDER+(stash.push(html2)-1)+PLACEHOLDER;const restore=value=>value.split(PLACEHOLDER).map((part,i)=>i%2===1?stash[Number(part)]:part).join("");let out=escapeHtml(text.split(PLACEHOLDER).join("")).replace(/`([^`]+)`/g,(_m,code)=>hold(`<code>${code}</code>`));out=out.replace(/\[([^\]]+)\]\(([^)\s]+)\)/g,(_m,label,href)=>SAFE_HREF.test(href)?hold(`<a href="${href}" rel="noopener noreferrer">${emphasis(label)}</a>`):label);if(gfm){out=out.replace(BARE_URL,match=>{const url=trimAutolink(match.split(PLACEHOLDER,1)[0]);const href=/^www\./i.test(url)?`http://${url}`:url;return hold(`<a href="${href}" rel="noopener noreferrer">${url}</a>`)+match.slice(url.length)});out=out.replace(/~~([^~]+)~~/g,"<del>$1</del>")}out=emphasis(out);return restore(restore(out))}function listItem(item,gfm){const task=gfm?TASK_ITEM.exec(item):null;if(!task)return`<li>${inline(item,gfm)}</li>`;const checked=task[1]===" "?"":" checked";return`<li class="task-list-item"><input type="checkbox" disabled${checked}> ${inline(task[2],gfm)}</li>`}function tableCells(row){const cells=row.trim().replace(/^\|/,"").replace(/(?<!\\)\|$/,"").split(/(?<!\\)\|/);return cells.map(cell=>cell.trim().replace(/\\\|/g,"|"))}function cellAlignment(delimiter){const left=delimiter.startsWith(":");const right=delimiter.endsWith(":");if(left&&right)return' align="center"';if(right)return' align="right"';return left?' align="left"':""}function table(header,delimiter,body){const alignments=tableCells(delimiter).map(cellAlignment);const row=(cells,tag)=>`<tr>${alignments.map((align,i)=>`<${tag}${align}>${inline(cells[i]??"",true)}</${tag}>`).join("")}</tr>`;const rows=body.map(line=>row(tableCells(line),"td")).join("");return`<table><thead>${row(tableCells(header),"th")}</thead>${rows?`<tbody>${rows}</tbody>`:""}</table>`}function isTableStart(line,next){return line.includes("|")&&next!==void 0&&TABLE_DELIMITER.test(next)&&tableCells(line).length===tableCells(next).length}function markdownToHtml(source,options={}){const gfm=options.gfm===true;const lines=source.replace(/\r\n?/g,"\n").split("\n");const blocks=[];let paragraph=[];let list;const flushParagraph=()=>{if(paragraph.length>0)blocks.push(`<p>${inline(paragraph.join(" "),gfm)}</p>`);paragraph=[]};const flushList=()=>{if(list)blocks.push(`<${list.tag}>${list.items.map(item=>listItem(item,gfm)).join("")}</${list.tag}>`);list=void 0};for(let i=0;i<lines.length;i++){const line=lines[i];const fence=/^\s*(`{3,})\s*([\w#+.-]*)/.exec(line);if(fence){flushParagraph();flushList();const fenceLength=fence[1].length;const isClosing=l=>{const m=/^\s*(`{3,})\s*$/.exec(l);return m!==null&&m[1].length>=fenceLength};const code=[];for(i++;i<lines.length&&!isClosing(lines[i]);i++)code.push(lines[i]);blocks.push(codeBlock(code.join("\n"),fence[2],options.highlight));continue}if(gfm&&isTableStart(line,lines[i+1])){flushParagraph();flushList();const header=line;const delimiter=lines[i+1];const body=[];for(i+=2;i<lines.length&&lines[i].trim()!==""&&lines[i].includes("|");i++)body.push(lines[i]);i--;blocks.push(table(header,delimiter,body));continue}const heading=/^(#{1,6})\s+(.*)$/.exec(line);if(heading){flushParagraph();flushList();blocks.push(`<h${heading[1].length}>${inline(heading[2].trim(),gfm)}</h${heading[1].length}>`);continue}const item=/^\s*([-*+]|\d+\.)\s+(.*)$/.exec(line);if(item){flushParagraph();const tag=/\d/.test(item[1])?"ol":"ul";if(list&&list.tag!==tag)flushList();list??={tag,items:[]};list.items.push(item[2]);continue}if(line.trim()===""){flushParagraph();flushList();continue}flushList();paragraph.push(line.trim())}flushParagraph();flushList();return blocks.join("\n")}function codeBlock(code,language,highlight){if(!highlight)return`<pre><code>${escapeHtml(code)}
1334
+ </code></pre>`;const highlighted=language?highlight(code,language):void 0;const languageClass=language?` language-${language}`:"";return`<pre><code class="hljs${languageClass}">${highlighted??escapeHtml(code)}
1335
+ </code></pre>`}function loadServerReact(purpose){return{react:requireFromProject("react",purpose),jsxRuntime:requireFromProject("react/jsx-runtime",purpose),server:requireFromProject("react-dom/server",purpose)}}function renderComponentOnServer(component,input,output){const{react,server}=loadServerReact("`ui.uiType: 'react'`");return server.renderToStaticMarkup(react.createElement(component,widgetProps(input,output)))}var PURPOSE2="An MDX template";var compiledTemplates=new Map;async function preloadMdxCompiler(gfm=false){await preloadFromProject("@mdx-js/mdx",PURPOSE2);if(gfm)await preloadFromProject("remark-gfm",`${PURPOSE2} with \`gfm\``)}function compileMdxTemplate(source,gfm=false){const key=`${gfm}:${source}`;const cached=compiledTemplates.get(key);if(cached)return cached;const{compileSync}=requireFromProject("@mdx-js/mdx",PURPOSE2);const remarkPlugins=gfm?[requireFromProject("remark-gfm",`${PURPOSE2} with \`gfm\``).default]:[];const body=String(compileSync(source,{outputFormat:"function-body",development:false,remarkPlugins}));const compiled=new Function("__mdxRuntime","input","output","helpers",body);compiledTemplates.set(key,compiled);return compiled}function renderMdxTemplate(source,options){const compiled=compileMdxTemplate(source,options.gfm);const{react,jsxRuntime,server}=loadServerReact(PURPOSE2);const{default:content}=compiled(jsxRuntime,options.input,options.output,options.helpers);const components={...options.highlighter?{code:highlightedCode(react,options.highlighter)}:{},...options.components};return server.renderToStaticMarkup(react.createElement(content,{components}))}function highlightedCode(react,highlighter){return props=>{const language=/\blanguage-([\w#+.-]+)/.exec(props.className??"")?.[1];const highlighted=language&&typeof props.children==="string"?highlighter.highlight(props.children,language):void 0;if(highlighted===void 0)return react.createElement("code",props);return react.createElement("code",{className:`hljs ${props.className}`,dangerouslySetInnerHTML:{__html:highlighted}})}}function detectUIType(template){if(template===null||template===void 0){return"auto"}if(typeof template==="object"&&template!==null&&"file"in template){const file=template.file;if(/\.(tsx|jsx)$/i.test(file))return"react"}if(typeof template==="function"){const proto=template.prototype;if(proto&&typeof proto.render==="function"){return"react"}const asRecord=template;if(asRecord["$$typeof"]!==void 0){return"react"}if(template.name&&/^[A-Z]/.test(template.name)){return"react"}return"html"}if(typeof template==="string"){if(template.includes("<")&&template.includes(">")){return"html"}return"markdown"}return"auto"}function forcedUiType(options){return options.uiType&&options.uiType!=="auto"?options.uiType:void 0}function resolveUiType(template,options){const forced=forcedUiType(options);if(forced)return forced;return typeof template==="string"&&options.mdxComponents?"mdx":detectUIType(template)}function isFileTemplate(template){return typeof template==="object"&&template!==null&&"file"in template}function isJsxFileTemplate(template){return isFileTemplate(template)&&/\.(tsx|jsx)$/i.test(String(template.file))}function isComponentObjectOrClass(template){if(typeof template==="object"&&template!==null)return"$$typeof"in template;const prototype=typeof template==="function"?template.prototype:void 0;return typeof prototype?.render==="function"||Boolean(prototype?.isReactComponent)}function isComponentReference(template,options){if(isComponentObjectOrClass(template))return true;return typeof template==="function"&&resolveUiType(template,options)==="react"}function describeRenderOptionProblems(template,options){const problems=[];const forced=forcedUiType(options);const uiTypeProblem=forced&&describeUiTypeMismatch(forced,template);if(uiTypeProblem)problems.push(`\`ui.uiType: '${forced}'\` ${uiTypeProblem}`);if(options.contentSecurity!==void 0)problems.push(...describeContentSecurityProblems(options.contentSecurity));if(options.contentSecurity!==void 0&&isFileTemplate(template)){problems.push("`ui.contentSecurity` sanitizes the markup FrontMCP builds on the server, and a `{ file }` widget is rendered in the page, so there is nothing for it to sanitize. Remove it, or use an HTML, Markdown or function template.")}if(options.mdxComponents&&typeof template!=="string"){problems.push(`\`ui.mdxComponents\` is for an MDX template, which is a string, and this template is ${describeTemplate(template)}. A template function's result is never compiled as MDX: it carries the call's data, and compiling it would run that data as code. Write the template as a string and read the data in expressions, such as \`{output.title}\`.`)}else if(options.mdxComponents&&forced&&forced!=="mdx"){problems.push(`\`ui.mdxComponents\` is for an MDX template, and \`ui.uiType\` is '${forced}'.`)}const uiType=resolveUiType(template,options);const convertsMarkdown=uiType==="markdown"&&(typeof template==="string"||typeof template==="function")||uiType==="mdx"&&typeof template==="string";if((options.markdown?.gfm||options.markdown?.highlightTheme)&&!convertsMarkdown){problems.push(`\`ui.runtimeOptions.markdown\` applies to a Markdown template (a string without both \`<\` and \`>\`, or any template with \`uiType: 'markdown'\`) or an MDX template, and this template is ${describeTemplate(template)}. Remove it, or write the template in Markdown.`)}if(options.hydrate&&!isJsxFileTemplate(template)){problems.push(isComponentReference(template,options)?"`ui.hydrate` needs the component's code in the page, and a component reference has no source file to bundle. Use `template: { file: './widget.tsx' }`, or remove `hydrate` and set `uiType: 'react'` to render it on the server only.":`\`ui.hydrate\` hydrates a React widget rendered on the server, a \`{ file }\` .tsx or .jsx widget, and this template is ${uiType==="mdx"?"an MDX template, which renders to static HTML":describeTemplate(template)}.`)}return problems}function describeUiTypeMismatch(uiType,template){if(uiType==="react"){if(typeof template==="function"||isComponentObjectOrClass(template)||isJsxFileTemplate(template))return;return`renders a React component (a component reference, or a \`{ file }\` .tsx or .jsx widget), and this template is ${isFileTemplate(template)?"a `{ file }` that is not a .tsx or .jsx file":describeTemplate(template)}.`}if(uiType==="mdx"){return typeof template==="string"?void 0:`renders an MDX template, which is a string, and this template is ${describeTemplate(template)}.`}if(typeof template==="string"||typeof template==="function"&&!isComponentObjectOrClass(template))return;return`renders a string or a template function, and this template is ${describeTemplate(template)}.`}async function prepareRenderOptions(template,options){const uiType=resolveUiType(template,options);if(options.markdown?.highlightTheme&&(uiType==="markdown"||uiType==="mdx")){loadCodeHighlighter(options.markdown.highlightTheme)}if(uiType==="mdx"&&typeof template==="string"){loadServerReact("An MDX template");await preloadMdxCompiler(options.markdown?.gfm);compileMdxTemplate(template,options.markdown?.gfm)}if(forcedUiType(options)==="react"&&!isFileTemplate(template))loadServerReact("`ui.uiType: 'react'`");if(options.hydrate)loadServerReact("`ui.hydrate`")}function describeTemplate(template){if(typeof template==="string")return detectUIType(template)==="html"?"an HTML string":"a Markdown string";if(isComponentObjectOrClass(template))return"a React component";if(typeof template==="function")return"a function";return isFileTemplate(template)?"a `{ file }` widget":"not a string"}function buildCspConfig(resolver,extra){const cspResourceDomains=["https://esm.sh"];const cspConnectDomains=["https://esm.sh"];for(const domain of extra?.resourceDomains??[]){if(!cspResourceDomains.includes(domain))cspResourceDomains.push(domain)}for(const domain of extra?.connectDomains??[]){if(!cspConnectDomains.includes(domain))cspConnectDomains.push(domain)}if(resolver&&"overrides"in resolver){const overrides=resolver.overrides;if(overrides){for(const url of Object.values(overrides)){try{const origin=new URL(url).origin;if(!cspResourceDomains.includes(origin))cspResourceDomains.push(origin);if(!cspConnectDomains.includes(origin))cspConnectDomains.push(origin)}catch{}}}}return{resourceDomains:cspResourceDomains,connectDomains:cspConnectDomains}}var STRING_RESULT_DOCS="https://frontmcp.dev/learn/your-first-widget";var noticedStringResultTools=new Set;function noticeStringResult(toolName,logger){if(noticedStringResultTools.has(toolName))return;noticedStringResultTools.add(toolName);logger.warn(`[frontmcp] The UI template of tool "${toolName}" returned a plain string containing markup. Since 1.9.2, FrontMCP HTML-escapes plain string results by default, so it is shown as text. Build the markup with ctx.helpers.html\`\u2026\` (interpolated values are escaped) or wrap safe markup with ctx.helpers.trustedHtml(). Set ui.escapeStringResults: false to render plain strings as markup, or true to silence this notice. See ${STRING_RESULT_DOCS}`)}function renderUnwrappedResult(rawResult,options){if(isTrustedHtml(rawResult)){return String(rawResult)}if(typeof rawResult!=="string"){return`<pre>${escapeHtml(JSON.stringify(rawResult,null,2))}</pre>`}const looksLikeMarkup=detectContentType(rawResult)==="html";if(looksLikeMarkup&&options.escapeStringResults===false){return rawResult}if(looksLikeMarkup&&options.escapeStringResults===void 0){noticeStringResult(options.toolName,options.logger??console)}return escapeHtml(rawResult)}function convertMarkdown(source,options,mdx){const{gfm,highlightTheme}=options.markdown??{};const highlighter=highlightTheme?loadCodeHighlighter(highlightTheme):void 0;const converted=mdx?renderMdxTemplate(source,{input:options.input??{},output:options.output??{},helpers:createTemplateHelpers(),components:options.mdxComponents,gfm,highlighter}):markdownToHtml(source,{gfm,highlight:highlighter?.highlight});return highlighter?`<style>${highlighter.css}</style>
1336
+ ${converted}`:converted}function secureMarkup(markup,options){return options.contentSecurity===void 0?markup:sanitizeHtml(markup,options.contentSecurity)}var warnedComponentReferences=new WeakMap;function warnComponentReference(template,toolName,logger){const warned=warnedComponentReferences.get(template)??new Set;if(warned.has(toolName))return;warned.add(toolName);warnedComponentReferences.set(template,warned);logger.warn(`[FrontMCP] Tool "${toolName}": \`ui.template\` is a React component reference, which cannot be bundled (there is no source file to compile), so the widget would render an empty page. Use \`template: { file: './widget.tsx' }\` instead, or set \`ui.uiType: 'react'\` to render it on the server.`)}var COMPILED_JSX_PATTERN=/\b(?:React\.createElement|_?jsx(?:s|DEV)?)\)?\s*\(/;function isComponentReference2(template){if(isReactTypedObject(template))return true;if(typeof template!=="function"||detectUIType(template)!=="react")return false;return isDefinitelyReactComponent(template)||COMPILED_JSX_PATTERN.test(Function.prototype.toString.call(template))}function warnIfComponentReference(toolName,template,logger=console){if(!isComponentReference2(template))return false;warnComponentReference(template,toolName,logger);return true}function isReactTypedObject(value){return typeof value==="object"&&value!==null&&"$$typeof"in value}function isDefinitelyReactComponent(template){if(typeof template!=="function")return false;const proto=template.prototype;return typeof proto?.render==="function"||template["$$typeof"]!==void 0}function renderToolTemplate(options){const{toolName,input,output,template,resolver,platformType,sizing}=options;const problems=describeRenderOptionProblems(template,options);if(problems.length>0)throw new Error(`Tool "${toolName}": ${problems.join(" ")}`);const uiType=resolveUiType(template,options);const forced=forcedUiType(options);const isClaude=platformType==="claude"||platformType==="claude-code";const resourceMode=options.resourceMode??(isClaude?"inline":"cdn");const shellConfig={toolName,input,output,includeBridge:true,resolver,sizing,displayMode:options.displayMode,csp:options.csp?buildCspConfig(resolver,options.csp):void 0};let html2;let hash="";let size=0;if(typeof template==="object"&&template!==null&&"file"in template){const result=renderComponent({source:template,inlineReact:resourceMode==="inline",hydrate:options.hydrate===true},{...shellConfig,csp:buildCspConfig(resolver,options.csp)});html2=result.html;hash=result.hash;size=result.size}else if(typeof template==="function"&&forced==="react"){const shellResult=buildShell(secureMarkup(renderComponentOnServer(template,input,output),options),shellConfig);html2=shellResult.html;hash=shellResult.hash;size=shellResult.size}else if(typeof template==="function"){const helpers=createTemplateHelpers();const ctx={input:input??{},output:output??{},helpers};let rawResult;let isHtmlBuilder=forced!==void 0||uiType!=="react"||!isDefinitelyReactComponent(template);if(isHtmlBuilder){try{rawResult=template(ctx);isHtmlBuilder=forced!==void 0||!isReactTypedObject(rawResult)}catch(error){if(forced!==void 0||uiType!=="react")throw error;isHtmlBuilder=false}}if(!isHtmlBuilder){warnComponentReference(template,toolName,options.logger??console);const shellResult=buildShell('<div id="root"></div>',shellConfig);html2=shellResult.html;hash=shellResult.hash;size=shellResult.size}else if(forced==="markdown"){const markup=typeof rawResult==="string"?convertMarkdown(rawResult,options,false):renderUnwrappedResult(rawResult,options);const shellResult=buildShell(secureMarkup(markup,options),shellConfig);html2=shellResult.html;hash=shellResult.hash;size=shellResult.size}else{const wrapped=isTrustedHtml(rawResult)?void 0:wrapDetectedContent(rawResult);if(wrapped){html2=wrapped}else{const shellResult=buildShell(secureMarkup(renderUnwrappedResult(rawResult,options),options),shellConfig);html2=shellResult.html;hash=shellResult.hash;size=shellResult.size}}}else if(typeof template==="string"){const markup=uiType==="markdown"||uiType==="mdx"?convertMarkdown(template,options,uiType==="mdx"):template;const shellResult=buildShell(secureMarkup(markup,options),shellConfig);html2=shellResult.html;hash=shellResult.hash;size=shellResult.size}else if(forced==="react"){const shellResult=buildShell(secureMarkup(renderComponentOnServer(template,input,output),options),shellConfig);html2=shellResult.html;hash=shellResult.hash;size=shellResult.size}else{if(isReactTypedObject(template))warnComponentReference(template,toolName,options.logger??console);const shellResult=buildShell('<div id="root"></div>',shellConfig);html2=shellResult.html;hash=shellResult.hash;size=shellResult.size}const htmlKey="ui/html";const meta={[htmlKey]:html2,"ui/type":uiType,"ui/mimeType":MCP_APPS_MIME_TYPE};if(hasSizing(sizing)){if(sizing.preferredHeight!==void 0)meta["ui/preferredHeight"]=sizing.preferredHeight;if(sizing.minHeight!==void 0)meta["ui/minHeight"]=sizing.minHeight;if(sizing.maxHeight!==void 0)meta["ui/maxHeight"]=sizing.maxHeight;if(sizing.aspectRatio!==void 0)meta["ui/aspectRatio"]=sizing.aspectRatio}return{html:html2,uiType,hash,size:size||Buffer.byteLength(html2,"utf8"),meta}}function createDefaultBaseTemplate(options){const{toolName}=options;const safeToolName=escapeHtml(toolName);const bridgeScript=`<script>${generateBridgeIIFE({minify:true})}</script>`;const content=`
1317
1337
  ${bridgeScript}
1318
1338
  <div id="root">
1319
1339
  <div style="font-family:system-ui,sans-serif;padding:1rem;color:#374151;">
@@ -1339,4 +1359,4 @@ ${bridgeScript}
1339
1359
  });
1340
1360
  }
1341
1361
  })();
1342
- </script>`;const result=buildShell(content,{toolName,includeBridge:false,title:`Widget: ${toolName}`});return result.html}var ESM_SH_BASE="https://esm.sh";var REACT_DEPS=[{name:"react",url:`${ESM_SH_BASE}/react@18`},{name:"react-dom",url:`${ESM_SH_BASE}/react-dom@18`}];function buildCDNInfoForUIType(uiType){const deps=[];switch(uiType){case"react":deps.push(...REACT_DEPS);break;case"mdx":deps.push(...REACT_DEPS);deps.push({name:"marked",url:`${ESM_SH_BASE}/marked@latest`});break;case"markdown":deps.push({name:"marked",url:`${ESM_SH_BASE}/marked@latest`});break;case"auto":deps.push(...REACT_DEPS);deps.push({name:"marked",url:`${ESM_SH_BASE}/marked@latest`});break;case"html":default:break}return{base:ESM_SH_BASE,dependencies:deps}}0&&(module.exports={MCP_APPS_EXTENSION_ID,MCP_APPS_MIME_TYPE,buildCDNInfoForUIType,buildChartHtml,buildMermaidHtml,buildPdfHtml,buildToolResponseContent,createDefaultBaseTemplate,detectContentType,detectUIType,isUIRenderFailure,renderToolTemplate,resolveServingMode,warnIfComponentReference,wrapDetectedContent});
1362
+ </script>`;const result=buildShell(content,{toolName,includeBridge:false,title:`Widget: ${toolName}`});return result.html}var ESM_SH_BASE="https://esm.sh";var REACT_DEPS=[{name:"react",url:`${ESM_SH_BASE}/react@18`},{name:"react-dom",url:`${ESM_SH_BASE}/react-dom@18`}];function buildCDNInfoForUIType(uiType){const deps=[];switch(uiType){case"react":deps.push(...REACT_DEPS);break;case"mdx":deps.push(...REACT_DEPS);deps.push({name:"marked",url:`${ESM_SH_BASE}/marked@latest`});break;case"markdown":deps.push({name:"marked",url:`${ESM_SH_BASE}/marked@latest`});break;case"auto":deps.push(...REACT_DEPS);deps.push({name:"marked",url:`${ESM_SH_BASE}/marked@latest`});break;case"html":default:break}return{base:ESM_SH_BASE,dependencies:deps}}0&&(module.exports={MCP_APPS_EXTENSION_ID,MCP_APPS_MIME_TYPE,buildCDNInfoForUIType,buildChartHtml,buildHtmlResponseText,buildMermaidHtml,buildPdfHtml,buildToolResponseContent,createDefaultBaseTemplate,describeRenderOptionProblems,detectContentType,detectUIType,isUIRenderFailure,loadCodeHighlighter,prepareRenderOptions,renderToolTemplate,resolveServingMode,warnIfComponentReference,wrapDetectedContent});
@@ -3,7 +3,15 @@
3
3
  *
4
4
  * Supports headings, paragraphs, fenced code, ordered/unordered lists and inline
5
5
  * bold, italic, code and http(s)/mailto links. Everything else is escaped text; raw HTML in the
6
- * source is never passed through.
6
+ * source is never passed through. `ui.runtimeOptions.markdown` adds GitHub-flavored Markdown and
7
+ * highlighted code.
7
8
  */
8
- export declare function markdownToHtml(source: string): string;
9
+ /** `ui.runtimeOptions.markdown`, with the highlighter loaded for its theme. */
10
+ export interface MarkdownOptions {
11
+ /** GitHub-flavored Markdown: tables, task list items, `~~strikethrough~~` and links for bare URLs. */
12
+ gfm?: boolean;
13
+ /** Highlights the code of a fenced block: its HTML, or `undefined` for a language it doesn't know. */
14
+ highlight?: (code: string, language: string) => string | undefined;
15
+ }
16
+ export declare function markdownToHtml(source: string, options?: MarkdownOptions): string;
9
17
  //# sourceMappingURL=markdown.d.ts.map