@frontmcp/uipack 1.9.5 → 1.9.7

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 (3) hide show
  1. package/README.md +183 -315
  2. package/esm/package.json +14 -11
  3. package/package.json +14 -11
package/README.md CHANGED
@@ -1,9 +1,8 @@
1
1
  # @frontmcp/uipack
2
2
 
3
- HTML shell builder, pluggable import resolver, and NPM component loader for MCP UI development. React-free core.
3
+ `@frontmcp/uipack` is the React-free core FrontMCP builds widget pages with, on the server. It renders a tool's template into a full HTML page with a Content Security Policy, the call's data and the bridge script that talks to the host; resolves a widget's imports to CDN URLs; and bundles React widget files. `@frontmcp/sdk` depends on it, so a FrontMCP server already has it. Import it yourself to split a template into functions with `html`, to test a template without a server with `renderToolTemplate()`, or to build widget pages for a host of your own.
4
4
 
5
- [![NPM](https://img.shields.io/npm/v/@frontmcp/uipack.svg)](https://www.npmjs.com/package/@frontmcp/uipack)
6
- [![License](https://img.shields.io/npm/l/@frontmcp/uipack.svg)](../../LICENSE)
5
+ [![npm](https://img.shields.io/npm/v/@frontmcp/uipack.svg)](https://www.npmjs.com/package/@frontmcp/uipack) [![License](https://img.shields.io/npm/l/@frontmcp/uipack.svg)](https://github.com/agentfront/frontmcp/blob/main/LICENSE)
7
6
 
8
7
  ## Install
9
8
 
@@ -11,388 +10,257 @@ HTML shell builder, pluggable import resolver, and NPM component loader for MCP
11
10
  npm install @frontmcp/uipack
12
11
  ```
13
12
 
14
- No React required. The only runtime dependency is `zod`.
13
+ - No React. The one runtime dependency is `zod`; npm 7 and later also install the peer `@frontmcp/lazy-zod`, at the same version.
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
+ - It runs on Node.js 24 or later.
15
16
 
16
- ## Quick Start
17
+ ## Usage
17
18
 
18
- ### Build an HTML Shell
19
+ Render a template the way a tool call does, without a server:
20
+
21
+ ```ts
22
+ import { html, renderToolTemplate, type TemplateContext } from '@frontmcp/uipack';
23
+
24
+ type Weather = { city: string; tempC: number };
25
+
26
+ const badge = (text: string) => html`<span class="badge">${text}</span>`;
27
+
28
+ const result = renderToolTemplate({
29
+ toolName: 'get_weather',
30
+ input: { city: 'Oslo' },
31
+ output: { city: 'Oslo', tempC: 21 },
32
+ template: (ctx: TemplateContext<{ city: string }, Weather>) =>
33
+ html`<h2>${ctx.output.city} ${badge(`${ctx.output.tempC}°C`)}</h2>`,
34
+ });
35
+ ```
36
+
37
+ `result.html` is a whole HTML document whose body is `<h2>Oslo <span class="badge">21°C</span></h2>`, `result.uiType` is `"html"`, and `result.meta["ui/mimeType"]` is `"text/html;profile=mcp-app"`. Values interpolated into `html` are escaped, so a city of `<script>` renders as `&lt;script&gt;`; markup built with `html` or `trustedHtml()` isn't escaped twice. `html` is the same builder as a tool template's `ctx.helpers.html`, so pieces of a widget can live in their own file and be tested on their own ([Splitting a template into functions](https://frontmcp.dev/reference/ui/components#splitting-a-template-into-functions)).
38
+
39
+ ### Building a page around your own markup
19
40
 
20
41
  ```ts
21
42
  import { buildShell } from '@frontmcp/uipack/shell';
22
43
 
23
- const result = buildShell('<div id="root"></div>', {
44
+ const { html, hash, size } = buildShell('<div id="root"></div>', {
24
45
  toolName: 'get_weather',
25
46
  input: { city: 'London' },
26
47
  output: { temp: 18 },
27
48
  includeBridge: true,
28
49
  csp: { connectDomains: ['https://api.weather.com'] },
29
50
  });
30
-
31
- // result.html -> full HTML document with CSP, data injection, bridge IIFE
32
- // result.hash -> fast 32-bit content hash
33
- // result.size -> byte length
34
51
  ```
35
52
 
36
- ### Render an NPM Component
37
-
38
- ```ts
39
- import { renderComponent } from '@frontmcp/uipack/component';
53
+ `hash` is a short, non-cryptographic hash of the page, for cache keys, and `size` its length in bytes. The page looks like this, with the bridge script shortened:
40
54
 
41
- const result = await renderComponent(
42
- { source: { npm: 'my-weather-widget', exportName: 'WeatherCard' } },
43
- { toolName: 'get_weather', output: { temp: 18 }, includeBridge: true },
44
- );
55
+ ```html
56
+ <!DOCTYPE html>
57
+ <html lang="en">
58
+ <head>
59
+ <meta charset="UTF-8" />
60
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
61
+ <meta
62
+ http-equiv="Content-Security-Policy"
63
+ content="default-src 'none'; script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net ... https://esm.sh; ... connect-src https://cdn.jsdelivr.net ... https://esm.sh https://api.weather.com; ..."
64
+ />
65
+ <script>
66
+ window.__mcpAppsEnabled = true;
67
+ window.__mcpToolName = 'get_weather';
68
+ window.__mcpToolInput = { city: 'London' };
69
+ window.__mcpToolOutput = { temp: 18 };
70
+ window.__mcpStructuredContent = null;
71
+ </script>
72
+ <script>
73
+ /* the bridge: picks an adapter for the host and sets window.FrontMcpBridge */
74
+ </script>
75
+ </head>
76
+ <body>
77
+ <div id="root"></div>
78
+ </body>
79
+ </html>
45
80
  ```
46
81
 
47
- ### Resolve Imports to CDN URLs
82
+ ### Resolving imports to CDN URLs
48
83
 
49
84
  ```ts
50
- import { createEsmShResolver, createImportMap } from '@frontmcp/uipack/resolver';
85
+ import { createEsmShResolver, rewriteImports } from '@frontmcp/uipack/resolver';
51
86
 
52
87
  const resolver = createEsmShResolver({ providerOrder: ['cloudflare', 'jsdelivr'] });
53
- const result = resolver.resolve('chart.js');
54
- // -> { value: 'https://cdnjs.cloudflare.com/ajax/libs/Chart.js/4.5.1/chart.umd.min.js', type: 'url' }
55
- ```
56
-
57
- ### Generate Bridge IIFE
58
88
 
59
- ```ts
60
- import { generateBridgeIIFE, UNIVERSAL_BRIDGE_SCRIPT } from '@frontmcp/uipack/bridge-runtime';
61
-
62
- // Use pre-generated universal script
63
- const script = `<script>${UNIVERSAL_BRIDGE_SCRIPT}</script>`;
89
+ resolver.resolve('chart.js');
90
+ // { value: 'https://cdnjs.cloudflare.com/ajax/libs/Chart.js/4.5.1/chart.umd.min.js', type: 'url' }
64
91
 
65
- // Or generate with custom options
66
- const iife = generateBridgeIIFE({ debug: true, minify: false });
92
+ rewriteImports("import { format } from 'date-fns';", { resolver }).code;
93
+ // "import { format } from 'https://esm.sh/date-fns';"
67
94
  ```
68
95
 
69
- ### Fetch TypeScript Definitions
96
+ A package in the CDN registry (`react`, `react-dom`, `chart.js`, `d3`, `lodash`) resolves to its registered URL, trying the providers in `providerOrder`; any other package resolves to `https://esm.sh/<specifier>`.
70
97
 
71
- ```ts
72
- import { createTypeFetcher } from '@frontmcp/uipack/typings';
98
+ ### Rendering a component from npm
73
99
 
74
- const fetcher = createTypeFetcher({
75
- allowedPackages: ['react', 'zod', '@frontmcp/*'],
76
- maxDepth: 3,
77
- });
100
+ ```ts
101
+ import { renderComponent } from '@frontmcp/uipack/component';
78
102
 
79
- const result = await fetcher.fetchBatch({
80
- imports: ["import { z } from 'zod'"],
81
- });
82
- // result.results[0].files -> TypeFile[] with virtual node_modules/ paths
103
+ const { html } = renderComponent(
104
+ { source: { npm: 'my-weather-widget', exportName: 'WeatherCard' } },
105
+ { toolName: 'get_weather', output: { temp: 18 } },
106
+ );
83
107
  ```
84
108
 
85
- ## Features
86
-
87
- - **Shell builder** — Full HTML documents with CSP, data injection, and bridge runtime
88
- - **Import resolver** — Pluggable CDN resolution (esm.sh, Cloudflare, jsDelivr, unpkg)
89
- - **Component loader** — 4 source modes: npm, file, URL, inline function
90
- - **Bridge IIFE** — Platform-aware vanilla JS bridge (OpenAI, ExtApps, Claude, Gemini, Generic)
91
- - **Type fetching** — Download `.d.ts` from esm.sh with recursive dependency resolution
92
- - **XSS utilities** — `escapeHtml`, `safeJsonForScript`, `escapeScriptClose`, and more
93
- - **Custom shells** — Load shell templates from npm, URL, or inline HTML
94
- - **Standalone types** — SDK-independent type definitions for widget configuration
95
-
96
- ## Entry Points
97
-
98
- | Path | Purpose |
99
- | --------------------------------- | ------------------------------------ |
100
- | `@frontmcp/uipack` | Main barrel (re-exports all modules) |
101
- | `@frontmcp/uipack/resolver` | Pluggable import resolution |
102
- | `@frontmcp/uipack/shell` | HTML shell builder |
103
- | `@frontmcp/uipack/component` | Polymorphic component loader |
104
- | `@frontmcp/uipack/bridge-runtime` | Platform bridge IIFE generator |
105
- | `@frontmcp/uipack/typings` | TypeScript .d.ts fetching engine |
106
- | `@frontmcp/uipack/types` | Standalone type definitions |
107
- | `@frontmcp/uipack/utils` | XSS-safe escaping utilities |
109
+ `renderComponent()` is synchronous. The page imports the component from `https://esm.sh/my-weather-widget`, loads React from esm.sh through an import map, and renders the component into `#root` with the tool's output as its props. The source can be any of these:
108
110
 
109
- ## Module Exports
111
+ | Source | Shape | What happens |
112
+ | -------- | -------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------- |
113
+ | npm | `{ npm: 'my-chart', exportName?, version? }` | Loaded from its CDN registry entry, or from esm.sh. |
114
+ | file | `{ file: '/abs/path/widget.tsx', exportName?, inline? }` | A local file. `.tsx` and `.jsx` files are bundled with esbuild; a relative path resolves from the working directory. |
115
+ | import | `{ import: 'https://cdn.example.com/widget.js', exportName? }` | A module URL, used as it is. |
116
+ | function | `(input, output) => '<div>…</div>'` | Returns the HTML. |
110
117
 
111
- ### Resolver
118
+ ### Generating the bridge script
112
119
 
113
- | Export | Type | Purpose |
114
- | ---------------------------------------------- | -------- | ----------------------------------------------------------------- |
115
- | `createEsmShResolver(opts?)` | function | Default resolver using esm.sh with CDN registry fallback |
116
- | `DEFAULT_CDN_REGISTRY` | constant | Pre-configured entries for react, react-dom, chart.js, d3, lodash |
117
- | `parseImports(source)` | function | Parse source code for named, default, namespace, dynamic imports |
118
- | `rewriteImports(source, resolver, overrides?)` | function | Rewrite bare specifiers to CDN URLs |
119
- | `createImportMap(overrides?)` | function | Generate browser-standard import map |
120
- | `mergeImportMaps(...maps)` | function | Deep-merge multiple import maps |
121
- | `generateImportMapScriptTag(map)` | function | `<script type="importmap">` generation |
122
- | `lookupPackage(name)` | function | Query the CDN registry for a package |
123
- | `getPackageCDNUrl(name, provider?)` | function | Resolve package to best CDN URL |
124
- | `extractExternalPackages(code)` | function | Extract external package names from source |
120
+ ```ts
121
+ import { BRIDGE_SCRIPT_TAGS, generateBridgeIIFE } from '@frontmcp/uipack/bridge-runtime';
125
122
 
126
- ### Shell
123
+ const everyHost = BRIDGE_SCRIPT_TAGS.universal;
124
+ const openAiOnly = `<script>${generateBridgeIIFE({ adapters: ['openai', 'generic'], minify: true })}</script>`;
125
+ ```
127
126
 
128
- | Export | Type | Purpose |
129
- | ------------------------------------------------------------ | -------- | ----------------------------------------------------------------------------------------------- |
130
- | `buildShell(content, config)` | function | Main entry point -> `ShellResult` |
131
- | `buildCSPMetaTag(config?)` | function | Generate `<meta>` CSP tag |
132
- | `buildCSPDirectives(config?)` | function | Merge custom domains with defaults |
133
- | `buildDataInjectionScript(toolName, input?, output?)` | function | `<script>` setting `window.__mcp*` globals |
134
- | `createTemplateHelpers(toolName)` | function | `html`, `trustedHtml`, `escapeHtml`, `formatDate`, `formatCurrency`, `uniqueId`, `jsonEmbed` |
135
- | `` html`…` ``, `trustedHtml(markup)`, `isTrustedHtml(value)` | function | Build / mark / detect `TrustedHtml` template markup (interpolated values in `html` are escaped) |
136
- | `resolveShellTemplate(opts)` | function | Resolve custom shell from npm/URL/inline |
137
- | `applyShellTemplate(template, values)` | function | Apply values to resolved template |
138
- | `validateShellTemplate(template)` | function | Validate a custom shell template |
139
- | `DEFAULT_CDN_DOMAINS` | constant | jsDelivr, Cloudflare, Google Fonts |
127
+ The script detects the host and sets `window.FrontMcpBridge`:
140
128
 
141
- ### Component
129
+ | Member | Description |
130
+ | ---------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------- |
131
+ | `adapterId`, `initialized`, `capabilities` | The adapter picked for the host, whether it's ready, and what it can do. |
132
+ | `getToolInput()`, `getToolOutput()`, `getStructuredContent()` | The call's data, from the page and then from each result the host sends. |
133
+ | `callTool(name, args)` | Calls a tool through the host and returns the host's result. |
134
+ | `onToolResult(callback)`, `onContextChange(callback)` | Subscribe to results and to host changes. Each returns an unsubscribe function. |
135
+ | `getTheme()`, `getDisplayMode()`, `getHostContext()`, `hasCapability(name)` | What the host said about itself. |
136
+ | `sendMessage(text)`, `openLink(url)` | Add a message to the conversation, open a link, through the host. |
137
+ | `requestDisplayMode(mode)`, `setSize({ width?, height? })`, `requestClose()` | Ask for `inline`, `fullscreen` or `pip`, report the widget's size, ask to remove the widget. |
138
+ | `updateModelContext(data, merge?)` | Tell the host what the model should know, where the host supports it. |
139
+ | `getWidgetState()`, `setWidgetState(state)` | State kept in `localStorage`. |
142
140
 
143
- | Export | Type | Purpose |
144
- | ------------------------------------------------------------------- | -------- | -------------------------------------------------- |
145
- | `renderComponent(config, shell)` | function | Top-level compositor -> `ShellResult` |
146
- | `resolveUISource(source, opts)` | function | Resolve any source to `ResolvedComponent` |
147
- | `generateMountScript(resolved, data)` | function | ES module mount script for React/module components |
148
- | `isNpmSource`, `isFileSource`, `isImportSource`, `isFunctionSource` | guards | Type narrowing helpers |
141
+ ### Fetching TypeScript definitions
149
142
 
150
- ### Bridge Runtime
143
+ ```ts
144
+ import { createTypeFetcher } from '@frontmcp/uipack/typings';
151
145
 
152
- | Export | Type | Purpose |
153
- | ---------------------------------- | -------- | ---------------------------------------------------------------------- |
154
- | `generateBridgeIIFE(opts?)` | function | Generate the bridge IIFE string |
155
- | `generatePlatformBundle(platform)` | function | Platform-specific bundles (`chatgpt`, `claude`, `gemini`, `universal`) |
156
- | `UNIVERSAL_BRIDGE_SCRIPT` | constant | Pre-generated script with all adapters |
157
- | `BRIDGE_SCRIPT_TAGS` | constant | `{ universal, chatgpt, claude, gemini }` pre-wrapped `<script>` tags |
146
+ const fetcher = createTypeFetcher({ allowedPackages: ['@myorg/*'], maxDepth: 3 });
147
+ const { results, errors } = await fetcher.fetchBatch({ imports: ["import { z } from 'zod'"] });
148
+ // results[0].files: the .d.ts files, at virtual node_modules/ paths
149
+ ```
158
150
 
159
- ### Typings
151
+ It fetches the `.d.ts` files of each import from esm.sh, following their own imports up to `maxDepth` (default 2). Only allowed packages are fetched: `react`, `react-dom`, `react/jsx-runtime`, `zod` and `@frontmcp/*`, plus `allowedPackages`, or any package with `allowedPackages: false`.
160
152
 
161
- | Export | Type | Purpose |
162
- | ----------------------------- | -------- | ------------------------------------------------------------- |
163
- | `createTypeFetcher(opts?)` | function | Factory creating a `TypeFetcher` instance |
164
- | `TypeFetcher` | class | Main fetcher with caching and recursive dependency resolution |
165
- | `MemoryTypeCache` | class | LRU cache with TTL support |
166
- | `globalTypeCache` | constant | Module-level singleton cache (500 entries, 1h TTL) |
167
- | `parseDtsImports(content)` | function | Parse `.d.ts` files for imports/references |
168
- | `combineDtsContents(results)` | function | Combine multiple `.d.ts` files into one |
153
+ ## Entry points
169
154
 
170
- ### Utils
155
+ `@frontmcp/uipack` exports everything below. Each module is also its own entry point:
171
156
 
172
- | Export | Type | Purpose |
173
- | ------------------------------ | -------- | ---------------------------------------------------------------------------- |
174
- | `escapeHtml(str)` | function | Escape `&`, `<`, `>`, `"`, `'`, U+2028/2029 |
175
- | `escapeHtmlAttr(str)` | function | Lighter escape for attributes |
176
- | `escapeJsString(str)` | function | Escape for JS string literals |
177
- | `escapeScriptClose(str)` | function | Replace `</` with `<\/` to prevent `</script>` breakout |
178
- | `safeJsonForScript(value)` | function | JSON serialize safe for `<script>` embedding (handles BigInt, circular refs) |
179
- | `safeStringify(value, space?)` | function | `JSON.stringify` with circular reference detection |
157
+ | Import | What's in it |
158
+ | --------------------------------- | ------------------------------------------------------------------------------------------------------------- |
159
+ | `@frontmcp/uipack/adapters` | `renderToolTemplate()`, `MCP_APPS_MIME_TYPE`, and the helpers the SDK uses to build a tool's widget response. |
160
+ | `@frontmcp/uipack/shell` | `buildShell()`, `html`, `trustedHtml()`, template helpers, CSP, custom shells. |
161
+ | `@frontmcp/uipack/resolver` | Import resolution, the CDN registry, import maps. |
162
+ | `@frontmcp/uipack/component` | `renderComponent()` and the component sources. |
163
+ | `@frontmcp/uipack/bridge-runtime` | The bridge script generator. |
164
+ | `@frontmcp/uipack/typings` | The `.d.ts` fetcher and its cache. |
165
+ | `@frontmcp/uipack/types` | Types for widget configuration, such as `UITemplateConfig` and `TemplateContext`. |
166
+ | `@frontmcp/uipack/utils` | Escaping helpers. |
180
167
 
181
- ## Shell HTML Structure
168
+ ### Adapters
182
169
 
183
- Annotated output of `buildShell` with `withShell: true` and `includeBridge: true`:
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"`. |
184
174
 
185
- ```html
186
- <!DOCTYPE html>
187
- <html lang="en">
188
- <head>
189
- <meta charset="UTF-8" />
190
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
191
- <title>get_weather</title>
175
+ ### Shell
192
176
 
193
- <!-- CSP: default CDN domains + custom connectDomains/resourceDomains -->
194
- <meta
195
- http-equiv="Content-Security-Policy"
196
- content="default-src 'self'; script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net ..."
197
- />
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. |
198
187
 
199
- <!-- Data injection: tool context as window globals -->
200
- <script>
201
- window.__mcpToolName = 'get_weather';
202
- window.__mcpToolInput = { city: 'London' };
203
- window.__mcpToolOutput = { temp: 18 };
204
- window.__mcpStructuredContent = null;
205
- </script>
188
+ ### Resolver
206
189
 
207
- <!-- Bridge IIFE: auto-detects platform, exposes window.FrontMcpBridge -->
208
- <script>
209
- (function () {
210
- /* ... adapters, FrontMcpBridge class ... */
211
- })();
212
- </script>
213
- </head>
214
- <body>
215
- <!-- Content from resolver + component -->
216
- <script type="importmap">
217
- { "imports": { "react": "https://esm.sh/react@19" } }
218
- </script>
219
- <div id="root"></div>
220
- <script type="module">
221
- import { WeatherCard } from 'https://esm.sh/my-weather-widget';
190
+ | Export | Description |
191
+ | ---------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- |
192
+ | `createEsmShResolver({ registry?, providerOrder?, fallbackCdnBase? })` | The default resolver. |
193
+ | `createResolverWithOverrides(overrides, options?)` | The default resolver, with fixed URLs for some packages. |
194
+ | `DEFAULT_CDN_REGISTRY`, `mergeRegistries(custom)` | The registry (`react`, `react-dom`, `chart.js`, `d3`, `lodash`), and it merged with your entries. |
195
+ | `lookupPackage(name, registry?)`, `getPackageCDNUrl(name, providerOrder?, registry?)` | Query the registry. |
196
+ | `parseImports(source)`, `extractExternalPackages(source)` | The imports in source code, and the packages they name. |
197
+ | `rewriteImports(source, { resolver?, skipPackages?, overrides? })` | Replaces bare specifiers with CDN URLs: `{ code, rewrittenCount, rewrites, fallbackPackages }`. |
198
+ | `createImportMap(dependencies)`, `mergeImportMaps(...maps)`, `generateImportMapScriptTag(map)` | Build, merge and write a browser import map. |
222
199
 
223
- /* mount logic */
224
- </script>
225
- </body>
226
- </html>
227
- ```
200
+ ### Component
228
201
 
229
- ## Component Source Modes
230
-
231
- | Mode | Config Shape | Description |
232
- | ---------- | ------------------------------------------------- | ---------------------------------- |
233
- | **npm** | `{ npm: 'my-chart', exportName?, version? }` | Resolved via CDN registry / esm.sh |
234
- | **file** | `{ file: './widget.html', inline? }` | Local file path |
235
- | **import** | `{ import: 'https://cdn.example.com/widget.js' }` | Direct URL passthrough |
236
- | **inline** | `(input, output) => '<div>...</div>'` | Function returning HTML string |
237
-
238
- ## Bridge API
239
-
240
- The bridge IIFE exposes `window.FrontMcpBridge` with the following API:
241
-
242
- | Method | Description |
243
- | --------------------------- | ------------------------------------ |
244
- | `callTool(name, args)` | Invoke an MCP tool from the widget |
245
- | `getToolOutput()` | Read injected tool output data |
246
- | `getToolInput()` | Read injected tool input data |
247
- | `sendMessage(content)` | Send message to host |
248
- | `openLink(url)` | Open a URL via platform adapter |
249
- | `requestDisplayMode(mode)` | Switch inline / fullscreen / pip |
250
- | `setWidgetState(state)` | Persist widget state to localStorage |
251
- | `onContextChange(callback)` | Subscribe to host context changes |
252
- | `onToolResult(callback)` | Subscribe to tool result events |
253
- | `hasCapability(cap)` | Check adapter capabilities |
254
- | `getTheme()` | Get current theme from host |
255
- | `getDisplayMode()` | Get current display mode |
256
- | `getHostContext()` | Get host context metadata |
202
+ | Export | Description |
203
+ | ------------------------------------------------------------------- | ------------------------------------------------------------------------------- |
204
+ | `renderComponent(config, shellConfig)` | Resolves `config.source` and builds the page around it: `{ html, hash, size }`. |
205
+ | `resolveUISource(source, options?)` | Resolves a source to a `ResolvedComponent`. |
206
+ | `generateMountScript(resolved, propsMapping?)` | The module script that imports and mounts a module component. |
207
+ | `bundleFileSource()`, `transpileReactSource()` | Bundle or transpile a `.tsx` or `.jsx` widget with esbuild. |
208
+ | `isNpmSource`, `isFileSource`, `isImportSource`, `isFunctionSource` | Type guards for the sources. |
257
209
 
258
- ## Contributing
210
+ ### Bridge runtime
259
211
 
260
- ### Architecture
261
-
262
- ```mermaid
263
- graph TD
264
- subgraph "@frontmcp/uipack"
265
- IDX["index.ts<br/><i>Main barrel exports</i>"]
266
-
267
- subgraph bridge-runtime["bridge-runtime/"]
268
- BR_IIFE["iife-generator.ts<br/>5 platform adapters:<br/>OpenAI, ExtApps, Claude,<br/>Gemini, Generic"]
269
- end
270
-
271
- subgraph component["component/"]
272
- CO_TYPES["types.ts<br/>NpmSource, FileSource,<br/>ImportSource, FunctionSource"]
273
- CO_LOAD["loader.ts<br/>resolveUISource(),<br/>generateMountScript()"]
274
- CO_REND["renderer.ts<br/>renderComponent()"]
275
- end
276
-
277
- subgraph resolver["resolver/"]
278
- RE_CDN["cdn-registry.ts<br/>DEFAULT_CDN_REGISTRY,<br/>lookupPackage()"]
279
- RE_ESM["esm-sh.resolver.ts<br/>createEsmShResolver()"]
280
- RE_PARSE["import-parser.ts<br/>parseImports()"]
281
- RE_REWRITE["import-rewriter.ts<br/>rewriteImports()"]
282
- RE_MAP["import-map.ts<br/>createImportMap(),<br/>mergeImportMaps()"]
283
- end
284
-
285
- subgraph shell["shell/"]
286
- SH_BUILD["builder.ts<br/>buildShell()"]
287
- SH_CSP["csp.ts<br/>CSP directives + meta tag"]
288
- SH_DATA["data-injector.ts<br/>window.__mcp* globals"]
289
- SH_CUSTOM["custom-shell-*.ts<br/>Resolve, validate, apply<br/>custom shell templates"]
290
- end
291
-
292
- subgraph types["types/"]
293
- TY_CFG["ui-config.ts<br/>UITemplateConfig,<br/>WidgetServingMode"]
294
- TY_RT["ui-runtime.ts<br/>UIType, WidgetManifest,<br/>WidgetConfig"]
295
- end
296
-
297
- subgraph typings["typings/"]
298
- TP_FETCH["type-fetcher.ts<br/>TypeFetcher class"]
299
- TP_DTS["dts-parser.ts<br/>parseDtsImports()"]
300
- TP_SCHEMA["schemas.ts<br/>Zod validation"]
301
- TP_CACHE["cache/<br/>MemoryTypeCache (LRU + TTL)"]
302
- end
303
-
304
- subgraph utils["utils/"]
305
- UT["escapeHtml, safeJsonForScript,<br/>escapeScriptClose, safeStringify"]
306
- end
307
- end
308
- ```
212
+ | Export | Description |
213
+ | -------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
214
+ | `generateBridgeIIFE(options?)` | The bridge script. Options: `adapters` (from `openai`, `ext-apps`, `claude`, `gemini`, `generic`), `debug`, `trustedOrigins`, `minify`. |
215
+ | `generatePlatformBundle(platform, options?)` | The script for `chatgpt`, `claude`, `gemini` or `universal`. |
216
+ | `UNIVERSAL_BRIDGE_SCRIPT` | The script with every adapter. |
217
+ | `BRIDGE_SCRIPT_TAGS` | `{ universal, chatgpt, claude, gemini }`, each wrapped in `<script>`. |
309
218
 
310
- ### Module Dependency Graph
311
-
312
- ```mermaid
313
- graph BT
314
- utils["utils<br/><i>foundation, zero deps</i>"]
315
- resolver["resolver<br/><i>import resolution</i>"]
316
- shell["shell<br/><i>HTML shell builder</i>"]
317
- bridge["bridge-runtime<br/><i>IIFE generator, standalone</i>"]
318
- component["component<br/><i>polymorphic loader</i>"]
319
- types["types<br/><i>standalone, no internal deps</i>"]
320
- typings["typings<br/><i>standalone, uses zod</i>"]
321
-
322
- resolver --> utils
323
- shell --> utils
324
- shell --> resolver
325
- shell --> bridge
326
- component --> utils
327
- component --> resolver
328
- component --> shell
329
- ```
219
+ ### Typings
330
220
 
331
- ### How @frontmcp/uipack works with @frontmcp/ui
221
+ | Export | Description |
222
+ | ---------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
223
+ | `createTypeFetcher(options?, cache?)` | A `TypeFetcher`. Options: `allowedPackages`, `maxDepth`, `timeout` (10 s per request), `maxConcurrency` (5), `cdnBaseUrl`, `fetch`. |
224
+ | `TypeFetcher` | `fetchBatch({ imports, maxDepth?, timeout? })`: `{ results, errors, totalTimeMs, cacheHits }`. |
225
+ | `MemoryTypeCache`, `globalTypeCache` | A least-recently-used cache with expiry; the shared one keeps up to 500 entries for an hour. |
226
+ | `parseDtsImports(content)`, `combineDtsContents(contents)` | Parse a `.d.ts` file's imports; join several files into one. |
332
227
 
333
- `@frontmcp/uipack` runs **server-side** (Node.js) and produces complete HTML documents. `@frontmcp/ui` runs **browser-side** and provides React components and hooks that mount inside those documents.
228
+ ### Utils
334
229
 
335
- **Bridge contract:** uipack generates a vanilla JS IIFE that creates `window.FrontMcpBridge`. The `@frontmcp/ui` bridge module wraps this same API in a React-friendly class and hooks (`useCallTool`, etc.). The ui package re-exports bridge IIFE generators from uipack via `@frontmcp/ui/bridge/runtime`.
230
+ | Export | Description |
231
+ | ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
232
+ | `escapeHtml(value)` | Escapes `&`, `<`, `>`, `"` and `'`, and U+2028 and U+2029. `null` and `undefined` give `''`. |
233
+ | `escapeHtmlAttr(value)` | Escapes `&` and `"`, for attribute values. |
234
+ | `escapeJsString(value)` | Escapes a string for a JavaScript string literal. |
235
+ | `escapeScriptClose(json)` | Replaces `</` with `<\/`, so the text can't close a `<script>`. |
236
+ | `safeJsonForScript(value)` | JSON safe to put in a `<script>`: `<`, `>` and `&` escaped, BigInt as a string, and `{"error":"Value could not be serialized"}` for a value JSON can't hold, such as a circular one. |
237
+ | `safeStringify(value, space?)` | `JSON.stringify` that writes `[Circular]` for circular references. |
336
238
 
337
- **Dependency direction:** `@frontmcp/ui` optionally depends on `@frontmcp/uipack` (optional dependency). They can be used independently — uipack alone for vanilla JS widgets, or together for React-based widgets.
239
+ ## Docs
338
240
 
339
- **End-to-end data flow:**
241
+ - [Widget components](https://frontmcp.dev/reference/ui/components): `@frontmcp/uipack` in a server, `html` and `renderToolTemplate()`
242
+ - [The `ui` option](https://frontmcp.dev/reference/ui): templates, serving modes, Content Security Policy
243
+ - [Hosts and platforms](https://frontmcp.dev/reference/ui/hosts): the bridge, `window.FrontMcpBridge`, and what each host gets
244
+ - [Choosing how a widget is served](https://frontmcp.dev/learn/choosing-how-a-widget-is-served): inline and static pages, and what a widget can load
340
245
 
341
- ```mermaid
342
- sequenceDiagram
343
- participant Dev as Developer
344
- participant SDK as @frontmcp/sdk
345
- participant UP as @frontmcp/uipack
346
- participant Browser
347
- participant UI as @frontmcp/ui
246
+ ## Related packages
348
247
 
349
- Dev->>SDK: @Tool({ ui: { npm: '...' } })
350
- SDK->>UP: renderComponent(config, shellConfig)
351
- UP->>UP: resolve deps (esm.sh)
352
- UP->>UP: build shell (CSP + data injection)
353
- UP->>UP: inject bridge IIFE
354
- UP-->>SDK: ShellResult { html, hash, size }
248
+ - [`@frontmcp/sdk`](https://www.npmjs.com/package/@frontmcp/sdk): the server framework; it renders a tool's `ui` with this package.
249
+ - [`@frontmcp/ui`](https://www.npmjs.com/package/@frontmcp/ui): the React components, hooks and renderers for widget files.
250
+ - [`@frontmcp/testing`](https://www.npmjs.com/package/@frontmcp/testing): test helpers, with matchers for a tool's rendered widget.
355
251
 
356
- Browser->>SDK: readResource("ui://widget/tool.html")
357
- SDK-->>Browser: cached HTML
252
+ ## Contributing
358
253
 
359
- Browser->>Browser: Load @frontmcp/ui via esm.sh
360
- Browser->>UI: Mount React components
361
- UI->>UI: Bridge hooks read window.__mcp* globals
362
- UI->>Browser: Interactive widget rendered
363
- ```
254
+ Source: [`libs/uipack`](https://github.com/agentfront/frontmcp/tree/main/libs/uipack). Issues and pull requests are welcome; see [CONTRIBUTING.md](https://github.com/agentfront/frontmcp/blob/main/CONTRIBUTING.md).
364
255
 
365
- ### Development
256
+ The source has one folder per entry point under `src/`: `adapters/`, `shell/`, `resolver/`, `component/`, `bridge-runtime/`, `typings/`, `types/` and `utils/`. `utils` and `bridge-runtime` depend on nothing else; `resolver` uses `utils`; `shell` uses `utils`, `resolver` and `bridge-runtime`; `component` builds on `shell`; `adapters` uses them all; `typings` stands alone, and `types` only imports types. Test escaping and Content Security Policy changes with hostile input. In the monorepo:
366
257
 
367
258
  ```bash
368
- # Build
369
259
  nx build uipack
370
-
371
- # Test
372
260
  nx test uipack
373
-
374
- # Lint
375
261
  nx lint uipack
376
262
  ```
377
263
 
378
- ### Testing
379
-
380
- - 95%+ coverage required across statements, branches, functions, lines
381
- - **XSS tests** are critical — test all escaping functions with malicious input
382
- - **CSP tests** — verify generated Content-Security-Policy headers are correct
383
- - Test all 4 component source modes (npm, file, import, inline)
384
- - Test bridge IIFE generation for each platform
385
-
386
- ## Requirements
387
-
388
- - Node.js >= 22.0.0
389
-
390
- ## Related Packages
391
-
392
- - [`@frontmcp/ui`](../ui) — React components, hooks, and renderers that consume uipack output
393
- - [`@frontmcp/sdk`](../sdk) — Core FrontMCP SDK with `ToolUIRegistry`
394
- - [`@frontmcp/testing`](../testing) — UI test assertions
395
-
396
264
  ## License
397
265
 
398
- Apache-2.0 — see [LICENSE](../../LICENSE).
266
+ Apache-2.0. See [LICENSE](https://github.com/agentfront/frontmcp/blob/main/LICENSE).
package/esm/package.json CHANGED
@@ -1,23 +1,26 @@
1
1
  {
2
2
  "name": "@frontmcp/uipack",
3
- "version": "1.9.5",
4
- "description": "FrontMCP UIpack - HTML shell builder, pluggable import resolver, and NPM component loader for MCP UI (React-free core)",
3
+ "version": "1.9.7",
4
+ "description": "React-free core that builds FrontMCP widget pages: HTML shell with CSP and bridge, template rendering, CDN import resolution",
5
5
  "author": "AgentFront <info@agentfront.dev>",
6
- "homepage": "https://frontmcp.dev/reference/ui",
6
+ "homepage": "https://frontmcp.dev/reference/ui/components#frontmcpuipack",
7
7
  "license": "Apache-2.0",
8
8
  "keywords": [
9
+ "frontmcp",
9
10
  "mcp",
11
+ "model-context-protocol",
12
+ "mcp-apps",
13
+ "widgets",
10
14
  "ui",
11
- "bundler",
12
- "template",
13
- "agentfront",
14
- "frontmcp",
15
- "framework",
16
- "typescript"
15
+ "html",
16
+ "csp",
17
+ "import-map",
18
+ "agentfront"
17
19
  ],
18
20
  "repository": {
19
21
  "type": "git",
20
- "url": "git+https://github.com/agentfront/frontmcp.git"
22
+ "url": "git+https://github.com/agentfront/frontmcp.git",
23
+ "directory": "libs/uipack"
21
24
  },
22
25
  "bugs": {
23
26
  "url": "https://github.com/agentfront/frontmcp/issues"
@@ -61,7 +64,7 @@
61
64
  "typescript": "^5.9.3"
62
65
  },
63
66
  "peerDependencies": {
64
- "@frontmcp/lazy-zod": "1.9.5",
67
+ "@frontmcp/lazy-zod": "1.9.7",
65
68
  "esbuild": ">=0.27.0 <1"
66
69
  },
67
70
  "peerDependenciesMeta": {
package/package.json CHANGED
@@ -1,23 +1,26 @@
1
1
  {
2
2
  "name": "@frontmcp/uipack",
3
- "version": "1.9.5",
4
- "description": "FrontMCP UIpack - HTML shell builder, pluggable import resolver, and NPM component loader for MCP UI (React-free core)",
3
+ "version": "1.9.7",
4
+ "description": "React-free core that builds FrontMCP widget pages: HTML shell with CSP and bridge, template rendering, CDN import resolution",
5
5
  "author": "AgentFront <info@agentfront.dev>",
6
- "homepage": "https://frontmcp.dev/reference/ui",
6
+ "homepage": "https://frontmcp.dev/reference/ui/components#frontmcpuipack",
7
7
  "license": "Apache-2.0",
8
8
  "keywords": [
9
+ "frontmcp",
9
10
  "mcp",
11
+ "model-context-protocol",
12
+ "mcp-apps",
13
+ "widgets",
10
14
  "ui",
11
- "bundler",
12
- "template",
13
- "agentfront",
14
- "frontmcp",
15
- "framework",
16
- "typescript"
15
+ "html",
16
+ "csp",
17
+ "import-map",
18
+ "agentfront"
17
19
  ],
18
20
  "repository": {
19
21
  "type": "git",
20
- "url": "git+https://github.com/agentfront/frontmcp.git"
22
+ "url": "git+https://github.com/agentfront/frontmcp.git",
23
+ "directory": "libs/uipack"
21
24
  },
22
25
  "bugs": {
23
26
  "url": "https://github.com/agentfront/frontmcp/issues"
@@ -61,7 +64,7 @@
61
64
  "typescript": "^5.9.3"
62
65
  },
63
66
  "peerDependencies": {
64
- "@frontmcp/lazy-zod": "1.9.5",
67
+ "@frontmcp/lazy-zod": "1.9.7",
65
68
  "esbuild": ">=0.27.0 <1"
66
69
  },
67
70
  "peerDependenciesMeta": {