@frontmcp/uipack 0.8.0 → 0.9.0
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/README.md +46 -106
- package/bundler/index.js +2 -2
- package/esm/bundler/index.mjs +1 -1
- package/esm/index.mjs +190 -175
- package/esm/package.json +4 -3
- package/esm/runtime/index.mjs +433 -176
- package/esm/tool-template/index.mjs +1 -0
- package/index.js +190 -175
- package/package.json +4 -3
- package/runtime/adapters/html.adapter.d.ts.map +1 -1
- package/runtime/index.d.ts +1 -1
- package/runtime/index.d.ts.map +1 -1
- package/runtime/index.js +434 -176
- package/runtime/mcp-bridge.d.ts.map +1 -1
- package/runtime/sanitizer.d.ts +8 -0
- package/runtime/sanitizer.d.ts.map +1 -1
- package/tool-template/index.js +1 -0
package/README.md
CHANGED
|
@@ -1,98 +1,47 @@
|
|
|
1
1
|
# @frontmcp/uipack
|
|
2
2
|
|
|
3
|
-
React-free build utilities, theming, runtime helpers, and platform adapters for FrontMCP UI development.
|
|
3
|
+
React-free build utilities, theming, runtime helpers, and platform adapters for FrontMCP UI development.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
[](https://www.npmjs.com/package/@frontmcp/uipack)
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
| ------------------ | --------------------------------------------------------------------- | --------------------- |
|
|
9
|
-
| `@frontmcp/uipack` | Themes, runtime helpers, build/render pipelines, validation, adapters | No |
|
|
10
|
-
| `@frontmcp/ui` | HTML/React components, layouts, widgets, web components | Yes (peer dependency) |
|
|
7
|
+
## Package Split
|
|
11
8
|
|
|
12
|
-
|
|
9
|
+
| Package | Purpose | React Required |
|
|
10
|
+
| ------------------ | --------------------------------------------------------------------- | -------------- |
|
|
11
|
+
| `@frontmcp/uipack` | Themes, runtime helpers, build/render pipelines, validation, adapters | No |
|
|
12
|
+
| `@frontmcp/ui` | HTML/React components, layouts, widgets, web components | Yes (peer dep) |
|
|
13
13
|
|
|
14
|
-
##
|
|
14
|
+
## Install
|
|
15
15
|
|
|
16
16
|
```bash
|
|
17
17
|
npm install @frontmcp/uipack
|
|
18
|
-
# or
|
|
19
|
-
yarn add @frontmcp/uipack
|
|
20
18
|
```
|
|
21
19
|
|
|
22
20
|
## Features
|
|
23
21
|
|
|
24
|
-
- **Theme system**
|
|
25
|
-
- **Build API**
|
|
26
|
-
- **Build modes**
|
|
27
|
-
- **Runtime helpers**
|
|
28
|
-
- **Platform adapters**
|
|
29
|
-
- **Validation**
|
|
30
|
-
- **Bundler/cache**
|
|
31
|
-
|
|
32
|
-
## Quick Start
|
|
33
|
-
|
|
34
|
-
### Theme + layout utilities
|
|
35
|
-
|
|
36
|
-
```ts
|
|
37
|
-
import { DEFAULT_THEME, createTheme, buildCdnScriptsFromTheme } from '@frontmcp/uipack/theme';
|
|
38
|
-
|
|
39
|
-
const customTheme = createTheme({
|
|
40
|
-
name: 'brand',
|
|
41
|
-
colors: {
|
|
42
|
-
semantic: {
|
|
43
|
-
primary: '#0BA5EC',
|
|
44
|
-
secondary: '#6366F1',
|
|
45
|
-
},
|
|
46
|
-
},
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
const scripts = await buildCdnScriptsFromTheme(customTheme, { inline: true });
|
|
50
|
-
```
|
|
22
|
+
- **Theme system** — Tailwind-style palettes, fonts, CDN assets, platform-aware inlining ([docs][docs-theme])
|
|
23
|
+
- **Build API** — compile tool templates with esbuild/SWC, emit static widgets, cached manifests ([docs][docs-build])
|
|
24
|
+
- **Build modes** — static, dynamic, or hybrid rendering; multi-platform bundler helpers ([docs][docs-build-modes])
|
|
25
|
+
- **Runtime helpers** — wrap HTML/React/MDX with CSP, sanitize content, expose MCP Bridge metadata ([docs][docs-runtime])
|
|
26
|
+
- **Platform adapters** — OpenAI/Claude/Gemini discovery metadata, serving modes, host capabilities ([docs][docs-adapters])
|
|
27
|
+
- **Validation** — schema path extraction, Handlebars template validation, error boxes ([docs][docs-validation])
|
|
28
|
+
- **Bundler/cache** — filesystem and Redis caches, transpile/render caches, hashing utilities ([docs][docs-bundler])
|
|
51
29
|
|
|
52
|
-
|
|
30
|
+
## Quick Example
|
|
53
31
|
|
|
54
32
|
```ts
|
|
55
33
|
import { buildToolUI } from '@frontmcp/uipack/build';
|
|
56
34
|
|
|
57
35
|
const result = await buildToolUI({
|
|
58
|
-
template: '<div>{{output.temperature}}
|
|
59
|
-
context: {
|
|
60
|
-
input: { location: 'London' },
|
|
61
|
-
output: { temperature: 18 },
|
|
62
|
-
},
|
|
36
|
+
template: '<div>{{output.temperature}} C</div>',
|
|
37
|
+
context: { input: { location: 'London' }, output: { temperature: 18 } },
|
|
63
38
|
platform: 'openai',
|
|
64
39
|
});
|
|
65
|
-
|
|
66
|
-
console.log(result.html);
|
|
67
40
|
```
|
|
68
41
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
```ts
|
|
72
|
-
import { wrapToolUI, createTemplateHelpers } from '@frontmcp/uipack/runtime';
|
|
42
|
+
> Full guide: [UI Overview][docs-overview]
|
|
73
43
|
|
|
74
|
-
|
|
75
|
-
const html = `<div>${helpers.escapeHtml(ctx.output.summary)}</div>`;
|
|
76
|
-
|
|
77
|
-
const response = wrapToolUI({
|
|
78
|
-
html,
|
|
79
|
-
displayMode: 'inline',
|
|
80
|
-
servingMode: 'auto',
|
|
81
|
-
widgetDescription: 'Summarized status card',
|
|
82
|
-
});
|
|
83
|
-
```
|
|
84
|
-
|
|
85
|
-
### Resolve platform capabilities
|
|
86
|
-
|
|
87
|
-
```ts
|
|
88
|
-
import { getPlatform, canUseCdn, needsInlineScripts } from '@frontmcp/uipack/theme';
|
|
89
|
-
|
|
90
|
-
const claude = getPlatform('claude');
|
|
91
|
-
const inline = needsInlineScripts(claude);
|
|
92
|
-
const cdnOk = canUseCdn(claude);
|
|
93
|
-
```
|
|
94
|
-
|
|
95
|
-
## Entry points
|
|
44
|
+
## Entry Points
|
|
96
45
|
|
|
97
46
|
| Path | Purpose |
|
|
98
47
|
| ----------------------------- | -------------------------------------------------------- |
|
|
@@ -107,45 +56,36 @@ const cdnOk = canUseCdn(claude);
|
|
|
107
56
|
| `@frontmcp/uipack/types` | Shared template/context types |
|
|
108
57
|
| `@frontmcp/uipack/utils` | Escaping, safe stringify, helper utilities |
|
|
109
58
|
|
|
110
|
-
##
|
|
59
|
+
## Docs
|
|
111
60
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
61
|
+
| Topic | Link |
|
|
62
|
+
| ----------------- | ------------------------------- |
|
|
63
|
+
| Overview | [UI Overview][docs-overview] |
|
|
64
|
+
| Theme system | [Theming][docs-theme] |
|
|
65
|
+
| Build API | [Build Tools][docs-build] |
|
|
66
|
+
| Build modes | [Build Modes][docs-build-modes] |
|
|
67
|
+
| Runtime helpers | [Runtime][docs-runtime] |
|
|
68
|
+
| Platform adapters | [Adapters][docs-adapters] |
|
|
69
|
+
| Validation | [Validation][docs-validation] |
|
|
70
|
+
| Bundler | [Bundler][docs-bundler] |
|
|
115
71
|
|
|
116
|
-
|
|
117
|
-
temperature: z.number(),
|
|
118
|
-
city: z.string(),
|
|
119
|
-
});
|
|
72
|
+
## Related Packages
|
|
120
73
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
}
|
|
125
|
-
```
|
|
126
|
-
|
|
127
|
-
## Cache + bundler helpers
|
|
128
|
-
|
|
129
|
-
```ts
|
|
130
|
-
import { createFilesystemBuilder } from '@frontmcp/uipack/bundler/file-cache';
|
|
74
|
+
- [`@frontmcp/ui`](../ui) — React components that consume these helpers
|
|
75
|
+
- [`@frontmcp/sdk`](../sdk) — core framework and decorators
|
|
76
|
+
- [`@frontmcp/testing`](../testing) — UI assertions for automated testing
|
|
131
77
|
|
|
132
|
-
|
|
133
|
-
const manifest = await builder.build({ entry: './widgets/weather.tsx' });
|
|
134
|
-
```
|
|
135
|
-
|
|
136
|
-
## Development
|
|
137
|
-
|
|
138
|
-
```bash
|
|
139
|
-
yarn nx build uipack
|
|
140
|
-
yarn nx test uipack
|
|
141
|
-
```
|
|
142
|
-
|
|
143
|
-
## Related packages
|
|
78
|
+
## License
|
|
144
79
|
|
|
145
|
-
- [
|
|
146
|
-
- [`@frontmcp/sdk`](../sdk/README.md) – Core SDK and decorators
|
|
147
|
-
- [`@frontmcp/testing`](../testing/README.md) – UI assertions for automated testing
|
|
80
|
+
Apache-2.0 — see [LICENSE](../../LICENSE).
|
|
148
81
|
|
|
149
|
-
|
|
82
|
+
<!-- links -->
|
|
150
83
|
|
|
151
|
-
|
|
84
|
+
[docs-overview]: https://docs.agentfront.dev/frontmcp/ui/overview
|
|
85
|
+
[docs-theme]: https://docs.agentfront.dev/frontmcp/ui/theming
|
|
86
|
+
[docs-build]: https://docs.agentfront.dev/frontmcp/ui/build-tools
|
|
87
|
+
[docs-build-modes]: https://docs.agentfront.dev/frontmcp/ui/build-modes
|
|
88
|
+
[docs-runtime]: https://docs.agentfront.dev/frontmcp/ui/runtime
|
|
89
|
+
[docs-adapters]: https://docs.agentfront.dev/frontmcp/ui/adapters
|
|
90
|
+
[docs-validation]: https://docs.agentfront.dev/frontmcp/ui/validation
|
|
91
|
+
[docs-bundler]: https://docs.agentfront.dev/frontmcp/ui/bundler
|
package/bundler/index.js
CHANGED
|
@@ -1023,7 +1023,7 @@ function throwOnViolations(violations) {
|
|
|
1023
1023
|
}
|
|
1024
1024
|
|
|
1025
1025
|
// libs/uipack/src/bundler/sandbox/enclave-adapter.ts
|
|
1026
|
-
var
|
|
1026
|
+
var import_core = require("@enclave-vm/core");
|
|
1027
1027
|
var DEFAULT_ENCLAVE_OPTIONS = {
|
|
1028
1028
|
securityLevel: "SECURE",
|
|
1029
1029
|
timeout: 5e3,
|
|
@@ -1189,7 +1189,7 @@ async function executeCode(code, context = {}) {
|
|
|
1189
1189
|
}
|
|
1190
1190
|
};
|
|
1191
1191
|
globals["require"] = buildRequireFunction(context);
|
|
1192
|
-
const enclave = new
|
|
1192
|
+
const enclave = new import_core.Enclave({
|
|
1193
1193
|
...DEFAULT_ENCLAVE_OPTIONS,
|
|
1194
1194
|
timeout: context.timeout ?? DEFAULT_ENCLAVE_OPTIONS.timeout,
|
|
1195
1195
|
maxIterations: context.maxIterations ?? DEFAULT_ENCLAVE_OPTIONS.maxIterations,
|
package/esm/bundler/index.mjs
CHANGED
|
@@ -959,7 +959,7 @@ function throwOnViolations(violations) {
|
|
|
959
959
|
}
|
|
960
960
|
|
|
961
961
|
// libs/uipack/src/bundler/sandbox/enclave-adapter.ts
|
|
962
|
-
import { Enclave } from "enclave-vm";
|
|
962
|
+
import { Enclave } from "@enclave-vm/core";
|
|
963
963
|
var DEFAULT_ENCLAVE_OPTIONS = {
|
|
964
964
|
securityLevel: "SECURE",
|
|
965
965
|
timeout: 5e3,
|
package/esm/index.mjs
CHANGED
|
@@ -797,6 +797,184 @@ var init_theme2 = __esm({
|
|
|
797
797
|
}
|
|
798
798
|
});
|
|
799
799
|
|
|
800
|
+
// libs/uipack/src/runtime/sanitizer.ts
|
|
801
|
+
import DOMPurify from "dompurify";
|
|
802
|
+
function isEmail(value) {
|
|
803
|
+
return PII_PATTERNS.email.test(value);
|
|
804
|
+
}
|
|
805
|
+
function isPhone(value) {
|
|
806
|
+
return PII_PATTERNS.phone.test(value);
|
|
807
|
+
}
|
|
808
|
+
function isCreditCard(value) {
|
|
809
|
+
const digits = value.replace(/[-\s]/g, "");
|
|
810
|
+
return digits.length >= 13 && digits.length <= 19 && PII_PATTERNS.creditCard.test(value);
|
|
811
|
+
}
|
|
812
|
+
function isSSN(value) {
|
|
813
|
+
return PII_PATTERNS.ssn.test(value);
|
|
814
|
+
}
|
|
815
|
+
function isIPv4(value) {
|
|
816
|
+
return PII_PATTERNS.ipv4.test(value);
|
|
817
|
+
}
|
|
818
|
+
function detectPIIType(value) {
|
|
819
|
+
if (isEmail(value)) return "EMAIL";
|
|
820
|
+
if (isCreditCard(value)) return "CARD";
|
|
821
|
+
if (isSSN(value)) return "ID";
|
|
822
|
+
if (isPhone(value)) return "PHONE";
|
|
823
|
+
if (isIPv4(value)) return "IP";
|
|
824
|
+
return null;
|
|
825
|
+
}
|
|
826
|
+
function redactPIIFromText(text) {
|
|
827
|
+
if (text.length > MAX_PII_TEXT_LENGTH) {
|
|
828
|
+
return text;
|
|
829
|
+
}
|
|
830
|
+
let result = text;
|
|
831
|
+
result = result.replace(PII_PATTERNS.creditCardInText, REDACTION_TOKENS.CARD);
|
|
832
|
+
result = result.replace(PII_PATTERNS.ssnInText, REDACTION_TOKENS.ID);
|
|
833
|
+
result = result.replace(PII_PATTERNS.emailInText, REDACTION_TOKENS.EMAIL);
|
|
834
|
+
result = result.replace(PII_PATTERNS.phoneInText, REDACTION_TOKENS.PHONE);
|
|
835
|
+
result = result.replace(PII_PATTERNS.ipv4InText, REDACTION_TOKENS.IP);
|
|
836
|
+
return result;
|
|
837
|
+
}
|
|
838
|
+
function sanitizeValue(key, value, path, options, depth, visited) {
|
|
839
|
+
const maxDepth = options.maxDepth ?? 10;
|
|
840
|
+
if (depth > maxDepth) {
|
|
841
|
+
return value;
|
|
842
|
+
}
|
|
843
|
+
if (value === null || value === void 0) {
|
|
844
|
+
return value;
|
|
845
|
+
}
|
|
846
|
+
if (typeof options.mode === "function") {
|
|
847
|
+
return options.mode(key, value, path);
|
|
848
|
+
}
|
|
849
|
+
if (Array.isArray(options.mode)) {
|
|
850
|
+
const lowerKey = key.toLowerCase();
|
|
851
|
+
if (options.mode.some((f) => f.toLowerCase() === lowerKey)) {
|
|
852
|
+
return REDACTION_TOKENS.REDACTED;
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
if (typeof value === "string") {
|
|
856
|
+
if (options.mode === true) {
|
|
857
|
+
const piiType = detectPIIType(value);
|
|
858
|
+
if (piiType) {
|
|
859
|
+
return REDACTION_TOKENS[piiType];
|
|
860
|
+
}
|
|
861
|
+
if (options.sanitizeInText !== false) {
|
|
862
|
+
const redacted = redactPIIFromText(value);
|
|
863
|
+
if (redacted !== value) {
|
|
864
|
+
return redacted;
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
return value;
|
|
869
|
+
}
|
|
870
|
+
if (Array.isArray(value)) {
|
|
871
|
+
if (visited.has(value)) {
|
|
872
|
+
return REDACTION_TOKENS.REDACTED;
|
|
873
|
+
}
|
|
874
|
+
visited.add(value);
|
|
875
|
+
return value.map(
|
|
876
|
+
(item, index) => sanitizeValue(String(index), item, [...path, String(index)], options, depth + 1, visited)
|
|
877
|
+
);
|
|
878
|
+
}
|
|
879
|
+
if (typeof value === "object") {
|
|
880
|
+
if (visited.has(value)) {
|
|
881
|
+
return REDACTION_TOKENS.REDACTED;
|
|
882
|
+
}
|
|
883
|
+
visited.add(value);
|
|
884
|
+
const result = {};
|
|
885
|
+
for (const [k, v] of Object.entries(value)) {
|
|
886
|
+
result[k] = sanitizeValue(k, v, [...path, k], options, depth + 1, visited);
|
|
887
|
+
}
|
|
888
|
+
return result;
|
|
889
|
+
}
|
|
890
|
+
return value;
|
|
891
|
+
}
|
|
892
|
+
function sanitizeInput(input, mode = true) {
|
|
893
|
+
if (!input || typeof input !== "object") {
|
|
894
|
+
return {};
|
|
895
|
+
}
|
|
896
|
+
const options = {
|
|
897
|
+
mode,
|
|
898
|
+
maxDepth: 10,
|
|
899
|
+
sanitizeInText: true
|
|
900
|
+
};
|
|
901
|
+
const visited = /* @__PURE__ */ new WeakSet();
|
|
902
|
+
return sanitizeValue("", input, [], options, 0, visited);
|
|
903
|
+
}
|
|
904
|
+
function createSanitizer(mode = true) {
|
|
905
|
+
return (input) => sanitizeInput(input, mode);
|
|
906
|
+
}
|
|
907
|
+
function detectPII(input, options) {
|
|
908
|
+
const maxDepth = options?.maxDepth ?? 10;
|
|
909
|
+
const fields = [];
|
|
910
|
+
const visited = /* @__PURE__ */ new WeakSet();
|
|
911
|
+
const check = (value, path, depth) => {
|
|
912
|
+
if (depth > maxDepth) return;
|
|
913
|
+
if (value === null || value === void 0) return;
|
|
914
|
+
if (typeof value === "string") {
|
|
915
|
+
if (detectPIIType(value)) {
|
|
916
|
+
fields.push(path.join("."));
|
|
917
|
+
}
|
|
918
|
+
return;
|
|
919
|
+
}
|
|
920
|
+
if (Array.isArray(value)) {
|
|
921
|
+
if (visited.has(value)) return;
|
|
922
|
+
visited.add(value);
|
|
923
|
+
value.forEach((item, index) => check(item, [...path, String(index)], depth + 1));
|
|
924
|
+
return;
|
|
925
|
+
}
|
|
926
|
+
if (typeof value === "object") {
|
|
927
|
+
if (visited.has(value)) return;
|
|
928
|
+
visited.add(value);
|
|
929
|
+
for (const [k, v] of Object.entries(value)) {
|
|
930
|
+
check(v, [...path, k], depth + 1);
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
};
|
|
934
|
+
check(input, [], 0);
|
|
935
|
+
return {
|
|
936
|
+
hasPII: fields.length > 0,
|
|
937
|
+
fields
|
|
938
|
+
};
|
|
939
|
+
}
|
|
940
|
+
var REDACTION_TOKENS, PII_PATTERNS, MAX_PII_TEXT_LENGTH;
|
|
941
|
+
var init_sanitizer = __esm({
|
|
942
|
+
"libs/uipack/src/runtime/sanitizer.ts"() {
|
|
943
|
+
"use strict";
|
|
944
|
+
REDACTION_TOKENS = {
|
|
945
|
+
EMAIL: "[EMAIL]",
|
|
946
|
+
PHONE: "[PHONE]",
|
|
947
|
+
CARD: "[CARD]",
|
|
948
|
+
ID: "[ID]",
|
|
949
|
+
IP: "[IP]",
|
|
950
|
+
REDACTED: "[REDACTED]"
|
|
951
|
+
};
|
|
952
|
+
PII_PATTERNS = {
|
|
953
|
+
// Email: user@domain.tld
|
|
954
|
+
email: /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/,
|
|
955
|
+
// Email embedded in text
|
|
956
|
+
emailInText: /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g,
|
|
957
|
+
// Phone: Various formats (US-centric but flexible)
|
|
958
|
+
phone: /^[+]?[(]?[0-9]{1,4}[)]?[-\s.]?[0-9]{1,4}[-\s.]?[0-9]{1,9}$/,
|
|
959
|
+
// Phone embedded in text
|
|
960
|
+
phoneInText: /(?:\+?1[-.\s]?)?\(?[0-9]{3}\)?[-.\s]?[0-9]{3}[-.\s]?[0-9]{4}/g,
|
|
961
|
+
// Credit card: 13-19 digits (with optional separators)
|
|
962
|
+
creditCard: /^(?:[0-9]{4}[-\s]?){3,4}[0-9]{1,4}$/,
|
|
963
|
+
// Credit card embedded in text
|
|
964
|
+
creditCardInText: /\b(?:[0-9]{4}[-\s]?){3,4}[0-9]{1,4}\b/g,
|
|
965
|
+
// SSN: XXX-XX-XXXX
|
|
966
|
+
ssn: /^[0-9]{3}[-]?[0-9]{2}[-]?[0-9]{4}$/,
|
|
967
|
+
// SSN embedded in text
|
|
968
|
+
ssnInText: /\b[0-9]{3}[-]?[0-9]{2}[-]?[0-9]{4}\b/g,
|
|
969
|
+
// IPv4 address
|
|
970
|
+
ipv4: /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/,
|
|
971
|
+
// IPv4 embedded in text
|
|
972
|
+
ipv4InText: /\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\b/g
|
|
973
|
+
};
|
|
974
|
+
MAX_PII_TEXT_LENGTH = 1e5;
|
|
975
|
+
}
|
|
976
|
+
});
|
|
977
|
+
|
|
800
978
|
// libs/uipack/src/renderers/utils/detect.ts
|
|
801
979
|
function isReactComponent(value) {
|
|
802
980
|
if (typeof value !== "function") {
|
|
@@ -6491,8 +6669,14 @@ var MCP_BRIDGE_RUNTIME = `
|
|
|
6491
6669
|
</script>
|
|
6492
6670
|
`;
|
|
6493
6671
|
function getMCPBridgeScript() {
|
|
6494
|
-
const
|
|
6495
|
-
|
|
6672
|
+
const openTag = "<script>";
|
|
6673
|
+
const closeTag = "</script>";
|
|
6674
|
+
const startIdx = MCP_BRIDGE_RUNTIME.indexOf(openTag);
|
|
6675
|
+
const endIdx = MCP_BRIDGE_RUNTIME.lastIndexOf(closeTag);
|
|
6676
|
+
if (startIdx === -1 || endIdx === -1 || endIdx <= startIdx) {
|
|
6677
|
+
return "";
|
|
6678
|
+
}
|
|
6679
|
+
return MCP_BRIDGE_RUNTIME.slice(startIdx + openTag.length, endIdx).trim();
|
|
6496
6680
|
}
|
|
6497
6681
|
function isMCPBridgeSupported() {
|
|
6498
6682
|
if (typeof window === "undefined") return false;
|
|
@@ -6587,179 +6771,7 @@ function sanitizeCSPDomains(domains) {
|
|
|
6587
6771
|
// libs/uipack/src/runtime/wrapper.ts
|
|
6588
6772
|
init_theme2();
|
|
6589
6773
|
init_utils();
|
|
6590
|
-
|
|
6591
|
-
// libs/uipack/src/runtime/sanitizer.ts
|
|
6592
|
-
var REDACTION_TOKENS = {
|
|
6593
|
-
EMAIL: "[EMAIL]",
|
|
6594
|
-
PHONE: "[PHONE]",
|
|
6595
|
-
CARD: "[CARD]",
|
|
6596
|
-
ID: "[ID]",
|
|
6597
|
-
IP: "[IP]",
|
|
6598
|
-
REDACTED: "[REDACTED]"
|
|
6599
|
-
};
|
|
6600
|
-
var PII_PATTERNS = {
|
|
6601
|
-
// Email: user@domain.tld
|
|
6602
|
-
email: /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/,
|
|
6603
|
-
// Email embedded in text
|
|
6604
|
-
emailInText: /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g,
|
|
6605
|
-
// Phone: Various formats (US-centric but flexible)
|
|
6606
|
-
phone: /^[+]?[(]?[0-9]{1,4}[)]?[-\s.]?[0-9]{1,4}[-\s.]?[0-9]{1,9}$/,
|
|
6607
|
-
// Phone embedded in text
|
|
6608
|
-
phoneInText: /(?:\+?1[-.\s]?)?\(?[0-9]{3}\)?[-.\s]?[0-9]{3}[-.\s]?[0-9]{4}/g,
|
|
6609
|
-
// Credit card: 13-19 digits (with optional separators)
|
|
6610
|
-
creditCard: /^(?:[0-9]{4}[-\s]?){3,4}[0-9]{1,4}$/,
|
|
6611
|
-
// Credit card embedded in text
|
|
6612
|
-
creditCardInText: /\b(?:[0-9]{4}[-\s]?){3,4}[0-9]{1,4}\b/g,
|
|
6613
|
-
// SSN: XXX-XX-XXXX
|
|
6614
|
-
ssn: /^[0-9]{3}[-]?[0-9]{2}[-]?[0-9]{4}$/,
|
|
6615
|
-
// SSN embedded in text
|
|
6616
|
-
ssnInText: /\b[0-9]{3}[-]?[0-9]{2}[-]?[0-9]{4}\b/g,
|
|
6617
|
-
// IPv4 address
|
|
6618
|
-
ipv4: /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/,
|
|
6619
|
-
// IPv4 embedded in text
|
|
6620
|
-
ipv4InText: /\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\b/g
|
|
6621
|
-
};
|
|
6622
|
-
function isEmail(value) {
|
|
6623
|
-
return PII_PATTERNS.email.test(value);
|
|
6624
|
-
}
|
|
6625
|
-
function isPhone(value) {
|
|
6626
|
-
return PII_PATTERNS.phone.test(value);
|
|
6627
|
-
}
|
|
6628
|
-
function isCreditCard(value) {
|
|
6629
|
-
const digits = value.replace(/[-\s]/g, "");
|
|
6630
|
-
return digits.length >= 13 && digits.length <= 19 && PII_PATTERNS.creditCard.test(value);
|
|
6631
|
-
}
|
|
6632
|
-
function isSSN(value) {
|
|
6633
|
-
return PII_PATTERNS.ssn.test(value);
|
|
6634
|
-
}
|
|
6635
|
-
function isIPv4(value) {
|
|
6636
|
-
return PII_PATTERNS.ipv4.test(value);
|
|
6637
|
-
}
|
|
6638
|
-
function detectPIIType(value) {
|
|
6639
|
-
if (isEmail(value)) return "EMAIL";
|
|
6640
|
-
if (isCreditCard(value)) return "CARD";
|
|
6641
|
-
if (isSSN(value)) return "ID";
|
|
6642
|
-
if (isPhone(value)) return "PHONE";
|
|
6643
|
-
if (isIPv4(value)) return "IP";
|
|
6644
|
-
return null;
|
|
6645
|
-
}
|
|
6646
|
-
var MAX_PII_TEXT_LENGTH = 1e5;
|
|
6647
|
-
function redactPIIFromText(text) {
|
|
6648
|
-
if (text.length > MAX_PII_TEXT_LENGTH) {
|
|
6649
|
-
return text;
|
|
6650
|
-
}
|
|
6651
|
-
let result = text;
|
|
6652
|
-
result = result.replace(PII_PATTERNS.creditCardInText, REDACTION_TOKENS.CARD);
|
|
6653
|
-
result = result.replace(PII_PATTERNS.ssnInText, REDACTION_TOKENS.ID);
|
|
6654
|
-
result = result.replace(PII_PATTERNS.emailInText, REDACTION_TOKENS.EMAIL);
|
|
6655
|
-
result = result.replace(PII_PATTERNS.phoneInText, REDACTION_TOKENS.PHONE);
|
|
6656
|
-
result = result.replace(PII_PATTERNS.ipv4InText, REDACTION_TOKENS.IP);
|
|
6657
|
-
return result;
|
|
6658
|
-
}
|
|
6659
|
-
function sanitizeValue(key, value, path, options, depth, visited) {
|
|
6660
|
-
const maxDepth = options.maxDepth ?? 10;
|
|
6661
|
-
if (depth > maxDepth) {
|
|
6662
|
-
return value;
|
|
6663
|
-
}
|
|
6664
|
-
if (value === null || value === void 0) {
|
|
6665
|
-
return value;
|
|
6666
|
-
}
|
|
6667
|
-
if (typeof options.mode === "function") {
|
|
6668
|
-
return options.mode(key, value, path);
|
|
6669
|
-
}
|
|
6670
|
-
if (Array.isArray(options.mode)) {
|
|
6671
|
-
const lowerKey = key.toLowerCase();
|
|
6672
|
-
if (options.mode.some((f) => f.toLowerCase() === lowerKey)) {
|
|
6673
|
-
return REDACTION_TOKENS.REDACTED;
|
|
6674
|
-
}
|
|
6675
|
-
}
|
|
6676
|
-
if (typeof value === "string") {
|
|
6677
|
-
if (options.mode === true) {
|
|
6678
|
-
const piiType = detectPIIType(value);
|
|
6679
|
-
if (piiType) {
|
|
6680
|
-
return REDACTION_TOKENS[piiType];
|
|
6681
|
-
}
|
|
6682
|
-
if (options.sanitizeInText !== false) {
|
|
6683
|
-
const redacted = redactPIIFromText(value);
|
|
6684
|
-
if (redacted !== value) {
|
|
6685
|
-
return redacted;
|
|
6686
|
-
}
|
|
6687
|
-
}
|
|
6688
|
-
}
|
|
6689
|
-
return value;
|
|
6690
|
-
}
|
|
6691
|
-
if (Array.isArray(value)) {
|
|
6692
|
-
if (visited.has(value)) {
|
|
6693
|
-
return REDACTION_TOKENS.REDACTED;
|
|
6694
|
-
}
|
|
6695
|
-
visited.add(value);
|
|
6696
|
-
return value.map(
|
|
6697
|
-
(item, index) => sanitizeValue(String(index), item, [...path, String(index)], options, depth + 1, visited)
|
|
6698
|
-
);
|
|
6699
|
-
}
|
|
6700
|
-
if (typeof value === "object") {
|
|
6701
|
-
if (visited.has(value)) {
|
|
6702
|
-
return REDACTION_TOKENS.REDACTED;
|
|
6703
|
-
}
|
|
6704
|
-
visited.add(value);
|
|
6705
|
-
const result = {};
|
|
6706
|
-
for (const [k, v] of Object.entries(value)) {
|
|
6707
|
-
result[k] = sanitizeValue(k, v, [...path, k], options, depth + 1, visited);
|
|
6708
|
-
}
|
|
6709
|
-
return result;
|
|
6710
|
-
}
|
|
6711
|
-
return value;
|
|
6712
|
-
}
|
|
6713
|
-
function sanitizeInput(input, mode = true) {
|
|
6714
|
-
if (!input || typeof input !== "object") {
|
|
6715
|
-
return {};
|
|
6716
|
-
}
|
|
6717
|
-
const options = {
|
|
6718
|
-
mode,
|
|
6719
|
-
maxDepth: 10,
|
|
6720
|
-
sanitizeInText: true
|
|
6721
|
-
};
|
|
6722
|
-
const visited = /* @__PURE__ */ new WeakSet();
|
|
6723
|
-
return sanitizeValue("", input, [], options, 0, visited);
|
|
6724
|
-
}
|
|
6725
|
-
function createSanitizer(mode = true) {
|
|
6726
|
-
return (input) => sanitizeInput(input, mode);
|
|
6727
|
-
}
|
|
6728
|
-
function detectPII(input, options) {
|
|
6729
|
-
const maxDepth = options?.maxDepth ?? 10;
|
|
6730
|
-
const fields = [];
|
|
6731
|
-
const visited = /* @__PURE__ */ new WeakSet();
|
|
6732
|
-
const check = (value, path, depth) => {
|
|
6733
|
-
if (depth > maxDepth) return;
|
|
6734
|
-
if (value === null || value === void 0) return;
|
|
6735
|
-
if (typeof value === "string") {
|
|
6736
|
-
if (detectPIIType(value)) {
|
|
6737
|
-
fields.push(path.join("."));
|
|
6738
|
-
}
|
|
6739
|
-
return;
|
|
6740
|
-
}
|
|
6741
|
-
if (Array.isArray(value)) {
|
|
6742
|
-
if (visited.has(value)) return;
|
|
6743
|
-
visited.add(value);
|
|
6744
|
-
value.forEach((item, index) => check(item, [...path, String(index)], depth + 1));
|
|
6745
|
-
return;
|
|
6746
|
-
}
|
|
6747
|
-
if (typeof value === "object") {
|
|
6748
|
-
if (visited.has(value)) return;
|
|
6749
|
-
visited.add(value);
|
|
6750
|
-
for (const [k, v] of Object.entries(value)) {
|
|
6751
|
-
check(v, [...path, k], depth + 1);
|
|
6752
|
-
}
|
|
6753
|
-
}
|
|
6754
|
-
};
|
|
6755
|
-
check(input, [], 0);
|
|
6756
|
-
return {
|
|
6757
|
-
hasPII: fields.length > 0,
|
|
6758
|
-
fields
|
|
6759
|
-
};
|
|
6760
|
-
}
|
|
6761
|
-
|
|
6762
|
-
// libs/uipack/src/runtime/wrapper.ts
|
|
6774
|
+
init_sanitizer();
|
|
6763
6775
|
function createTemplateHelpers() {
|
|
6764
6776
|
let idCounter2 = 0;
|
|
6765
6777
|
return {
|
|
@@ -10909,6 +10921,9 @@ async function buildToolUIMulti(options) {
|
|
|
10909
10921
|
};
|
|
10910
10922
|
}
|
|
10911
10923
|
|
|
10924
|
+
// libs/uipack/src/runtime/index.ts
|
|
10925
|
+
init_sanitizer();
|
|
10926
|
+
|
|
10912
10927
|
// libs/uipack/src/preview/openai-preview.ts
|
|
10913
10928
|
init_utils();
|
|
10914
10929
|
var DEFAULT_WIDGET_CSP = {
|
package/esm/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@frontmcp/uipack",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"description": "FrontMCP UIpack - Bundling, build tools, and platform adapters for MCP UI development (React-free core)",
|
|
5
5
|
"author": "AgentFront <info@agentfront.dev>",
|
|
6
6
|
"homepage": "https://docs.agentfront.dev",
|
|
@@ -58,9 +58,10 @@
|
|
|
58
58
|
"node": ">=22.0.0"
|
|
59
59
|
},
|
|
60
60
|
"dependencies": {
|
|
61
|
-
"@frontmcp/utils": "0.
|
|
61
|
+
"@frontmcp/utils": "0.9.0",
|
|
62
62
|
"@swc/core": "^1.5.0",
|
|
63
|
-
"enclave-vm": "^2.
|
|
63
|
+
"@enclave-vm/core": "^2.10.1",
|
|
64
|
+
"dompurify": "^3.3.1",
|
|
64
65
|
"esbuild": "^0.27.1",
|
|
65
66
|
"handlebars": "^4.7.8",
|
|
66
67
|
"zod": "^4.0.0"
|