@frontmcp/uipack 1.10.8 → 1.10.9-rc.2
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.
- package/adapters/index.js +29 -2040
- package/bridge-runtime/index.js +11 -217
- package/component/index.js +24 -1469
- package/esm/adapters/index.mjs +29 -2006
- package/esm/bridge-runtime/index.mjs +11 -185
- package/esm/component/index.mjs +24 -1439
- package/esm/index.mjs +150 -3483
- package/esm/package.json +2 -2
- package/esm/resolver/index.mjs +2 -660
- package/esm/shell/index.mjs +16 -750
- package/esm/types/index.mjs +1 -75
- package/esm/typings/index.mjs +3 -971
- package/esm/utils/index.mjs +1 -66
- package/index.js +150 -3605
- package/package.json +2 -2
- package/resolver/index.js +2 -707
- package/shell/index.js +16 -803
- package/types/index.js +1 -108
- package/typings/index.js +3 -1032
- package/utils/index.js +1 -96
package/esm/component/index.mjs
CHANGED
|
@@ -1,515 +1,7 @@
|
|
|
1
1
|
var __frontmcpModule = typeof process !== "undefined" && typeof process.getBuiltinModule === "function" ? process.getBuiltinModule("module") : undefined;
|
|
2
2
|
const require = __frontmcpModule ? __frontmcpModule.createRequire(import.meta.url || 'file:///') : undefined;
|
|
3
3
|
// __frontmcpCreateRequire
|
|
4
|
-
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
5
|
-
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
6
|
-
}) : x)(function(x) {
|
|
7
|
-
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
8
|
-
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
9
|
-
});
|
|
10
|
-
|
|
11
|
-
// libs/uipack/src/component/types.ts
|
|
12
|
-
function isNpmSource(source) {
|
|
13
|
-
return typeof source === "object" && source !== null && "npm" in source;
|
|
14
|
-
}
|
|
15
|
-
function isFileSource(source) {
|
|
16
|
-
return typeof source === "object" && source !== null && "file" in source;
|
|
17
|
-
}
|
|
18
|
-
function isImportSource(source) {
|
|
19
|
-
return typeof source === "object" && source !== null && "import" in source;
|
|
20
|
-
}
|
|
21
|
-
function isFunctionSource(source) {
|
|
22
|
-
return typeof source === "function";
|
|
23
|
-
}
|
|
24
|
-
var FRONTMCP_META_KEY = "__frontmcp_meta";
|
|
25
|
-
|
|
26
|
-
// libs/uipack/src/resolver/cdn-registry.ts
|
|
27
|
-
var DEFAULT_CDN_REGISTRY = {
|
|
28
|
-
react: {
|
|
29
|
-
packageName: "react",
|
|
30
|
-
defaultVersion: "19.2.4",
|
|
31
|
-
providers: {
|
|
32
|
-
cloudflare: {
|
|
33
|
-
url: "https://cdnjs.cloudflare.com/ajax/libs/react/19.2.4/umd/react.production.min.js",
|
|
34
|
-
global: "React",
|
|
35
|
-
crossorigin: "anonymous"
|
|
36
|
-
},
|
|
37
|
-
jsdelivr: {
|
|
38
|
-
url: "https://cdn.jsdelivr.net/npm/react@19.2.4/umd/react.production.min.js",
|
|
39
|
-
global: "React",
|
|
40
|
-
crossorigin: "anonymous"
|
|
41
|
-
},
|
|
42
|
-
unpkg: {
|
|
43
|
-
url: "https://unpkg.com/react@19.2.4/umd/react.production.min.js",
|
|
44
|
-
global: "React",
|
|
45
|
-
crossorigin: "anonymous"
|
|
46
|
-
},
|
|
47
|
-
"esm.sh": {
|
|
48
|
-
url: "https://esm.sh/react@19.2.4",
|
|
49
|
-
esm: true,
|
|
50
|
-
crossorigin: "anonymous"
|
|
51
|
-
}
|
|
52
|
-
},
|
|
53
|
-
preferredProviders: ["esm.sh", "cloudflare", "jsdelivr", "unpkg"],
|
|
54
|
-
metadata: {
|
|
55
|
-
description: "A JavaScript library for building user interfaces",
|
|
56
|
-
homepage: "https://react.dev",
|
|
57
|
-
license: "MIT"
|
|
58
|
-
}
|
|
59
|
-
},
|
|
60
|
-
"react-dom": {
|
|
61
|
-
packageName: "react-dom",
|
|
62
|
-
defaultVersion: "19.2.4",
|
|
63
|
-
providers: {
|
|
64
|
-
cloudflare: {
|
|
65
|
-
url: "https://cdnjs.cloudflare.com/ajax/libs/react-dom/19.2.4/umd/react-dom.production.min.js",
|
|
66
|
-
global: "ReactDOM",
|
|
67
|
-
crossorigin: "anonymous",
|
|
68
|
-
peerDependencies: ["react"]
|
|
69
|
-
},
|
|
70
|
-
jsdelivr: {
|
|
71
|
-
url: "https://cdn.jsdelivr.net/npm/react-dom@19.2.4/umd/react-dom.production.min.js",
|
|
72
|
-
global: "ReactDOM",
|
|
73
|
-
crossorigin: "anonymous",
|
|
74
|
-
peerDependencies: ["react"]
|
|
75
|
-
},
|
|
76
|
-
unpkg: {
|
|
77
|
-
url: "https://unpkg.com/react-dom@19.2.4/umd/react-dom.production.min.js",
|
|
78
|
-
global: "ReactDOM",
|
|
79
|
-
crossorigin: "anonymous",
|
|
80
|
-
peerDependencies: ["react"]
|
|
81
|
-
},
|
|
82
|
-
"esm.sh": {
|
|
83
|
-
url: "https://esm.sh/react-dom@19.2.4",
|
|
84
|
-
esm: true,
|
|
85
|
-
crossorigin: "anonymous",
|
|
86
|
-
peerDependencies: ["react"]
|
|
87
|
-
}
|
|
88
|
-
},
|
|
89
|
-
preferredProviders: ["esm.sh", "cloudflare", "jsdelivr", "unpkg"],
|
|
90
|
-
metadata: {
|
|
91
|
-
description: "React package for working with the DOM",
|
|
92
|
-
homepage: "https://react.dev",
|
|
93
|
-
license: "MIT"
|
|
94
|
-
}
|
|
95
|
-
},
|
|
96
|
-
"chart.js": {
|
|
97
|
-
packageName: "chart.js",
|
|
98
|
-
defaultVersion: "4.5.1",
|
|
99
|
-
providers: {
|
|
100
|
-
cloudflare: {
|
|
101
|
-
url: "https://cdnjs.cloudflare.com/ajax/libs/Chart.js/4.5.1/chart.umd.min.js",
|
|
102
|
-
global: "Chart",
|
|
103
|
-
crossorigin: "anonymous"
|
|
104
|
-
},
|
|
105
|
-
jsdelivr: {
|
|
106
|
-
url: "https://cdn.jsdelivr.net/npm/chart.js@4.5.1/dist/chart.umd.min.js",
|
|
107
|
-
global: "Chart",
|
|
108
|
-
crossorigin: "anonymous"
|
|
109
|
-
},
|
|
110
|
-
unpkg: {
|
|
111
|
-
url: "https://unpkg.com/chart.js@4.5.1/dist/chart.umd.min.js",
|
|
112
|
-
global: "Chart",
|
|
113
|
-
crossorigin: "anonymous"
|
|
114
|
-
}
|
|
115
|
-
},
|
|
116
|
-
preferredProviders: ["esm.sh", "cloudflare", "jsdelivr", "unpkg"],
|
|
117
|
-
metadata: {
|
|
118
|
-
description: "Simple yet flexible JavaScript charting library",
|
|
119
|
-
homepage: "https://www.chartjs.org",
|
|
120
|
-
license: "MIT"
|
|
121
|
-
}
|
|
122
|
-
},
|
|
123
|
-
d3: {
|
|
124
|
-
packageName: "d3",
|
|
125
|
-
defaultVersion: "7.9.0",
|
|
126
|
-
providers: {
|
|
127
|
-
cloudflare: {
|
|
128
|
-
url: "https://cdnjs.cloudflare.com/ajax/libs/d3/7.9.0/d3.min.js",
|
|
129
|
-
integrity: "sha512-vc58qvvBdrDR4etbxMdlTt4GBQk1qjvyORR2nrsPsFPyrs+/u5c3+1Ct6upOgdZoIl7eq6k3a1UPDSNAQi/32A==",
|
|
130
|
-
global: "d3",
|
|
131
|
-
crossorigin: "anonymous"
|
|
132
|
-
},
|
|
133
|
-
jsdelivr: {
|
|
134
|
-
url: "https://cdn.jsdelivr.net/npm/d3@7.9.0/dist/d3.min.js",
|
|
135
|
-
global: "d3",
|
|
136
|
-
crossorigin: "anonymous"
|
|
137
|
-
},
|
|
138
|
-
unpkg: {
|
|
139
|
-
url: "https://unpkg.com/d3@7.9.0/dist/d3.min.js",
|
|
140
|
-
global: "d3",
|
|
141
|
-
crossorigin: "anonymous"
|
|
142
|
-
}
|
|
143
|
-
},
|
|
144
|
-
preferredProviders: ["esm.sh", "cloudflare", "jsdelivr", "unpkg"],
|
|
145
|
-
metadata: {
|
|
146
|
-
description: "Data-Driven Documents",
|
|
147
|
-
homepage: "https://d3js.org",
|
|
148
|
-
license: "ISC"
|
|
149
|
-
}
|
|
150
|
-
},
|
|
151
|
-
lodash: {
|
|
152
|
-
packageName: "lodash",
|
|
153
|
-
defaultVersion: "4.17.21",
|
|
154
|
-
providers: {
|
|
155
|
-
cloudflare: {
|
|
156
|
-
url: "https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.21/lodash.min.js",
|
|
157
|
-
integrity: "sha512-WFN04846sdKMIP5LKNphMaWzU7YpMyCU245etK3g/2ARYbPK9Ub18eG+ljU96qKRCWh+quCY7yefSmlkQw1ANQ==",
|
|
158
|
-
global: "_",
|
|
159
|
-
crossorigin: "anonymous"
|
|
160
|
-
},
|
|
161
|
-
jsdelivr: {
|
|
162
|
-
url: "https://cdn.jsdelivr.net/npm/lodash@4.17.21/lodash.min.js",
|
|
163
|
-
global: "_",
|
|
164
|
-
crossorigin: "anonymous"
|
|
165
|
-
},
|
|
166
|
-
unpkg: {
|
|
167
|
-
url: "https://unpkg.com/lodash@4.17.21/lodash.min.js",
|
|
168
|
-
global: "_",
|
|
169
|
-
crossorigin: "anonymous"
|
|
170
|
-
}
|
|
171
|
-
},
|
|
172
|
-
preferredProviders: ["esm.sh", "cloudflare", "jsdelivr", "unpkg"],
|
|
173
|
-
metadata: {
|
|
174
|
-
description: "A modern JavaScript utility library",
|
|
175
|
-
homepage: "https://lodash.com",
|
|
176
|
-
license: "MIT"
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
};
|
|
180
|
-
function lookupPackage(packageName, registry = DEFAULT_CDN_REGISTRY) {
|
|
181
|
-
return registry[packageName];
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
// libs/uipack/src/resolver/import-parser.ts
|
|
185
|
-
var IMPORT_PATTERNS = {
|
|
186
|
-
named: /import\s*\{([^}]+)\}\s*from\s*['"]([^'"]+)['"]/g,
|
|
187
|
-
default: /import\s+(\w+)\s+from\s*['"]([^'"]+)['"]/g,
|
|
188
|
-
namespace: /import\s*\*\s*as\s+(\w+)\s+from\s*['"]([^'"]+)['"]/g,
|
|
189
|
-
sideEffect: /import\s*['"]([^'"]+)['"]/g,
|
|
190
|
-
dynamic: /(?:await\s+)?import\s*\(\s*['"]([^'"]+)['"]\s*\)/g,
|
|
191
|
-
defaultAndNamed: /import\s+(\w+)\s*,\s*\{([^}]+)\}\s*from\s*['"]([^'"]+)['"]/g,
|
|
192
|
-
defaultAndNamespace: /import\s+(\w+)\s*,\s*\*\s*as\s+(\w+)\s+from\s*['"]([^'"]+)['"]/g,
|
|
193
|
-
reExport: /export\s*\{[^}]+\}\s*from\s*['"]([^'"]+)['"]/g,
|
|
194
|
-
reExportAll: /export\s*\*\s*from\s*['"]([^'"]+)['"]/g
|
|
195
|
-
};
|
|
196
|
-
function parseNamedImports(namedString) {
|
|
197
|
-
return namedString.split(",").map((s) => s.trim()).filter((s) => s.length > 0).map((s) => {
|
|
198
|
-
const asMatch = s.match(/^(\w+)\s+as\s+\w+$/);
|
|
199
|
-
return asMatch ? asMatch[1] : s;
|
|
200
|
-
});
|
|
201
|
-
}
|
|
202
|
-
function isRelativeImport(specifier) {
|
|
203
|
-
return specifier.startsWith("./") || specifier.startsWith("../") || specifier.startsWith("/");
|
|
204
|
-
}
|
|
205
|
-
function isExternalImport(specifier) {
|
|
206
|
-
return !isRelativeImport(specifier) && !specifier.startsWith("#");
|
|
207
|
-
}
|
|
208
|
-
function getPackageName(specifier) {
|
|
209
|
-
if (specifier.startsWith("@")) {
|
|
210
|
-
const parts = specifier.split("/");
|
|
211
|
-
if (parts.length >= 2) {
|
|
212
|
-
return `${parts[0]}/${parts[1]}`;
|
|
213
|
-
}
|
|
214
|
-
return specifier;
|
|
215
|
-
}
|
|
216
|
-
const firstSlash = specifier.indexOf("/");
|
|
217
|
-
return firstSlash === -1 ? specifier : specifier.slice(0, firstSlash);
|
|
218
|
-
}
|
|
219
|
-
function getLineNumber(source, index) {
|
|
220
|
-
let line = 1;
|
|
221
|
-
for (let i = 0; i < index && i < source.length; i++) {
|
|
222
|
-
if (source[i] === "\n") {
|
|
223
|
-
line++;
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
return line;
|
|
227
|
-
}
|
|
228
|
-
function getColumnNumber(source, index) {
|
|
229
|
-
let column = 0;
|
|
230
|
-
for (let i = index - 1; i >= 0 && source[i] !== "\n"; i--) {
|
|
231
|
-
column++;
|
|
232
|
-
}
|
|
233
|
-
return column;
|
|
234
|
-
}
|
|
235
|
-
function parseImports(source) {
|
|
236
|
-
const imports = [];
|
|
237
|
-
const seenStatements = /* @__PURE__ */ new Set();
|
|
238
|
-
const addImport = (imp) => {
|
|
239
|
-
const key = `${imp.type}:${imp.specifier}:${imp.statement}`;
|
|
240
|
-
if (!seenStatements.has(key)) {
|
|
241
|
-
seenStatements.add(key);
|
|
242
|
-
imports.push(imp);
|
|
243
|
-
}
|
|
244
|
-
};
|
|
245
|
-
let match;
|
|
246
|
-
const defaultAndNamedRegex = new RegExp(IMPORT_PATTERNS.defaultAndNamed.source, "g");
|
|
247
|
-
while ((match = defaultAndNamedRegex.exec(source)) !== null) {
|
|
248
|
-
const [statement, defaultName, namedString, specifier] = match;
|
|
249
|
-
addImport({
|
|
250
|
-
statement,
|
|
251
|
-
specifier,
|
|
252
|
-
type: "default",
|
|
253
|
-
defaultImport: defaultName,
|
|
254
|
-
namedImports: parseNamedImports(namedString),
|
|
255
|
-
line: getLineNumber(source, match.index),
|
|
256
|
-
column: getColumnNumber(source, match.index)
|
|
257
|
-
});
|
|
258
|
-
}
|
|
259
|
-
const defaultAndNamespaceRegex = new RegExp(IMPORT_PATTERNS.defaultAndNamespace.source, "g");
|
|
260
|
-
while ((match = defaultAndNamespaceRegex.exec(source)) !== null) {
|
|
261
|
-
const [statement, defaultName, namespaceName, specifier] = match;
|
|
262
|
-
addImport({
|
|
263
|
-
statement,
|
|
264
|
-
specifier,
|
|
265
|
-
type: "default",
|
|
266
|
-
defaultImport: defaultName,
|
|
267
|
-
namespaceImport: namespaceName,
|
|
268
|
-
line: getLineNumber(source, match.index),
|
|
269
|
-
column: getColumnNumber(source, match.index)
|
|
270
|
-
});
|
|
271
|
-
}
|
|
272
|
-
const namedRegex = new RegExp(IMPORT_PATTERNS.named.source, "g");
|
|
273
|
-
while ((match = namedRegex.exec(source)) !== null) {
|
|
274
|
-
const [statement, namedString, specifier] = match;
|
|
275
|
-
addImport({
|
|
276
|
-
statement,
|
|
277
|
-
specifier,
|
|
278
|
-
type: "named",
|
|
279
|
-
namedImports: parseNamedImports(namedString),
|
|
280
|
-
line: getLineNumber(source, match.index),
|
|
281
|
-
column: getColumnNumber(source, match.index)
|
|
282
|
-
});
|
|
283
|
-
}
|
|
284
|
-
const defaultRegex = new RegExp(IMPORT_PATTERNS.default.source, "g");
|
|
285
|
-
while ((match = defaultRegex.exec(source)) !== null) {
|
|
286
|
-
const [statement, defaultName, specifier] = match;
|
|
287
|
-
const afterMatch = source.slice(match.index + match[0].length - specifier.length - 2);
|
|
288
|
-
if (afterMatch.startsWith(",")) continue;
|
|
289
|
-
addImport({
|
|
290
|
-
statement,
|
|
291
|
-
specifier,
|
|
292
|
-
type: "default",
|
|
293
|
-
defaultImport: defaultName,
|
|
294
|
-
line: getLineNumber(source, match.index),
|
|
295
|
-
column: getColumnNumber(source, match.index)
|
|
296
|
-
});
|
|
297
|
-
}
|
|
298
|
-
const namespaceRegex = new RegExp(IMPORT_PATTERNS.namespace.source, "g");
|
|
299
|
-
while ((match = namespaceRegex.exec(source)) !== null) {
|
|
300
|
-
const [statement, namespaceName, specifier] = match;
|
|
301
|
-
addImport({
|
|
302
|
-
statement,
|
|
303
|
-
specifier,
|
|
304
|
-
type: "namespace",
|
|
305
|
-
namespaceImport: namespaceName,
|
|
306
|
-
line: getLineNumber(source, match.index),
|
|
307
|
-
column: getColumnNumber(source, match.index)
|
|
308
|
-
});
|
|
309
|
-
}
|
|
310
|
-
const sideEffectRegex = new RegExp(IMPORT_PATTERNS.sideEffect.source, "g");
|
|
311
|
-
while ((match = sideEffectRegex.exec(source)) !== null) {
|
|
312
|
-
const [statement, specifier] = match;
|
|
313
|
-
const beforeMatch = source.slice(Math.max(0, match.index - 50), match.index);
|
|
314
|
-
if (beforeMatch.includes("from")) continue;
|
|
315
|
-
addImport({
|
|
316
|
-
statement,
|
|
317
|
-
specifier,
|
|
318
|
-
type: "side-effect",
|
|
319
|
-
line: getLineNumber(source, match.index),
|
|
320
|
-
column: getColumnNumber(source, match.index)
|
|
321
|
-
});
|
|
322
|
-
}
|
|
323
|
-
const dynamicRegex = new RegExp(IMPORT_PATTERNS.dynamic.source, "g");
|
|
324
|
-
while ((match = dynamicRegex.exec(source)) !== null) {
|
|
325
|
-
const [statement, specifier] = match;
|
|
326
|
-
addImport({
|
|
327
|
-
statement,
|
|
328
|
-
specifier,
|
|
329
|
-
type: "dynamic",
|
|
330
|
-
line: getLineNumber(source, match.index),
|
|
331
|
-
column: getColumnNumber(source, match.index)
|
|
332
|
-
});
|
|
333
|
-
}
|
|
334
|
-
const reExportRegex = new RegExp(IMPORT_PATTERNS.reExport.source, "g");
|
|
335
|
-
while ((match = reExportRegex.exec(source)) !== null) {
|
|
336
|
-
const [statement, specifier] = match;
|
|
337
|
-
addImport({
|
|
338
|
-
statement,
|
|
339
|
-
specifier,
|
|
340
|
-
type: "named",
|
|
341
|
-
line: getLineNumber(source, match.index),
|
|
342
|
-
column: getColumnNumber(source, match.index)
|
|
343
|
-
});
|
|
344
|
-
}
|
|
345
|
-
const reExportAllRegex = new RegExp(IMPORT_PATTERNS.reExportAll.source, "g");
|
|
346
|
-
while ((match = reExportAllRegex.exec(source)) !== null) {
|
|
347
|
-
const [statement, specifier] = match;
|
|
348
|
-
addImport({
|
|
349
|
-
statement,
|
|
350
|
-
specifier,
|
|
351
|
-
type: "namespace",
|
|
352
|
-
line: getLineNumber(source, match.index),
|
|
353
|
-
column: getColumnNumber(source, match.index)
|
|
354
|
-
});
|
|
355
|
-
}
|
|
356
|
-
const externalImports = imports.filter((imp) => isExternalImport(imp.specifier));
|
|
357
|
-
const relativeImports = imports.filter((imp) => isRelativeImport(imp.specifier));
|
|
358
|
-
const externalPackages = [...new Set(externalImports.map((imp) => getPackageName(imp.specifier)))];
|
|
359
|
-
return {
|
|
360
|
-
imports,
|
|
361
|
-
externalImports,
|
|
362
|
-
relativeImports,
|
|
363
|
-
externalPackages
|
|
364
|
-
};
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
// libs/uipack/src/resolver/esm-sh.resolver.ts
|
|
368
|
-
var DEFAULT_FALLBACK_CDN = "https://esm.sh";
|
|
369
|
-
function createEsmShResolver(options = {}) {
|
|
370
|
-
const {
|
|
371
|
-
fallbackCdnBase = DEFAULT_FALLBACK_CDN,
|
|
372
|
-
registry = DEFAULT_CDN_REGISTRY,
|
|
373
|
-
providerOrder = ["esm.sh", "cloudflare", "jsdelivr", "unpkg"]
|
|
374
|
-
} = options;
|
|
375
|
-
return {
|
|
376
|
-
resolve(specifier, _context) {
|
|
377
|
-
if (specifier.startsWith("./") || specifier.startsWith("../") || specifier.startsWith("/")) {
|
|
378
|
-
return null;
|
|
379
|
-
}
|
|
380
|
-
if (specifier.startsWith("node:") || specifier.startsWith("#")) {
|
|
381
|
-
return null;
|
|
382
|
-
}
|
|
383
|
-
const pkgName = getPackageName(specifier);
|
|
384
|
-
const entry = lookupPackage(pkgName, registry);
|
|
385
|
-
if (entry) {
|
|
386
|
-
for (const provider of providerOrder) {
|
|
387
|
-
const config = entry.providers[provider];
|
|
388
|
-
if (config?.url) {
|
|
389
|
-
const subpath = specifier.slice(pkgName.length);
|
|
390
|
-
if (subpath && config.url.includes("esm.sh/")) {
|
|
391
|
-
return {
|
|
392
|
-
value: config.url + subpath,
|
|
393
|
-
type: "url",
|
|
394
|
-
integrity: config.integrity
|
|
395
|
-
};
|
|
396
|
-
}
|
|
397
|
-
if (subpath) {
|
|
398
|
-
return {
|
|
399
|
-
value: `${fallbackCdnBase}/${specifier}`,
|
|
400
|
-
type: "url"
|
|
401
|
-
};
|
|
402
|
-
}
|
|
403
|
-
if (config.global && !config.esm) {
|
|
404
|
-
return {
|
|
405
|
-
value: config.url,
|
|
406
|
-
type: "url",
|
|
407
|
-
integrity: config.integrity
|
|
408
|
-
};
|
|
409
|
-
}
|
|
410
|
-
return {
|
|
411
|
-
value: config.url,
|
|
412
|
-
type: "url",
|
|
413
|
-
integrity: config.integrity
|
|
414
|
-
};
|
|
415
|
-
}
|
|
416
|
-
}
|
|
417
|
-
}
|
|
418
|
-
return {
|
|
419
|
-
value: `${fallbackCdnBase}/${specifier}`,
|
|
420
|
-
type: "url"
|
|
421
|
-
};
|
|
422
|
-
}
|
|
423
|
-
};
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
// libs/uipack/src/utils/index.ts
|
|
427
|
-
var SCRIPT_UNSAFE_CHARACTERS = /[<>&\u2028\u2029]/g;
|
|
428
|
-
function toUnicodeEscape(character) {
|
|
429
|
-
return `\\u${character.charCodeAt(0).toString(16).padStart(4, "0")}`;
|
|
430
|
-
}
|
|
431
|
-
function safeJsonForScript(value) {
|
|
432
|
-
if (value === void 0) {
|
|
433
|
-
return "null";
|
|
434
|
-
}
|
|
435
|
-
try {
|
|
436
|
-
const jsonString = JSON.stringify(value, (_key, val) => {
|
|
437
|
-
if (typeof val === "bigint") {
|
|
438
|
-
return val.toString();
|
|
439
|
-
}
|
|
440
|
-
return val;
|
|
441
|
-
});
|
|
442
|
-
if (jsonString === void 0) {
|
|
443
|
-
return "null";
|
|
444
|
-
}
|
|
445
|
-
return jsonString.replace(SCRIPT_UNSAFE_CHARACTERS, toUnicodeEscape);
|
|
446
|
-
} catch {
|
|
447
|
-
return '{"error":"Value could not be serialized"}';
|
|
448
|
-
}
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
// libs/uipack/src/component/ui-availability.ts
|
|
452
|
-
function isFrontmcpUiResolvable(...candidatePaths) {
|
|
453
|
-
try {
|
|
454
|
-
const nodeFs = __require("fs");
|
|
455
|
-
const nodePath = __require("path");
|
|
456
|
-
const ORG = "@frontmcp";
|
|
457
|
-
const PKG = "ui";
|
|
458
|
-
for (const candidate of candidatePaths) {
|
|
459
|
-
let dir = candidate;
|
|
460
|
-
while (true) {
|
|
461
|
-
const pkgJson = nodePath.join(dir, "node_modules", ORG, PKG, "package.json");
|
|
462
|
-
if (nodeFs.existsSync(pkgJson)) return true;
|
|
463
|
-
const parent = nodePath.dirname(dir);
|
|
464
|
-
if (parent === dir) break;
|
|
465
|
-
dir = parent;
|
|
466
|
-
}
|
|
467
|
-
}
|
|
468
|
-
return false;
|
|
469
|
-
} catch {
|
|
470
|
-
return false;
|
|
471
|
-
}
|
|
472
|
-
}
|
|
473
|
-
|
|
474
|
-
// libs/uipack/src/component/transpiler.ts
|
|
475
|
-
function loadEsbuild(filename) {
|
|
476
|
-
try {
|
|
477
|
-
return __require("esbuild");
|
|
478
|
-
} catch (err) {
|
|
479
|
-
if (!isMissingEsbuild(err)) throw err;
|
|
480
|
-
const subject = filename ? `FileSource widget "${filename}"` : "A FileSource widget";
|
|
481
|
-
throw new Error(
|
|
482
|
-
`${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.`,
|
|
483
|
-
{ cause: err }
|
|
484
|
-
);
|
|
485
|
-
}
|
|
486
|
-
}
|
|
487
|
-
function isMissingEsbuild(err) {
|
|
488
|
-
if (!(err instanceof Error)) return false;
|
|
489
|
-
return err.code === "MODULE_NOT_FOUND" && /Cannot find module ['"]esbuild['"]/.test(err.message);
|
|
490
|
-
}
|
|
491
|
-
function transpileReactSource(source, filename) {
|
|
492
|
-
const esbuild = loadEsbuild(filename);
|
|
493
|
-
const loader = filename?.endsWith(".tsx") ? "tsx" : "jsx";
|
|
494
|
-
const result = esbuild.transformSync(source, {
|
|
495
|
-
loader,
|
|
496
|
-
jsx: "transform",
|
|
497
|
-
jsxFactory: "React.createElement",
|
|
498
|
-
jsxFragment: "React.Fragment",
|
|
499
|
-
format: "esm",
|
|
500
|
-
target: "es2020",
|
|
501
|
-
define: { "process.env.NODE_ENV": '"production"' }
|
|
502
|
-
});
|
|
503
|
-
return result.code;
|
|
504
|
-
}
|
|
505
|
-
function bundleFileSource(source, filename, resolveDir, componentName, options = {}) {
|
|
506
|
-
if (!isFrontmcpUiResolvable(resolveDir, process.cwd())) {
|
|
507
|
-
throw new Error(
|
|
508
|
-
`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.`
|
|
509
|
-
);
|
|
510
|
-
}
|
|
511
|
-
const esbuild = loadEsbuild(filename);
|
|
512
|
-
const mountCode = `
|
|
4
|
+
var __require=(x=>typeof require!=="undefined"?require:typeof Proxy!=="undefined"?new Proxy(x,{get:(a,b)=>(typeof require!=="undefined"?require:a)[b]}):x)(function(x){if(typeof require!=="undefined")return require.apply(this,arguments);throw Error('Dynamic require of "'+x+'" is not supported')});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 FRONTMCP_META_KEY="__frontmcp_meta";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"}}}}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 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=`
|
|
513
5
|
// --- Auto-generated mount ---
|
|
514
6
|
import { createElement as __h } from 'react';
|
|
515
7
|
import { createRoot } from 'react-dom/client';
|
|
@@ -551,364 +43,19 @@ if (__root) {
|
|
|
551
43
|
if (d) __render(d.structuredContent !== undefined ? d.structuredContent : d.content !== undefined ? d.content : d);
|
|
552
44
|
});
|
|
553
45
|
}
|
|
554
|
-
}`;
|
|
555
|
-
const loader = {
|
|
556
|
-
".tsx": "tsx",
|
|
557
|
-
".jsx": "jsx"
|
|
558
|
-
};
|
|
559
|
-
const stdinLoader = filename.endsWith(".tsx") ? "tsx" : "jsx";
|
|
560
|
-
const alias = {};
|
|
561
|
-
try {
|
|
562
|
-
const nodePath = __require("path");
|
|
563
|
-
const nodeFs = __require("fs");
|
|
564
|
-
const candidates = [
|
|
565
|
-
nodePath.join(process.cwd(), "node_modules", "@frontmcp", "ui", "dist", "esm"),
|
|
566
|
-
nodePath.join(resolveDir, "node_modules", "@frontmcp", "ui", "dist", "esm")
|
|
567
|
-
];
|
|
568
|
-
for (const uiEsmBase of candidates) {
|
|
569
|
-
if (!nodeFs.existsSync(uiEsmBase)) continue;
|
|
570
|
-
const subpaths = ["components", "react", "theme", "bridge", "runtime"];
|
|
571
|
-
for (const sub of subpaths) {
|
|
572
|
-
const mjs = nodePath.join(uiEsmBase, sub, "index.mjs");
|
|
573
|
-
if (nodeFs.existsSync(mjs)) alias[`@frontmcp/ui/${sub}`] = mjs;
|
|
574
|
-
}
|
|
575
|
-
if (Object.keys(alias).length > 0) break;
|
|
576
|
-
}
|
|
577
|
-
} catch {
|
|
578
|
-
}
|
|
579
|
-
try {
|
|
580
|
-
const result = esbuild.buildSync({
|
|
581
|
-
stdin: {
|
|
582
|
-
contents: source + "\n" + mountCode,
|
|
583
|
-
loader: stdinLoader,
|
|
584
|
-
resolveDir,
|
|
585
|
-
sourcefile: filename
|
|
586
|
-
},
|
|
587
|
-
bundle: true,
|
|
588
|
-
write: false,
|
|
589
|
-
format: "esm",
|
|
590
|
-
target: "es2020",
|
|
591
|
-
jsx: "automatic",
|
|
592
|
-
// When `bundleReact` is set (resourceMode: 'inline'), React itself is
|
|
593
|
-
// bundled — required for hosts that block external script execution
|
|
594
|
-
// such as Claude (#454). Otherwise React stays external and is loaded
|
|
595
|
-
// via the import map emitted by the renderer.
|
|
596
|
-
external: options.bundleReact ? [] : ["react", "react-dom", "react/jsx-runtime", "react/jsx-dev-runtime"],
|
|
597
|
-
alias,
|
|
598
|
-
define: { "process.env.NODE_ENV": '"production"' },
|
|
599
|
-
platform: "browser",
|
|
600
|
-
treeShaking: true,
|
|
601
|
-
logLevel: "warning",
|
|
602
|
-
loader
|
|
603
|
-
});
|
|
604
|
-
return { code: result.outputFiles[0].text };
|
|
605
|
-
} catch (err) {
|
|
606
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
607
|
-
throw new Error(
|
|
608
|
-
`Failed to bundle FileSource "${filename}": ${message}. If the error mentions @frontmcp/ui or @frontmcp/uipack, ensure both packages are installed in the consuming project.`,
|
|
609
|
-
{ cause: err }
|
|
610
|
-
);
|
|
611
|
-
}
|
|
612
|
-
}
|
|
613
|
-
function extractDefaultExportName(code) {
|
|
614
|
-
let match = code.match(/export\s+default\s+(?:function|class)\s+(\w+)/);
|
|
615
|
-
if (match) return match[1];
|
|
616
|
-
match = code.match(/export\s+default\s+(\w+)\s*;/);
|
|
617
|
-
if (match) return match[1];
|
|
618
|
-
return null;
|
|
619
|
-
}
|
|
620
|
-
|
|
621
|
-
// libs/uipack/src/component/loader.ts
|
|
622
|
-
var DEFAULT_META = {
|
|
623
|
-
mcpAware: false,
|
|
624
|
-
renderer: "auto"
|
|
625
|
-
};
|
|
626
|
-
function resolveUISource(source, options) {
|
|
627
|
-
if (options?.inlineReact && options?.transformOnly) {
|
|
628
|
-
throw new Error("resolveUISource: `inlineReact` and `transformOnly` are mutually exclusive \u2014 set at most one.");
|
|
629
|
-
}
|
|
630
|
-
const resolver = options?.resolver ?? createEsmShResolver();
|
|
631
|
-
if (isNpmSource(source)) {
|
|
632
|
-
return resolveNpmSource(source, resolver);
|
|
633
|
-
}
|
|
634
|
-
if (isImportSource(source)) {
|
|
635
|
-
return resolveImportSource(source);
|
|
636
|
-
}
|
|
637
|
-
if (isFileSource(source)) {
|
|
638
|
-
return resolveFileSource(source, { inlineReact: options?.inlineReact, transformOnly: options?.transformOnly });
|
|
639
|
-
}
|
|
640
|
-
if (isFunctionSource(source)) {
|
|
641
|
-
return resolveFunctionSource(source, options?.input, options?.output);
|
|
642
|
-
}
|
|
643
|
-
throw new Error("Unknown UISource type");
|
|
644
|
-
}
|
|
645
|
-
function resolveNpmSource(source, resolver) {
|
|
646
|
-
const specifier = source.version ? `${source.npm}@${source.version}` : source.npm;
|
|
647
|
-
const pkgName = getPackageName(source.npm);
|
|
648
|
-
const resolved = resolver.resolve(specifier);
|
|
649
|
-
const url = resolved?.value ?? `https://esm.sh/${specifier}`;
|
|
650
|
-
const peerDeps = [];
|
|
651
|
-
if (pkgName !== "react" && pkgName !== "react-dom") {
|
|
652
|
-
peerDeps.push("react", "react-dom");
|
|
653
|
-
}
|
|
654
|
-
return {
|
|
655
|
-
mode: "module",
|
|
656
|
-
url,
|
|
657
|
-
exportName: source.exportName ?? "default",
|
|
658
|
-
meta: { ...DEFAULT_META, renderer: "react" },
|
|
659
|
-
peerDependencies: peerDeps
|
|
660
|
-
};
|
|
661
|
-
}
|
|
662
|
-
function resolveImportSource(source) {
|
|
663
|
-
return {
|
|
664
|
-
mode: "module",
|
|
665
|
-
url: source.import,
|
|
666
|
-
exportName: source.exportName ?? "default",
|
|
667
|
-
meta: { ...DEFAULT_META },
|
|
668
|
-
peerDependencies: []
|
|
669
|
-
};
|
|
670
|
-
}
|
|
671
|
-
var BUILD_DIR_SEGMENTS = /* @__PURE__ */ new Set(["dist", "build", "out"]);
|
|
672
|
-
function missingWidgetError(filePath, cause) {
|
|
673
|
-
const sourceWidget = findSourceWidget(filePath);
|
|
674
|
-
const error = new Error(
|
|
675
|
-
`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\`.` : ""),
|
|
676
|
-
{ cause }
|
|
677
|
-
);
|
|
678
|
-
error.code = "ENOENT";
|
|
679
|
-
error.path = filePath;
|
|
680
|
-
return error;
|
|
681
|
-
}
|
|
682
|
-
function findSourceWidget(filePath) {
|
|
683
|
-
const path = __require("path");
|
|
684
|
-
const fs = __require("fs");
|
|
685
|
-
const parts = filePath.split(path.sep);
|
|
686
|
-
for (let i = parts.length - 2; i >= 0; i--) {
|
|
687
|
-
if (!BUILD_DIR_SEGMENTS.has(parts[i])) continue;
|
|
688
|
-
const candidates = [[...parts.slice(0, i), "src", ...parts.slice(i + 1)]];
|
|
689
|
-
if (parts.length - i > 2) candidates.push([...parts.slice(0, i), "src", ...parts.slice(i + 2)]);
|
|
690
|
-
candidates.push([...parts.slice(0, i), ...parts.slice(i + 1)]);
|
|
691
|
-
for (const candidate of candidates) {
|
|
692
|
-
const candidatePath = candidate.join(path.sep);
|
|
693
|
-
try {
|
|
694
|
-
if (fs.existsSync(candidatePath)) return candidatePath;
|
|
695
|
-
} catch {
|
|
696
|
-
}
|
|
697
|
-
}
|
|
698
|
-
return void 0;
|
|
699
|
-
}
|
|
700
|
-
return void 0;
|
|
701
|
-
}
|
|
702
|
-
function resolveFileSource(source, options = {}) {
|
|
703
|
-
const path = __require("path");
|
|
704
|
-
const ext = path.extname(source.file).toLowerCase();
|
|
705
|
-
if (ext === ".tsx" || ext === ".jsx") {
|
|
706
|
-
const fs = __require("fs");
|
|
707
|
-
const wasRelative = !path.isAbsolute(source.file);
|
|
708
|
-
const filePath = wasRelative ? path.resolve(process.cwd(), source.file) : source.file;
|
|
709
|
-
let rawSource;
|
|
710
|
-
try {
|
|
711
|
-
rawSource = fs.readFileSync(filePath, "utf-8");
|
|
712
|
-
} catch (err) {
|
|
713
|
-
const isNotFound = err?.code === "ENOENT";
|
|
714
|
-
if (isNotFound && wasRelative) {
|
|
715
|
-
throw new Error(
|
|
716
|
-
`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).`,
|
|
717
|
-
{ cause: err }
|
|
718
|
-
);
|
|
719
|
-
}
|
|
720
|
-
if (isNotFound) throw missingWidgetError(filePath, err);
|
|
721
|
-
throw err;
|
|
722
|
-
}
|
|
723
|
-
const componentName = source.exportName || extractDefaultExportName(rawSource) || "Component";
|
|
724
|
-
if (options.transformOnly === true) {
|
|
725
|
-
const code = transpileReactSource(rawSource, path.basename(filePath));
|
|
726
|
-
const parsed2 = parseImports(code);
|
|
727
|
-
return {
|
|
728
|
-
mode: "module",
|
|
729
|
-
code,
|
|
730
|
-
imports: [...new Set(parsed2.externalImports.map((i) => i.specifier))],
|
|
731
|
-
exportName: componentName,
|
|
732
|
-
meta: { mcpAware: true, renderer: "react" },
|
|
733
|
-
peerDependencies: source.peerDependencies || ["react", "react-dom"],
|
|
734
|
-
bundled: false
|
|
735
|
-
};
|
|
736
|
-
}
|
|
737
|
-
const bundled = bundleFileSource(rawSource, source.file, path.dirname(filePath), componentName, {
|
|
738
|
-
bundleReact: options.inlineReact === true
|
|
739
|
-
});
|
|
740
|
-
const parsed = parseImports(bundled.code);
|
|
741
|
-
return {
|
|
742
|
-
mode: "module",
|
|
743
|
-
code: bundled.code,
|
|
744
|
-
imports: [...new Set(parsed.externalImports.map((i) => i.specifier))],
|
|
745
|
-
exportName: componentName,
|
|
746
|
-
meta: { mcpAware: true, renderer: "react" },
|
|
747
|
-
peerDependencies: source.peerDependencies || ["react", "react-dom"],
|
|
748
|
-
bundled: true
|
|
749
|
-
};
|
|
750
|
-
}
|
|
751
|
-
if (source.inline) {
|
|
752
|
-
return {
|
|
753
|
-
mode: "inline",
|
|
754
|
-
html: `<!-- File source: ${source.file} (inline mode - content to be resolved at build time) -->`,
|
|
755
|
-
exportName: "default",
|
|
756
|
-
meta: { ...DEFAULT_META, renderer: "html" },
|
|
757
|
-
peerDependencies: []
|
|
758
|
-
};
|
|
759
|
-
}
|
|
760
|
-
return {
|
|
761
|
-
mode: "module",
|
|
762
|
-
url: source.file,
|
|
763
|
-
exportName: "default",
|
|
764
|
-
meta: { ...DEFAULT_META },
|
|
765
|
-
peerDependencies: []
|
|
766
|
-
};
|
|
767
|
-
}
|
|
768
|
-
function resolveFunctionSource(source, input, output) {
|
|
769
|
-
const html = source(input, output);
|
|
770
|
-
return {
|
|
771
|
-
mode: "inline",
|
|
772
|
-
html,
|
|
773
|
-
exportName: "default",
|
|
774
|
-
meta: { mcpAware: false, renderer: "html" },
|
|
775
|
-
peerDependencies: []
|
|
776
|
-
};
|
|
777
|
-
}
|
|
778
|
-
function generateMountScript(resolved, propsMapping) {
|
|
779
|
-
if (resolved.mode !== "module" || !resolved.url) {
|
|
780
|
-
return "";
|
|
781
|
-
}
|
|
782
|
-
const componentImport = resolved.exportName === "default" ? `import Component from '${resolved.url}';` : `import { ${resolved.exportName} as Component } from '${resolved.url}';`;
|
|
783
|
-
if (resolved.meta.mcpAware) {
|
|
784
|
-
return `${componentImport}
|
|
46
|
+
}`;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}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 html=source(input,output);return{mode:"inline",html,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}
|
|
785
47
|
import React from 'react';
|
|
786
48
|
import { createRoot } from 'react-dom/client';
|
|
787
49
|
createRoot(document.getElementById('root'))
|
|
788
|
-
.render(React.createElement(Component))
|
|
789
|
-
}
|
|
790
|
-
const propsCode = propsMapping ? generateMappedPropsCode(propsMapping) : "window.__mcpToolOutput";
|
|
791
|
-
return `${componentImport}
|
|
50
|
+
.render(React.createElement(Component));`}const propsCode=propsMapping?generateMappedPropsCode(propsMapping):"window.__mcpToolOutput";return`${componentImport}
|
|
792
51
|
import React from 'react';
|
|
793
52
|
import { createRoot } from 'react-dom/client';
|
|
794
53
|
const output = window.__mcpToolOutput || {};
|
|
795
54
|
const props = ${propsCode};
|
|
796
55
|
createRoot(document.getElementById('root'))
|
|
797
|
-
.render(React.createElement(Component, props))
|
|
798
|
-
}
|
|
799
|
-
function generateMappedPropsCode(mapping) {
|
|
800
|
-
const entries = Object.entries(mapping).map(([prop, path]) => {
|
|
801
|
-
const accessPath = path.split(".").map((part) => `[${safeJsonForScript(part)}]`).join("");
|
|
802
|
-
return `${safeJsonForScript(prop)}: output${accessPath}`;
|
|
803
|
-
}).join(", ");
|
|
804
|
-
return `({ ${entries} })`;
|
|
805
|
-
}
|
|
806
|
-
|
|
807
|
-
// libs/uipack/src/resolver/import-map.ts
|
|
808
|
-
function createImportMapFromResolved(resolved) {
|
|
809
|
-
const imports = {};
|
|
810
|
-
const integrity = {};
|
|
811
|
-
for (const [specifier, res] of Object.entries(resolved)) {
|
|
812
|
-
if (res.type === "url") {
|
|
813
|
-
imports[specifier] = res.value;
|
|
814
|
-
if (res.integrity) {
|
|
815
|
-
integrity[res.value] = res.integrity;
|
|
816
|
-
}
|
|
817
|
-
}
|
|
818
|
-
}
|
|
819
|
-
return {
|
|
820
|
-
imports,
|
|
821
|
-
integrity: Object.keys(integrity).length > 0 ? integrity : void 0
|
|
822
|
-
};
|
|
823
|
-
}
|
|
824
|
-
function generateImportMapScriptTag(map) {
|
|
825
|
-
const json = JSON.stringify(map, null, 2).replace(/<\//g, "<\\/");
|
|
826
|
-
return `<script type="importmap">
|
|
56
|
+
.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} })`}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">
|
|
827
57
|
${json}
|
|
828
|
-
</script
|
|
829
|
-
}
|
|
830
|
-
|
|
831
|
-
// libs/uipack/src/bridge-runtime/iife-generator.ts
|
|
832
|
-
var WIDGET_CALL_META_KEY = "frontmcp/widgetCall";
|
|
833
|
-
var MCP_APPS_DISPLAY_MODES = ["inline", "fullscreen", "pip"];
|
|
834
|
-
function generateBridgeIIFE(options = {}) {
|
|
835
|
-
const { debug = false, trustedOrigins = [], minify = false } = options;
|
|
836
|
-
const adapters = options.adapters || ["openai", "ext-apps", "claude", "gemini", "generic"];
|
|
837
|
-
const parts = [];
|
|
838
|
-
parts.push("(function() {");
|
|
839
|
-
parts.push('"use strict";');
|
|
840
|
-
parts.push("");
|
|
841
|
-
if (debug) {
|
|
842
|
-
parts.push('function log(msg) { console.log("[FrontMcpBridge] " + msg); }');
|
|
843
|
-
} else {
|
|
844
|
-
parts.push("function log() {}");
|
|
845
|
-
}
|
|
846
|
-
parts.push("");
|
|
847
|
-
parts.push("var DEFAULT_SAFE_AREA = { top: 0, bottom: 0, left: 0, right: 0 };");
|
|
848
|
-
parts.push("");
|
|
849
|
-
parts.push(generateContextDetection());
|
|
850
|
-
parts.push("");
|
|
851
|
-
parts.push(generateBaseCapabilities());
|
|
852
|
-
parts.push("");
|
|
853
|
-
if (adapters.includes("openai")) {
|
|
854
|
-
parts.push(generateOpenAIAdapter());
|
|
855
|
-
parts.push("");
|
|
856
|
-
}
|
|
857
|
-
if (adapters.includes("ext-apps")) {
|
|
858
|
-
parts.push(generateExtAppsAdapter(trustedOrigins));
|
|
859
|
-
parts.push("");
|
|
860
|
-
}
|
|
861
|
-
if (adapters.includes("claude")) {
|
|
862
|
-
parts.push(generateClaudeAdapter());
|
|
863
|
-
parts.push("");
|
|
864
|
-
}
|
|
865
|
-
if (adapters.includes("gemini")) {
|
|
866
|
-
parts.push(generateGeminiAdapter());
|
|
867
|
-
parts.push("");
|
|
868
|
-
}
|
|
869
|
-
if (adapters.includes("generic")) {
|
|
870
|
-
parts.push(generateGenericAdapter());
|
|
871
|
-
parts.push("");
|
|
872
|
-
}
|
|
873
|
-
parts.push(generatePlatformDetection(adapters));
|
|
874
|
-
parts.push("");
|
|
875
|
-
parts.push(generateBridgeClass());
|
|
876
|
-
parts.push("");
|
|
877
|
-
parts.push("var bridge = new FrontMcpBridge();");
|
|
878
|
-
parts.push("bridge.initialize().then(function() {");
|
|
879
|
-
parts.push(' log("Bridge initialized with adapter: " + bridge.adapterId);');
|
|
880
|
-
parts.push(' window.dispatchEvent(new CustomEvent("bridge:ready", { detail: { adapter: bridge.adapterId } }));');
|
|
881
|
-
parts.push(" if (window.__mcpDisplayMode) bridge.requestDisplayMode(window.__mcpDisplayMode).catch(function() {});");
|
|
882
|
-
parts.push("}).catch(function(err) {");
|
|
883
|
-
parts.push(' console.error("[FrontMcpBridge] Init failed:", err);');
|
|
884
|
-
parts.push(' window.dispatchEvent(new CustomEvent("bridge:error", { detail: { error: err } }));');
|
|
885
|
-
parts.push("});");
|
|
886
|
-
parts.push("");
|
|
887
|
-
parts.push("window.FrontMcpBridge = bridge;");
|
|
888
|
-
parts.push("");
|
|
889
|
-
parts.push(generateAutoResize());
|
|
890
|
-
parts.push("function __showLoading() {");
|
|
891
|
-
parts.push(' var root = document.getElementById("root");');
|
|
892
|
-
parts.push(" if (root && !root.hasChildNodes()) {");
|
|
893
|
-
parts.push(
|
|
894
|
-
` 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>';`
|
|
895
|
-
);
|
|
896
|
-
parts.push(" }");
|
|
897
|
-
parts.push("}");
|
|
898
|
-
parts.push('if (document.readyState === "loading") {');
|
|
899
|
-
parts.push(' document.addEventListener("DOMContentLoaded", __showLoading);');
|
|
900
|
-
parts.push("} else {");
|
|
901
|
-
parts.push(" __showLoading();");
|
|
902
|
-
parts.push("}");
|
|
903
|
-
parts.push("})();");
|
|
904
|
-
const code = parts.join("\n");
|
|
905
|
-
if (minify) {
|
|
906
|
-
return minifyJS(code);
|
|
907
|
-
}
|
|
908
|
-
return code;
|
|
909
|
-
}
|
|
910
|
-
function generateAutoResize() {
|
|
911
|
-
return `
|
|
58
|
+
</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`
|
|
912
59
|
function __applySizingCss(sizing) {
|
|
913
60
|
if (typeof document === 'undefined' || !document.documentElement) return;
|
|
914
61
|
function toLen(v) { return typeof v === 'number' ? v + 'px' : v; }
|
|
@@ -1043,10 +190,7 @@ function __initAutoResize() {
|
|
|
1043
190
|
}
|
|
1044
191
|
}
|
|
1045
192
|
__initAutoResize();
|
|
1046
|
-
`.trim()
|
|
1047
|
-
}
|
|
1048
|
-
function generateContextDetection() {
|
|
1049
|
-
return `
|
|
193
|
+
`.trim()}function generateContextDetection(){return`
|
|
1050
194
|
function detectTheme() {
|
|
1051
195
|
if (typeof window !== 'undefined' && window.matchMedia) {
|
|
1052
196
|
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
|
|
@@ -1133,10 +277,7 @@ function readInjectedData() {
|
|
|
1133
277
|
}
|
|
1134
278
|
return data;
|
|
1135
279
|
}
|
|
1136
|
-
`.trim()
|
|
1137
|
-
}
|
|
1138
|
-
function generateBaseCapabilities() {
|
|
1139
|
-
return `
|
|
280
|
+
`.trim()}function generateBaseCapabilities(){return`
|
|
1140
281
|
var DEFAULT_CAPABILITIES = {
|
|
1141
282
|
canCallTools: false,
|
|
1142
283
|
canSendMessages: false,
|
|
@@ -1146,10 +287,7 @@ var DEFAULT_CAPABILITIES = {
|
|
|
1146
287
|
supportsDisplayModes: false,
|
|
1147
288
|
supportsTheme: true
|
|
1148
289
|
};
|
|
1149
|
-
`.trim()
|
|
1150
|
-
}
|
|
1151
|
-
function generateOpenAIAdapter() {
|
|
1152
|
-
return `
|
|
290
|
+
`.trim()}function generateOpenAIAdapter(){return`
|
|
1153
291
|
var OpenAIAdapter = {
|
|
1154
292
|
id: 'openai',
|
|
1155
293
|
name: 'OpenAI ChatGPT',
|
|
@@ -1234,11 +372,7 @@ var OpenAIAdapter = {
|
|
|
1234
372
|
return Promise.resolve();
|
|
1235
373
|
}
|
|
1236
374
|
};
|
|
1237
|
-
`.trim();
|
|
1238
|
-
}
|
|
1239
|
-
function generateExtAppsAdapter(trustedOrigins) {
|
|
1240
|
-
const originsArray = trustedOrigins.length > 0 ? JSON.stringify(trustedOrigins) : "[]";
|
|
1241
|
-
return `
|
|
375
|
+
`.trim()}function generateExtAppsAdapter(trustedOrigins){const originsArray=trustedOrigins.length>0?JSON.stringify(trustedOrigins):"[]";return`
|
|
1242
376
|
var ExtAppsAdapter = {
|
|
1243
377
|
id: 'ext-apps',
|
|
1244
378
|
name: 'ext-apps (SEP-1865)',
|
|
@@ -1581,10 +715,7 @@ var ExtAppsAdapter = {
|
|
|
1581
715
|
return this.sendRequest('ui/unregisterTool', { name: name });
|
|
1582
716
|
}
|
|
1583
717
|
};
|
|
1584
|
-
`.trim()
|
|
1585
|
-
}
|
|
1586
|
-
function generateClaudeAdapter() {
|
|
1587
|
-
return `
|
|
718
|
+
`.trim()}function generateClaudeAdapter(){return`
|
|
1588
719
|
var ClaudeAdapter = {
|
|
1589
720
|
id: 'claude',
|
|
1590
721
|
name: 'Claude (Anthropic)',
|
|
@@ -1645,10 +776,7 @@ var ClaudeAdapter = {
|
|
|
1645
776
|
return Promise.resolve();
|
|
1646
777
|
}
|
|
1647
778
|
};
|
|
1648
|
-
`.trim()
|
|
1649
|
-
}
|
|
1650
|
-
function generateGeminiAdapter() {
|
|
1651
|
-
return `
|
|
779
|
+
`.trim()}function generateGeminiAdapter(){return`
|
|
1652
780
|
var GeminiAdapter = {
|
|
1653
781
|
id: 'gemini',
|
|
1654
782
|
name: 'Google Gemini',
|
|
@@ -1699,10 +827,7 @@ var GeminiAdapter = {
|
|
|
1699
827
|
return Promise.resolve();
|
|
1700
828
|
}
|
|
1701
829
|
};
|
|
1702
|
-
`.trim()
|
|
1703
|
-
}
|
|
1704
|
-
function generateGenericAdapter() {
|
|
1705
|
-
return `
|
|
830
|
+
`.trim()}function generateGenericAdapter(){return`
|
|
1706
831
|
var GenericAdapter = {
|
|
1707
832
|
id: 'generic',
|
|
1708
833
|
name: 'Generic Web',
|
|
@@ -1737,26 +862,7 @@ var GenericAdapter = {
|
|
|
1737
862
|
return Promise.resolve();
|
|
1738
863
|
}
|
|
1739
864
|
};
|
|
1740
|
-
`.trim();
|
|
1741
|
-
}
|
|
1742
|
-
function generatePlatformDetection(adapters) {
|
|
1743
|
-
const adapterVars = adapters.map((a) => {
|
|
1744
|
-
switch (a) {
|
|
1745
|
-
case "openai":
|
|
1746
|
-
return "OpenAIAdapter";
|
|
1747
|
-
case "ext-apps":
|
|
1748
|
-
return "ExtAppsAdapter";
|
|
1749
|
-
case "claude":
|
|
1750
|
-
return "ClaudeAdapter";
|
|
1751
|
-
case "gemini":
|
|
1752
|
-
return "GeminiAdapter";
|
|
1753
|
-
case "generic":
|
|
1754
|
-
return "GenericAdapter";
|
|
1755
|
-
default:
|
|
1756
|
-
return "";
|
|
1757
|
-
}
|
|
1758
|
-
}).filter(Boolean);
|
|
1759
|
-
return `
|
|
865
|
+
`.trim()}function generatePlatformDetection(adapters){const adapterVars=adapters.map(a=>{switch(a){case"openai":return"OpenAIAdapter";case"ext-apps":return"ExtAppsAdapter";case"claude":return"ClaudeAdapter";case"gemini":return"GeminiAdapter";case"generic":return"GenericAdapter";default:return""}}).filter(Boolean);return`
|
|
1760
866
|
var ADAPTERS = [${adapterVars.join(", ")}].sort(function(a, b) { return b.priority - a.priority; });
|
|
1761
867
|
|
|
1762
868
|
function detectPlatform() {
|
|
@@ -1769,10 +875,7 @@ function detectPlatform() {
|
|
|
1769
875
|
log('No platform detected, using generic');
|
|
1770
876
|
return GenericAdapter;
|
|
1771
877
|
}
|
|
1772
|
-
`.trim()
|
|
1773
|
-
}
|
|
1774
|
-
function generateBridgeClass() {
|
|
1775
|
-
return `
|
|
878
|
+
`.trim()}function generateBridgeClass(){return`
|
|
1776
879
|
function FrontMcpBridge() {
|
|
1777
880
|
this._adapter = null;
|
|
1778
881
|
this._initialized = false;
|
|
@@ -2148,530 +1251,26 @@ FrontMcpBridge.prototype._setupDataToolCallHandler = function() {
|
|
|
2148
1251
|
}
|
|
2149
1252
|
}, true); // Use capture phase to handle before React handlers
|
|
2150
1253
|
};
|
|
2151
|
-
`.trim();
|
|
2152
|
-
}
|
|
2153
|
-
var MAX_MINIFY_CODE_LENGTH = 5e5;
|
|
2154
|
-
function minifyJS(code) {
|
|
2155
|
-
if (code.length > MAX_MINIFY_CODE_LENGTH) {
|
|
2156
|
-
return code;
|
|
2157
|
-
}
|
|
2158
|
-
const out = [];
|
|
2159
|
-
let inBlockComment = false;
|
|
2160
|
-
for (const raw of code.split("\n")) {
|
|
2161
|
-
const line = raw.trim();
|
|
2162
|
-
if (inBlockComment) {
|
|
2163
|
-
if (line.includes("*/")) inBlockComment = false;
|
|
2164
|
-
continue;
|
|
2165
|
-
}
|
|
2166
|
-
if (line === "" || line.startsWith("//")) continue;
|
|
2167
|
-
if (line.startsWith("/*")) {
|
|
2168
|
-
if (!line.includes("*/")) inBlockComment = true;
|
|
2169
|
-
continue;
|
|
2170
|
-
}
|
|
2171
|
-
out.push(line);
|
|
2172
|
-
}
|
|
2173
|
-
return out.join("\n");
|
|
2174
|
-
}
|
|
2175
|
-
function generatePlatformBundle(platform, options = {}) {
|
|
2176
|
-
const platformAdapters = {
|
|
2177
|
-
chatgpt: ["openai", "generic"],
|
|
2178
|
-
claude: ["claude", "generic"],
|
|
2179
|
-
gemini: ["gemini", "generic"],
|
|
2180
|
-
universal: ["openai", "ext-apps", "claude", "gemini", "generic"]
|
|
2181
|
-
};
|
|
2182
|
-
return generateBridgeIIFE({
|
|
2183
|
-
...options,
|
|
2184
|
-
adapters: platformAdapters[platform]
|
|
2185
|
-
});
|
|
2186
|
-
}
|
|
2187
|
-
var UNIVERSAL_BRIDGE_SCRIPT = generateBridgeIIFE();
|
|
2188
|
-
var BRIDGE_SCRIPT_TAGS = {
|
|
2189
|
-
universal: `<script>${UNIVERSAL_BRIDGE_SCRIPT}</script>`,
|
|
2190
|
-
chatgpt: `<script>${generatePlatformBundle("chatgpt")}</script>`,
|
|
2191
|
-
claude: `<script>${generatePlatformBundle("claude")}</script>`,
|
|
2192
|
-
gemini: `<script>${generatePlatformBundle("gemini")}</script>`
|
|
2193
|
-
};
|
|
2194
|
-
|
|
2195
|
-
// libs/uipack/src/shell/csp.ts
|
|
2196
|
-
var DEFAULT_CDN_DOMAINS = [
|
|
2197
|
-
"https://cdn.jsdelivr.net",
|
|
2198
|
-
"https://cdnjs.cloudflare.com",
|
|
2199
|
-
"https://fonts.googleapis.com",
|
|
2200
|
-
"https://fonts.gstatic.com",
|
|
2201
|
-
"https://esm.sh"
|
|
2202
|
-
];
|
|
2203
|
-
var DEFAULT_CSP_DIRECTIVES = [
|
|
2204
|
-
"default-src 'none'",
|
|
2205
|
-
`script-src 'self' 'unsafe-inline' ${DEFAULT_CDN_DOMAINS.join(" ")}`,
|
|
2206
|
-
`style-src 'self' 'unsafe-inline' ${DEFAULT_CDN_DOMAINS.join(" ")}`,
|
|
2207
|
-
`img-src 'self' data: ${DEFAULT_CDN_DOMAINS.join(" ")}`,
|
|
2208
|
-
`font-src 'self' data: ${DEFAULT_CDN_DOMAINS.join(" ")}`,
|
|
2209
|
-
`connect-src ${DEFAULT_CDN_DOMAINS.join(" ")}`,
|
|
2210
|
-
"object-src 'self' data:"
|
|
2211
|
-
];
|
|
2212
|
-
function buildCSPDirectives(csp) {
|
|
2213
|
-
if (!csp) {
|
|
2214
|
-
return [...DEFAULT_CSP_DIRECTIVES];
|
|
2215
|
-
}
|
|
2216
|
-
const validResourceDomains = sanitizeCSPDomains(csp.resourceDomains);
|
|
2217
|
-
const validConnectDomains = sanitizeCSPDomains(csp.connectDomains);
|
|
2218
|
-
const allResourceDomains = [.../* @__PURE__ */ new Set([...DEFAULT_CDN_DOMAINS, ...validResourceDomains])];
|
|
2219
|
-
const directives = [
|
|
2220
|
-
"default-src 'none'",
|
|
2221
|
-
`script-src 'self' 'unsafe-inline' ${allResourceDomains.join(" ")}`,
|
|
2222
|
-
`style-src 'self' 'unsafe-inline' ${allResourceDomains.join(" ")}`
|
|
2223
|
-
];
|
|
2224
|
-
const imgSources = ["'self'", "data:", ...allResourceDomains];
|
|
2225
|
-
directives.push(`img-src ${imgSources.join(" ")}`);
|
|
2226
|
-
const fontSources = ["'self'", "data:", ...allResourceDomains];
|
|
2227
|
-
directives.push(`font-src ${fontSources.join(" ")}`);
|
|
2228
|
-
const connectSources = [.../* @__PURE__ */ new Set([...allResourceDomains, ...validConnectDomains])];
|
|
2229
|
-
directives.push(`connect-src ${connectSources.join(" ")}`);
|
|
2230
|
-
directives.push("object-src 'self' data:");
|
|
2231
|
-
return directives;
|
|
2232
|
-
}
|
|
2233
|
-
function buildCSPMetaTag(csp) {
|
|
2234
|
-
const directives = buildCSPDirectives(csp);
|
|
2235
|
-
const content = directives.join("; ");
|
|
2236
|
-
return `<meta http-equiv="Content-Security-Policy" content="${escapeAttribute(content)}">`;
|
|
2237
|
-
}
|
|
2238
|
-
var LOOPBACK_HOSTS = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "[::1]"]);
|
|
2239
|
-
function validateCSPDomain(domain) {
|
|
2240
|
-
if (typeof domain !== "string" || /[\s;,'"]/.test(domain)) return false;
|
|
2241
|
-
if (/[?#]/.test(domain)) return false;
|
|
2242
|
-
if (domain.includes("*")) {
|
|
2243
|
-
const wildcard = /^(?:https|wss):\/\/\*\.(.*)$/.exec(domain);
|
|
2244
|
-
return wildcard !== null && /^([a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$/.test(wildcard[1]);
|
|
2245
|
-
}
|
|
2246
|
-
try {
|
|
2247
|
-
const url = new URL(domain);
|
|
2248
|
-
if (url.protocol === "https:" || url.protocol === "wss:") return true;
|
|
2249
|
-
return (url.protocol === "http:" || url.protocol === "ws:") && LOOPBACK_HOSTS.has(url.hostname);
|
|
2250
|
-
} catch {
|
|
2251
|
-
return false;
|
|
2252
|
-
}
|
|
2253
|
-
}
|
|
2254
|
-
var reportedInvalidDomains = /* @__PURE__ */ new Set();
|
|
2255
|
-
function sanitizeCSPDomains(domains) {
|
|
2256
|
-
if (!domains) return [];
|
|
2257
|
-
const valid = [];
|
|
2258
|
-
for (const domain of domains) {
|
|
2259
|
-
if (validateCSPDomain(domain)) {
|
|
2260
|
-
valid.push(domain);
|
|
2261
|
-
} else if (!reportedInvalidDomains.has(domain)) {
|
|
2262
|
-
reportedInvalidDomains.add(domain);
|
|
2263
|
-
console.warn(
|
|
2264
|
-
`Invalid CSP domain ignored: ${domain} (expected an https:// or wss:// origin, or http:// / ws:// on localhost)`
|
|
2265
|
-
);
|
|
2266
|
-
}
|
|
2267
|
-
}
|
|
2268
|
-
return valid;
|
|
2269
|
-
}
|
|
2270
|
-
function escapeAttribute(str) {
|
|
2271
|
-
return str.replace(/&/g, "&").replace(/"/g, """).replace(/'/g, "'").replace(/</g, "<").replace(/>/g, ">");
|
|
2272
|
-
}
|
|
2273
|
-
|
|
2274
|
-
// libs/uipack/src/shell/custom-shell-types.ts
|
|
2275
|
-
var SHELL_PLACEHOLDER_NAMES = ["CSP", "DATA", "BRIDGE", "CONTENT", "TITLE"];
|
|
2276
|
-
var SHELL_PLACEHOLDERS = {
|
|
2277
|
-
CSP: "{{CSP}}",
|
|
2278
|
-
DATA: "{{DATA}}",
|
|
2279
|
-
BRIDGE: "{{BRIDGE}}",
|
|
2280
|
-
CONTENT: "{{CONTENT}}",
|
|
2281
|
-
TITLE: "{{TITLE}}"
|
|
2282
|
-
};
|
|
2283
|
-
var REQUIRED_PLACEHOLDERS = ["CONTENT"];
|
|
2284
|
-
var OPTIONAL_PLACEHOLDERS = ["CSP", "DATA", "BRIDGE", "TITLE"];
|
|
2285
|
-
|
|
2286
|
-
// libs/uipack/src/shell/custom-shell-applier.ts
|
|
2287
|
-
var PLACEHOLDER_PATTERN = new RegExp(`\\{\\{(${SHELL_PLACEHOLDER_NAMES.join("|")})\\}\\}`, "g");
|
|
2288
|
-
var VALUE_KEYS = {
|
|
2289
|
-
CSP: "csp",
|
|
2290
|
-
DATA: "data",
|
|
2291
|
-
BRIDGE: "bridge",
|
|
2292
|
-
CONTENT: "content",
|
|
2293
|
-
TITLE: "title"
|
|
2294
|
-
};
|
|
2295
|
-
function applyShellTemplate(template, values) {
|
|
2296
|
-
return template.replace(PLACEHOLDER_PATTERN, (_token, name) => values[VALUE_KEYS[name]]);
|
|
2297
|
-
}
|
|
2298
|
-
|
|
2299
|
-
// libs/uipack/src/shell/custom-shell-validator.ts
|
|
2300
|
-
function validateShellTemplate(template) {
|
|
2301
|
-
const found = {};
|
|
2302
|
-
for (const name of SHELL_PLACEHOLDER_NAMES) {
|
|
2303
|
-
found[name] = template.includes(SHELL_PLACEHOLDERS[name]);
|
|
2304
|
-
}
|
|
2305
|
-
const missingRequired = REQUIRED_PLACEHOLDERS.filter((name) => !found[name]);
|
|
2306
|
-
const missingOptional = OPTIONAL_PLACEHOLDERS.filter((name) => !found[name]);
|
|
2307
|
-
return {
|
|
2308
|
-
valid: missingRequired.length === 0,
|
|
2309
|
-
found,
|
|
2310
|
-
missingRequired,
|
|
2311
|
-
missingOptional
|
|
2312
|
-
};
|
|
2313
|
-
}
|
|
2314
|
-
|
|
2315
|
-
// libs/uipack/src/shell/data-injector.ts
|
|
2316
|
-
function hasSizing(sizing) {
|
|
2317
|
-
if (!sizing) return false;
|
|
2318
|
-
return sizing.preferredHeight !== void 0 || sizing.minHeight !== void 0 || sizing.maxHeight !== void 0 || sizing.aspectRatio !== void 0 || sizing.autoResize !== void 0;
|
|
2319
|
-
}
|
|
2320
|
-
function buildDataInjectionScript(options) {
|
|
2321
|
-
const { toolName, input, output, structuredContent, sizing, displayMode } = options;
|
|
2322
|
-
const lines = [
|
|
2323
|
-
`window.__mcpAppsEnabled = true;`,
|
|
2324
|
-
`window.__mcpToolName = ${safeJsonForScript(toolName)};`,
|
|
2325
|
-
`window.__mcpToolInput = ${safeJsonForScript(input ?? null)};`,
|
|
2326
|
-
`window.__mcpToolOutput = ${safeJsonForScript(output ?? null)};`,
|
|
2327
|
-
`window.__mcpStructuredContent = ${safeJsonForScript(structuredContent ?? null)};`
|
|
2328
|
-
];
|
|
2329
|
-
if (hasSizing(sizing)) {
|
|
2330
|
-
lines.push(`window.__mcpWidgetSizing = ${safeJsonForScript(sizing)};`);
|
|
2331
|
-
}
|
|
2332
|
-
if (displayMode && displayMode !== "inline") {
|
|
2333
|
-
lines.push(`window.__mcpDisplayMode = ${safeJsonForScript(displayMode)};`);
|
|
2334
|
-
}
|
|
2335
|
-
return `<script>
|
|
1254
|
+
`.trim()}var MAX_MINIFY_CODE_LENGTH=5e5;function minifyJS(code){if(code.length>MAX_MINIFY_CODE_LENGTH){return code}const out=[];let inBlockComment=false;for(const raw of code.split("\n")){const line=raw.trim();if(inBlockComment){if(line.includes("*/"))inBlockComment=false;continue}if(line===""||line.startsWith("//"))continue;if(line.startsWith("/*")){if(!line.includes("*/"))inBlockComment=true;continue}out.push(line)}return out.join("\n")}function generatePlatformBundle(platform,options={}){const platformAdapters={chatgpt:["openai","generic"],claude:["claude","generic"],gemini:["gemini","generic"],universal:["openai","ext-apps","claude","gemini","generic"]};return generateBridgeIIFE({...options,adapters:platformAdapters[platform]})}var UNIVERSAL_BRIDGE_SCRIPT=generateBridgeIIFE();var BRIDGE_SCRIPT_TAGS={universal:`<script>${UNIVERSAL_BRIDGE_SCRIPT}</script>`,chatgpt:`<script>${generatePlatformBundle("chatgpt")}</script>`,claude:`<script>${generatePlatformBundle("claude")}</script>`,gemini:`<script>${generatePlatformBundle("gemini")}</script>`};var DEFAULT_CDN_DOMAINS=["https://cdn.jsdelivr.net","https://cdnjs.cloudflare.com","https://fonts.googleapis.com","https://fonts.gstatic.com","https://esm.sh"];var DEFAULT_CSP_DIRECTIVES=["default-src 'none'",`script-src 'self' 'unsafe-inline' ${DEFAULT_CDN_DOMAINS.join(" ")}`,`style-src 'self' 'unsafe-inline' ${DEFAULT_CDN_DOMAINS.join(" ")}`,`img-src 'self' data: ${DEFAULT_CDN_DOMAINS.join(" ")}`,`font-src 'self' data: ${DEFAULT_CDN_DOMAINS.join(" ")}`,`connect-src ${DEFAULT_CDN_DOMAINS.join(" ")}`,"object-src 'self' data:"];function buildCSPDirectives(csp){if(!csp){return[...DEFAULT_CSP_DIRECTIVES]}const validResourceDomains=sanitizeCSPDomains(csp.resourceDomains);const validConnectDomains=sanitizeCSPDomains(csp.connectDomains);const allResourceDomains=[...new Set([...DEFAULT_CDN_DOMAINS,...validResourceDomains])];const directives=["default-src 'none'",`script-src 'self' 'unsafe-inline' ${allResourceDomains.join(" ")}`,`style-src 'self' 'unsafe-inline' ${allResourceDomains.join(" ")}`];const imgSources=["'self'","data:",...allResourceDomains];directives.push(`img-src ${imgSources.join(" ")}`);const fontSources=["'self'","data:",...allResourceDomains];directives.push(`font-src ${fontSources.join(" ")}`);const connectSources=[...new Set([...allResourceDomains,...validConnectDomains])];directives.push(`connect-src ${connectSources.join(" ")}`);directives.push("object-src 'self' data:");return directives}function buildCSPMetaTag(csp){const directives=buildCSPDirectives(csp);const content=directives.join("; ");return`<meta http-equiv="Content-Security-Policy" content="${escapeAttribute(content)}">`}var LOOPBACK_HOSTS=new Set(["localhost","127.0.0.1","[::1]"]);function validateCSPDomain(domain){if(typeof domain!=="string"||/[\s;,'"]/.test(domain))return false;if(/[?#]/.test(domain))return false;if(domain.includes("*")){const wildcard=/^(?:https|wss):\/\/\*\.(.*)$/.exec(domain);return wildcard!==null&&/^([a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$/.test(wildcard[1])}try{const url=new URL(domain);if(url.protocol==="https:"||url.protocol==="wss:")return true;return(url.protocol==="http:"||url.protocol==="ws:")&&LOOPBACK_HOSTS.has(url.hostname)}catch{return false}}var reportedInvalidDomains=new Set;function sanitizeCSPDomains(domains){if(!domains)return[];const valid=[];for(const domain of domains){if(validateCSPDomain(domain)){valid.push(domain)}else if(!reportedInvalidDomains.has(domain)){reportedInvalidDomains.add(domain);console.warn(`Invalid CSP domain ignored: ${domain} (expected an https:// or wss:// origin, or http:// / ws:// on localhost)`)}}return valid}function escapeAttribute(str){return str.replace(/&/g,"&").replace(/"/g,""").replace(/'/g,"'").replace(/</g,"<").replace(/>/g,">")}var SHELL_PLACEHOLDER_NAMES=["CSP","DATA","BRIDGE","CONTENT","TITLE"];var SHELL_PLACEHOLDERS={CSP:"{{CSP}}",DATA:"{{DATA}}",BRIDGE:"{{BRIDGE}}",CONTENT:"{{CONTENT}}",TITLE:"{{TITLE}}"};var REQUIRED_PLACEHOLDERS=["CONTENT"];var OPTIONAL_PLACEHOLDERS=["CSP","DATA","BRIDGE","TITLE"];var PLACEHOLDER_PATTERN=new RegExp(`\\{\\{(${SHELL_PLACEHOLDER_NAMES.join("|")})\\}\\}`,"g");var VALUE_KEYS={CSP:"csp",DATA:"data",BRIDGE:"bridge",CONTENT:"content",TITLE:"title"};function applyShellTemplate(template,values){return template.replace(PLACEHOLDER_PATTERN,(_token,name)=>values[VALUE_KEYS[name]])}function validateShellTemplate(template){const found={};for(const name of SHELL_PLACEHOLDER_NAMES){found[name]=template.includes(SHELL_PLACEHOLDERS[name])}const missingRequired=REQUIRED_PLACEHOLDERS.filter(name=>!found[name]);const missingOptional=OPTIONAL_PLACEHOLDERS.filter(name=>!found[name]);return{valid:missingRequired.length===0,found,missingRequired,missingOptional}}function hasSizing(sizing){if(!sizing)return false;return sizing.preferredHeight!==void 0||sizing.minHeight!==void 0||sizing.maxHeight!==void 0||sizing.aspectRatio!==void 0||sizing.autoResize!==void 0}function buildDataInjectionScript(options){const{toolName,input,output,structuredContent,sizing,displayMode}=options;const lines=[`window.__mcpAppsEnabled = true;`,`window.__mcpToolName = ${safeJsonForScript(toolName)};`,`window.__mcpToolInput = ${safeJsonForScript(input??null)};`,`window.__mcpToolOutput = ${safeJsonForScript(output??null)};`,`window.__mcpStructuredContent = ${safeJsonForScript(structuredContent??null)};`];if(hasSizing(sizing)){lines.push(`window.__mcpWidgetSizing = ${safeJsonForScript(sizing)};`)}if(displayMode&&displayMode!=="inline"){lines.push(`window.__mcpDisplayMode = ${safeJsonForScript(displayMode)};`)}return`<script>
|
|
2336
1255
|
${lines.join("\n")}
|
|
2337
|
-
</script
|
|
2338
|
-
}
|
|
2339
|
-
function buildCustomDataInjectionScript(descriptor) {
|
|
2340
|
-
if (descriptor.script !== void 0) {
|
|
2341
|
-
return descriptor.script;
|
|
2342
|
-
}
|
|
2343
|
-
if (descriptor.globalKey !== void 0) {
|
|
2344
|
-
return `<script>window[${safeJsonForScript(descriptor.globalKey)}] = ${safeJsonForScript(
|
|
2345
|
-
descriptor.value ?? null
|
|
2346
|
-
)};</script>`;
|
|
2347
|
-
}
|
|
2348
|
-
return "";
|
|
2349
|
-
}
|
|
2350
|
-
|
|
2351
|
-
// libs/uipack/src/shell/sizing-css.ts
|
|
2352
|
-
function sanitizeCssValue(value) {
|
|
2353
|
-
return value.replace(/[<>{};]/g, "").trim();
|
|
2354
|
-
}
|
|
2355
|
-
function toCssLength(value) {
|
|
2356
|
-
return typeof value === "number" ? `${value}px` : sanitizeCssValue(value);
|
|
2357
|
-
}
|
|
2358
|
-
function buildSizingStyleTag(sizing) {
|
|
2359
|
-
if (!hasSizing(sizing)) return "";
|
|
2360
|
-
const hasCssSizing = sizing.preferredHeight !== void 0 || sizing.minHeight !== void 0 || sizing.maxHeight !== void 0 || sizing.aspectRatio !== void 0;
|
|
2361
|
-
if (!hasCssSizing) return "";
|
|
2362
|
-
const rootRules = [];
|
|
2363
|
-
const docRules = ["margin: 0;"];
|
|
2364
|
-
if (sizing.preferredHeight !== void 0) {
|
|
2365
|
-
const h = toCssLength(sizing.preferredHeight);
|
|
2366
|
-
docRules.push(`height: ${h};`);
|
|
2367
|
-
rootRules.push(`min-height: ${h};`);
|
|
2368
|
-
}
|
|
2369
|
-
if (sizing.minHeight !== void 0) {
|
|
2370
|
-
const mh = toCssLength(sizing.minHeight);
|
|
2371
|
-
docRules.push(`min-height: ${mh};`);
|
|
2372
|
-
rootRules.push(`min-height: ${mh};`);
|
|
2373
|
-
}
|
|
2374
|
-
if (sizing.maxHeight !== void 0) {
|
|
2375
|
-
const mx = toCssLength(sizing.maxHeight);
|
|
2376
|
-
docRules.push(`max-height: ${mx};`);
|
|
2377
|
-
rootRules.push(`max-height: ${mx};`);
|
|
2378
|
-
}
|
|
2379
|
-
if (sizing.aspectRatio !== void 0) {
|
|
2380
|
-
const ar = typeof sizing.aspectRatio === "number" ? String(sizing.aspectRatio) : sanitizeCssValue(sizing.aspectRatio);
|
|
2381
|
-
if (ar) rootRules.push(`aspect-ratio: ${ar};`);
|
|
2382
|
-
}
|
|
2383
|
-
const parts = [`html, body { ${docRules.join(" ")} }`];
|
|
2384
|
-
if (rootRules.length > 0) {
|
|
2385
|
-
parts.push(`#root { ${rootRules.join(" ")} }`);
|
|
2386
|
-
}
|
|
2387
|
-
return `<style>${parts.join("\n")}</style>`;
|
|
2388
|
-
}
|
|
2389
|
-
|
|
2390
|
-
// libs/uipack/src/shell/builder.ts
|
|
2391
|
-
function resolveDataInjectionScript(args) {
|
|
2392
|
-
if (args.dataInjection) {
|
|
2393
|
-
return buildCustomDataInjectionScript(args.dataInjection);
|
|
2394
|
-
}
|
|
2395
|
-
return buildDataInjectionScript({
|
|
2396
|
-
toolName: args.toolName,
|
|
2397
|
-
input: args.input,
|
|
2398
|
-
output: args.output,
|
|
2399
|
-
structuredContent: args.structuredContent,
|
|
2400
|
-
sizing: args.sizing,
|
|
2401
|
-
displayMode: args.displayMode
|
|
2402
|
-
});
|
|
2403
|
-
}
|
|
2404
|
-
function buildShell(content, config) {
|
|
2405
|
-
const {
|
|
2406
|
-
toolName,
|
|
2407
|
-
csp,
|
|
2408
|
-
withShell = true,
|
|
2409
|
-
input,
|
|
2410
|
-
output,
|
|
2411
|
-
structuredContent,
|
|
2412
|
-
includeBridge = true,
|
|
2413
|
-
title,
|
|
2414
|
-
sizing,
|
|
2415
|
-
displayMode
|
|
2416
|
-
} = config;
|
|
2417
|
-
const { customShell, dataInjection } = config;
|
|
2418
|
-
const dataScript = resolveDataInjectionScript({
|
|
2419
|
-
dataInjection,
|
|
2420
|
-
toolName,
|
|
2421
|
-
input,
|
|
2422
|
-
output,
|
|
2423
|
-
structuredContent,
|
|
2424
|
-
sizing,
|
|
2425
|
-
displayMode
|
|
2426
|
-
});
|
|
2427
|
-
if (!withShell) {
|
|
2428
|
-
const html2 = `${dataScript}
|
|
2429
|
-
${content}`;
|
|
2430
|
-
return {
|
|
2431
|
-
html: html2,
|
|
2432
|
-
hash: simpleHash(html2),
|
|
2433
|
-
size: Buffer.byteLength(html2, "utf-8")
|
|
2434
|
-
};
|
|
2435
|
-
}
|
|
2436
|
-
if (customShell) {
|
|
2437
|
-
return buildCustomShell(content, customShell, {
|
|
2438
|
-
csp,
|
|
2439
|
-
toolName,
|
|
2440
|
-
input,
|
|
2441
|
-
output,
|
|
2442
|
-
structuredContent,
|
|
2443
|
-
includeBridge,
|
|
2444
|
-
title,
|
|
2445
|
-
sizing,
|
|
2446
|
-
displayMode,
|
|
2447
|
-
dataInjection
|
|
2448
|
-
});
|
|
2449
|
-
}
|
|
2450
|
-
const headParts = [
|
|
2451
|
-
'<meta charset="UTF-8">',
|
|
2452
|
-
'<meta name="viewport" content="width=device-width, initial-scale=1.0">'
|
|
2453
|
-
];
|
|
2454
|
-
if (title) {
|
|
2455
|
-
headParts.push(`<title>${escapeHtmlForTag(title)}</title>`);
|
|
2456
|
-
}
|
|
2457
|
-
headParts.push(buildCSPMetaTag(csp));
|
|
2458
|
-
headParts.push(dataScript);
|
|
2459
|
-
const sizingStyle = buildSizingStyleTag(sizing);
|
|
2460
|
-
if (sizingStyle) {
|
|
2461
|
-
headParts.push(sizingStyle);
|
|
2462
|
-
}
|
|
2463
|
-
if (includeBridge) {
|
|
2464
|
-
const bridgeScript = generateBridgeIIFE({ minify: true });
|
|
2465
|
-
headParts.push(`<script>${bridgeScript}</script>`);
|
|
2466
|
-
}
|
|
2467
|
-
const html = `<!DOCTYPE html>
|
|
1256
|
+
</script>`}function buildCustomDataInjectionScript(descriptor){if(descriptor.script!==void 0){return descriptor.script}if(descriptor.globalKey!==void 0){return`<script>window[${safeJsonForScript(descriptor.globalKey)}] = ${safeJsonForScript(descriptor.value??null)};</script>`}return""}function sanitizeCssValue(value){return value.replace(/[<>{};]/g,"").trim()}function toCssLength(value){return typeof value==="number"?`${value}px`:sanitizeCssValue(value)}function buildSizingStyleTag(sizing){if(!hasSizing(sizing))return"";const hasCssSizing=sizing.preferredHeight!==void 0||sizing.minHeight!==void 0||sizing.maxHeight!==void 0||sizing.aspectRatio!==void 0;if(!hasCssSizing)return"";const rootRules=[];const docRules=["margin: 0;"];if(sizing.preferredHeight!==void 0){const h=toCssLength(sizing.preferredHeight);docRules.push(`height: ${h};`);rootRules.push(`min-height: ${h};`)}if(sizing.minHeight!==void 0){const mh=toCssLength(sizing.minHeight);docRules.push(`min-height: ${mh};`);rootRules.push(`min-height: ${mh};`)}if(sizing.maxHeight!==void 0){const mx=toCssLength(sizing.maxHeight);docRules.push(`max-height: ${mx};`);rootRules.push(`max-height: ${mx};`)}if(sizing.aspectRatio!==void 0){const ar=typeof sizing.aspectRatio==="number"?String(sizing.aspectRatio):sanitizeCssValue(sizing.aspectRatio);if(ar)rootRules.push(`aspect-ratio: ${ar};`)}const parts=[`html, body { ${docRules.join(" ")} }`];if(rootRules.length>0){parts.push(`#root { ${rootRules.join(" ")} }`)}return`<style>${parts.join("\n")}</style>`}function resolveDataInjectionScript(args){if(args.dataInjection){return buildCustomDataInjectionScript(args.dataInjection)}return buildDataInjectionScript({toolName:args.toolName,input:args.input,output:args.output,structuredContent:args.structuredContent,sizing:args.sizing,displayMode:args.displayMode})}function buildShell(content,config){const{toolName,csp,withShell=true,input,output,structuredContent,includeBridge=true,title,sizing,displayMode}=config;const{customShell,dataInjection}=config;const dataScript=resolveDataInjectionScript({dataInjection,toolName,input,output,structuredContent,sizing,displayMode});if(!withShell){const html2=`${dataScript}
|
|
1257
|
+
${content}`;return{html:html2,hash:simpleHash(html2),size:Buffer.byteLength(html2,"utf-8")}}if(customShell){return buildCustomShell(content,customShell,{csp,toolName,input,output,structuredContent,includeBridge,title,sizing,displayMode,dataInjection})}const headParts=['<meta charset="UTF-8">','<meta name="viewport" content="width=device-width, initial-scale=1.0">'];if(title){headParts.push(`<title>${escapeHtmlForTag(title)}</title>`)}headParts.push(buildCSPMetaTag(csp));headParts.push(dataScript);const sizingStyle=buildSizingStyleTag(sizing);if(sizingStyle){headParts.push(sizingStyle)}if(includeBridge){const bridgeScript=generateBridgeIIFE({minify:true});headParts.push(`<script>${bridgeScript}</script>`)}const html=`<!DOCTYPE html>
|
|
2468
1258
|
<html lang="en">
|
|
2469
1259
|
<head>
|
|
2470
|
-
${headParts.map(
|
|
1260
|
+
${headParts.map(p=>` ${p}`).join("\n")}
|
|
2471
1261
|
</head>
|
|
2472
1262
|
<body>
|
|
2473
1263
|
${content}
|
|
2474
1264
|
</body>
|
|
2475
|
-
</html>`;
|
|
2476
|
-
|
|
2477
|
-
html,
|
|
2478
|
-
hash: simpleHash(html),
|
|
2479
|
-
size: Buffer.byteLength(html, "utf-8")
|
|
2480
|
-
};
|
|
2481
|
-
}
|
|
2482
|
-
function buildCustomShell(content, customShell, ctx) {
|
|
2483
|
-
let template;
|
|
2484
|
-
if (typeof customShell === "string") {
|
|
2485
|
-
const validation = validateShellTemplate(customShell);
|
|
2486
|
-
if (!validation.valid) {
|
|
2487
|
-
throw new Error(
|
|
2488
|
-
`Custom shell template is missing required placeholder(s): ${validation.missingRequired.map((n) => `{{${n}}}`).join(", ")}`
|
|
2489
|
-
);
|
|
2490
|
-
}
|
|
2491
|
-
template = customShell;
|
|
2492
|
-
} else {
|
|
2493
|
-
template = customShell.template;
|
|
2494
|
-
}
|
|
2495
|
-
const cspTag = buildCSPMetaTag(ctx.csp);
|
|
2496
|
-
const dataScript = resolveDataInjectionScript({
|
|
2497
|
-
dataInjection: ctx.dataInjection,
|
|
2498
|
-
toolName: ctx.toolName,
|
|
2499
|
-
input: ctx.input,
|
|
2500
|
-
output: ctx.output,
|
|
2501
|
-
structuredContent: ctx.structuredContent,
|
|
2502
|
-
sizing: ctx.sizing,
|
|
2503
|
-
displayMode: ctx.displayMode
|
|
2504
|
-
});
|
|
2505
|
-
const sizingStyle = buildSizingStyleTag(ctx.sizing);
|
|
2506
|
-
const dataWithSizing = sizingStyle ? `${dataScript}
|
|
2507
|
-
${sizingStyle}` : dataScript;
|
|
2508
|
-
let bridgeHtml = "";
|
|
2509
|
-
if (ctx.includeBridge) {
|
|
2510
|
-
const bridgeScript = generateBridgeIIFE({ minify: true });
|
|
2511
|
-
bridgeHtml = `<script>${bridgeScript}</script>`;
|
|
2512
|
-
}
|
|
2513
|
-
const html = applyShellTemplate(template, {
|
|
2514
|
-
csp: cspTag,
|
|
2515
|
-
data: dataWithSizing,
|
|
2516
|
-
bridge: bridgeHtml,
|
|
2517
|
-
content,
|
|
2518
|
-
title: ctx.title ? escapeHtmlForTag(ctx.title) : ""
|
|
2519
|
-
});
|
|
2520
|
-
return {
|
|
2521
|
-
html,
|
|
2522
|
-
hash: simpleHash(html),
|
|
2523
|
-
size: Buffer.byteLength(html, "utf-8")
|
|
2524
|
-
};
|
|
2525
|
-
}
|
|
2526
|
-
function simpleHash(str) {
|
|
2527
|
-
let hash = 0;
|
|
2528
|
-
for (let i = 0; i < str.length; i++) {
|
|
2529
|
-
const char = str.charCodeAt(i);
|
|
2530
|
-
hash = (hash << 5) - hash + char | 0;
|
|
2531
|
-
}
|
|
2532
|
-
return Math.abs(hash).toString(16).padStart(8, "0");
|
|
2533
|
-
}
|
|
2534
|
-
function escapeHtmlForTag(str) {
|
|
2535
|
-
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
2536
|
-
}
|
|
2537
|
-
|
|
2538
|
-
// libs/uipack/src/component/renderer.ts
|
|
2539
|
-
var DEFAULT_WIDGET_MOUNT = {
|
|
2540
|
-
moduleSpecifier: "@frontmcp/ui/react",
|
|
2541
|
-
wrapperImportName: "McpBridgeProvider"
|
|
2542
|
-
};
|
|
2543
|
-
function renderComponent(config, shellConfig) {
|
|
2544
|
-
const resolver = shellConfig.resolver ?? createEsmShResolver();
|
|
2545
|
-
const resolved = resolveUISource(config.source, {
|
|
2546
|
-
resolver,
|
|
2547
|
-
input: shellConfig.input,
|
|
2548
|
-
output: shellConfig.output,
|
|
2549
|
-
inlineReact: config.inlineReact === true,
|
|
2550
|
-
transformOnly: config.transformOnly === true
|
|
2551
|
-
});
|
|
2552
|
-
const mergedShellConfig = {
|
|
2553
|
-
...shellConfig,
|
|
2554
|
-
withShell: config.withShell ?? shellConfig.withShell,
|
|
2555
|
-
includeBridge: config.includeBridge ?? shellConfig.includeBridge,
|
|
2556
|
-
customShell: config.customShell ?? shellConfig.customShell
|
|
2557
|
-
};
|
|
2558
|
-
if (resolved.mode === "inline") {
|
|
2559
|
-
return buildShell(resolved.html ?? "", mergedShellConfig);
|
|
2560
|
-
}
|
|
2561
|
-
const content = buildModuleContent(resolved, resolver, config.props, shellConfig.mount);
|
|
2562
|
-
return buildShell(content, mergedShellConfig);
|
|
2563
|
-
}
|
|
2564
|
-
function buildModuleContent(resolved, resolver, propsMapping, mount = DEFAULT_WIDGET_MOUNT) {
|
|
2565
|
-
const parts = [];
|
|
2566
|
-
if (resolved.code && resolved.bundled) {
|
|
2567
|
-
const importEntries = {};
|
|
2568
|
-
for (const spec of resolved.imports || []) {
|
|
2569
|
-
const depResolved = resolver.resolve(spec);
|
|
2570
|
-
if (depResolved) {
|
|
2571
|
-
importEntries[spec] = depResolved;
|
|
2572
|
-
}
|
|
2573
|
-
}
|
|
2574
|
-
if (Object.keys(importEntries).length > 0) {
|
|
2575
|
-
const importMap = createImportMapFromResolved(importEntries);
|
|
2576
|
-
parts.push(generateImportMapScriptTag(importMap));
|
|
2577
|
-
}
|
|
2578
|
-
parts.push('<div id="root"></div>');
|
|
2579
|
-
parts.push(`<script type="module">
|
|
1265
|
+
</html>`;return{html,hash:simpleHash(html),size:Buffer.byteLength(html,"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}
|
|
1266
|
+
${sizingStyle}`:dataScript;let bridgeHtml="";if(ctx.includeBridge){const bridgeScript=generateBridgeIIFE({minify:true});bridgeHtml=`<script>${bridgeScript}</script>`}const html=applyShellTemplate(template,{csp:cspTag,data:dataWithSizing,bridge:bridgeHtml,content,title:ctx.title?escapeHtmlForTag(ctx.title):""});return{html,hash:simpleHash(html),size:Buffer.byteLength(html,"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,"&").replace(/</g,"<").replace(/>/g,">")}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">
|
|
2580
1267
|
${resolved.code}
|
|
2581
|
-
</script>`);
|
|
2582
|
-
} else if (resolved.code) {
|
|
2583
|
-
const importEntries = {};
|
|
2584
|
-
const allSpecifiers = /* @__PURE__ */ new Set([
|
|
2585
|
-
...resolved.imports || [],
|
|
2586
|
-
"react-dom/client",
|
|
2587
|
-
// needed by mount script
|
|
2588
|
-
mount.moduleSpecifier,
|
|
2589
|
-
// mounter/provider package (e.g. @frontmcp/ui/react)
|
|
2590
|
-
// React subpath entries needed by esm.sh externalized modules:
|
|
2591
|
-
"react/jsx-runtime",
|
|
2592
|
-
"react/jsx-dev-runtime"
|
|
2593
|
-
]);
|
|
2594
|
-
if (mount === DEFAULT_WIDGET_MOUNT) {
|
|
2595
|
-
allSpecifiers.add("react-dom/server");
|
|
2596
|
-
allSpecifiers.add("react-dom/static");
|
|
2597
|
-
}
|
|
2598
|
-
const coreDeps = /* @__PURE__ */ new Set([
|
|
2599
|
-
"react",
|
|
2600
|
-
"react-dom",
|
|
2601
|
-
"react-dom/client",
|
|
2602
|
-
"react/jsx-runtime",
|
|
2603
|
-
"react/jsx-dev-runtime",
|
|
2604
|
-
"react-dom/server",
|
|
2605
|
-
"react-dom/static"
|
|
2606
|
-
]);
|
|
2607
|
-
for (const spec of allSpecifiers) {
|
|
2608
|
-
const depResolved = resolver.resolve(spec);
|
|
2609
|
-
if (depResolved) {
|
|
2610
|
-
if (!coreDeps.has(spec) && depResolved.value?.includes("esm.sh")) {
|
|
2611
|
-
importEntries[spec] = {
|
|
2612
|
-
...depResolved,
|
|
2613
|
-
value: addExternalParam(depResolved.value, ["react", "react-dom"])
|
|
2614
|
-
};
|
|
2615
|
-
} else {
|
|
2616
|
-
importEntries[spec] = depResolved;
|
|
2617
|
-
}
|
|
2618
|
-
}
|
|
2619
|
-
}
|
|
2620
|
-
if (importEntries["react-dom"] && !importEntries["react-dom/client"]) {
|
|
2621
|
-
const rdc = resolver.resolve("react-dom/client");
|
|
2622
|
-
if (rdc) importEntries["react-dom/client"] = rdc;
|
|
2623
|
-
}
|
|
2624
|
-
if (Object.keys(importEntries).length > 0) {
|
|
2625
|
-
const importMap = createImportMapFromResolved(importEntries);
|
|
2626
|
-
parts.push(generateImportMapScriptTag(importMap));
|
|
2627
|
-
}
|
|
2628
|
-
const mountNodeId = mount.mountNodeId ?? "root";
|
|
2629
|
-
parts.push(`<div id="${mountNodeId}">${mount.mountNodeInnerHtml ?? ""}</div>`);
|
|
2630
|
-
const mountCode = generateInlineMountCode(resolved.exportName, mount);
|
|
2631
|
-
parts.push(`<script type="module">
|
|
1268
|
+
</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">
|
|
2632
1269
|
${resolved.code}
|
|
2633
1270
|
${mountCode}
|
|
2634
|
-
</script>`);
|
|
2635
|
-
} else {
|
|
2636
|
-
const importEntries = {};
|
|
2637
|
-
for (const dep of resolved.peerDependencies) {
|
|
2638
|
-
const depResolved = resolver.resolve(dep);
|
|
2639
|
-
if (depResolved) {
|
|
2640
|
-
importEntries[dep] = depResolved;
|
|
2641
|
-
}
|
|
2642
|
-
}
|
|
2643
|
-
if (importEntries["react-dom"]) {
|
|
2644
|
-
const reactDomClient = resolver.resolve("react-dom/client");
|
|
2645
|
-
if (reactDomClient) {
|
|
2646
|
-
importEntries["react-dom/client"] = reactDomClient;
|
|
2647
|
-
}
|
|
2648
|
-
}
|
|
2649
|
-
if (Object.keys(importEntries).length > 0) {
|
|
2650
|
-
const importMap = createImportMapFromResolved(importEntries);
|
|
2651
|
-
parts.push(generateImportMapScriptTag(importMap));
|
|
2652
|
-
}
|
|
2653
|
-
parts.push('<div id="root"></div>');
|
|
2654
|
-
const mountScript = generateMountScript(resolved, propsMapping);
|
|
2655
|
-
if (mountScript) {
|
|
2656
|
-
parts.push(`<script type="module">
|
|
1271
|
+
</script>`)}else{const importEntries={};for(const dep of resolved.peerDependencies){const depResolved=resolver.resolve(dep);if(depResolved){importEntries[dep]=depResolved}}if(importEntries["react-dom"]){const reactDomClient=resolver.resolve("react-dom/client");if(reactDomClient){importEntries["react-dom/client"]=reactDomClient}}if(Object.keys(importEntries).length>0){const importMap=createImportMapFromResolved(importEntries);parts.push(generateImportMapScriptTag(importMap))}parts.push('<div id="root"></div>');const mountScript=generateMountScript(resolved,propsMapping);if(mountScript){parts.push(`<script type="module">
|
|
2657
1272
|
${mountScript}
|
|
2658
|
-
</script>`);
|
|
2659
|
-
}
|
|
2660
|
-
}
|
|
2661
|
-
return parts.join("\n");
|
|
2662
|
-
}
|
|
2663
|
-
function addExternalParam(url, externals) {
|
|
2664
|
-
const sep = url.includes("?") ? "&" : "?";
|
|
2665
|
-
return `${url}${sep}external=${externals.join(",")}`;
|
|
2666
|
-
}
|
|
2667
|
-
function generateInlineMountCode(componentName, mount) {
|
|
2668
|
-
if (mount.generate) {
|
|
2669
|
-
return mount.generate(componentName);
|
|
2670
|
-
}
|
|
2671
|
-
const wrapperName = mount.wrapperImportName ?? "McpBridgeProvider";
|
|
2672
|
-
const wrapperAlias = `__${wrapperName}`;
|
|
2673
|
-
const mountNodeId = mount.mountNodeId ?? "root";
|
|
2674
|
-
return `
|
|
1273
|
+
</script>`)}}return parts.join("\n")}function addExternalParam(url,externals){const sep=url.includes("?")?"&":"?";return`${url}${sep}external=${externals.join(",")}`}function generateInlineMountCode(componentName,mount){if(mount.generate){return mount.generate(componentName)}const wrapperName=mount.wrapperImportName??"McpBridgeProvider";const wrapperAlias=`__${wrapperName}`;const mountNodeId=mount.mountNodeId??"root";return`
|
|
2675
1274
|
// --- Mount ---
|
|
2676
1275
|
import { createRoot as __createRoot } from 'react-dom/client';
|
|
2677
1276
|
import { ${wrapperName} as ${wrapperAlias} } from '${mount.moduleSpecifier}';
|
|
@@ -2682,18 +1281,4 @@ if (__root) {
|
|
|
2682
1281
|
React.createElement(${componentName})
|
|
2683
1282
|
)
|
|
2684
1283
|
);
|
|
2685
|
-
}
|
|
2686
|
-
}
|
|
2687
|
-
export {
|
|
2688
|
-
FRONTMCP_META_KEY,
|
|
2689
|
-
bundleFileSource,
|
|
2690
|
-
extractDefaultExportName,
|
|
2691
|
-
generateMountScript,
|
|
2692
|
-
isFileSource,
|
|
2693
|
-
isFunctionSource,
|
|
2694
|
-
isImportSource,
|
|
2695
|
-
isNpmSource,
|
|
2696
|
-
renderComponent,
|
|
2697
|
-
resolveUISource,
|
|
2698
|
-
transpileReactSource
|
|
2699
|
-
};
|
|
1284
|
+
}`}export{FRONTMCP_META_KEY,bundleFileSource,extractDefaultExportName,generateMountScript,isFileSource,isFunctionSource,isImportSource,isNpmSource,renderComponent,resolveUISource,transpileReactSource};
|