@bleedingdev/modern-js-mcp-apps 0.0.0-stage → 3.11.3-ultramodern.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,317 @@
1
+ "use strict";
2
+ var __webpack_require__ = {};
3
+ (()=>{
4
+ __webpack_require__.n = (module)=>{
5
+ var getter = module && module.__esModule ? ()=>module['default'] : ()=>module;
6
+ __webpack_require__.d(getter, {
7
+ a: getter
8
+ });
9
+ return getter;
10
+ };
11
+ })();
12
+ (()=>{
13
+ __webpack_require__.d = (exports1, getters, values)=>{
14
+ var define = (defs, kind)=>{
15
+ for(var key in defs)if (__webpack_require__.o(defs, key) && !__webpack_require__.o(exports1, key)) Object.defineProperty(exports1, key, {
16
+ enumerable: true,
17
+ [kind]: defs[key]
18
+ });
19
+ };
20
+ define(getters, "get");
21
+ define(values, "value");
22
+ };
23
+ })();
24
+ (()=>{
25
+ __webpack_require__.o = (obj, prop)=>Object.prototype.hasOwnProperty.call(obj, prop);
26
+ })();
27
+ (()=>{
28
+ __webpack_require__.r = (exports1)=>{
29
+ if ("u" > typeof Symbol && Symbol.toStringTag) Object.defineProperty(exports1, Symbol.toStringTag, {
30
+ value: 'Module'
31
+ });
32
+ Object.defineProperty(exports1, '__esModule', {
33
+ value: true
34
+ });
35
+ };
36
+ })();
37
+ var __webpack_exports__ = {};
38
+ __webpack_require__.r(__webpack_exports__);
39
+ __webpack_require__.d(__webpack_exports__, {
40
+ createBffHandlers: ()=>createBffHandlers,
41
+ createFetchHandler: ()=>createFetchHandler,
42
+ registerUiResources: ()=>registerUiResources
43
+ });
44
+ const server_namespaceObject = require("@modelcontextprotocol/server");
45
+ const requestUiOptions = new WeakMap();
46
+ const promises_namespaceObject = require("node:fs/promises");
47
+ const external_node_path_namespaceObject = require("node:path");
48
+ var external_node_path_default = /*#__PURE__*/ __webpack_require__.n(external_node_path_namespaceObject);
49
+ const external_node_url_namespaceObject = require("node:url");
50
+ const ext_apps_server_namespaceObject = require("@modelcontextprotocol/ext-apps/server");
51
+ function isRecord(value) {
52
+ return null !== value && 'object' == typeof value && !Array.isArray(value);
53
+ }
54
+ function validateUiMetadata(value) {
55
+ if (!isRecord(value)) throw new Error('UI metadata must be an object');
56
+ if (void 0 !== value.prefersBorder && 'boolean' != typeof value.prefersBorder) throw new Error('prefersBorder must be a boolean');
57
+ if (void 0 !== value.domain && 'string' != typeof value.domain) throw new Error('domain must be a string');
58
+ if (void 0 !== value.permissions && !isRecord(value.permissions)) throw new Error('permissions must be an object');
59
+ if (void 0 !== value.csp) {
60
+ if (!isRecord(value.csp)) throw new Error('csp must be an object');
61
+ for (const [key, domains] of Object.entries(value.csp))if (![
62
+ 'resourceDomains',
63
+ 'connectDomains',
64
+ 'frameDomains',
65
+ 'baseUriDomains'
66
+ ].includes(key) || !Array.isArray(domains) || domains.some((domain)=>'string' != typeof domain)) throw new Error(`Invalid CSP field: ${key}`);
67
+ }
68
+ }
69
+ function assertHttpUrl(source) {
70
+ const url = new URL(source);
71
+ if (![
72
+ 'http:',
73
+ 'https:'
74
+ ].includes(url.protocol) || url.username || url.password) throw new Error(`Invalid UI URL: ${source}`);
75
+ return url;
76
+ }
77
+ function validateUiManifest(value) {
78
+ if (!isRecord(value) || 1 !== value.version) throw new Error('Unsupported MCP UI manifest version');
79
+ if ('string' != typeof value.assetBase || !value.assetBase || !isRecord(value.remotes) || !isRecord(value.views)) throw new Error('Invalid MCP UI manifest');
80
+ if ('request' !== value.assetBase) assertHttpUrl(value.assetBase);
81
+ const checkSource = (source)=>{
82
+ if ('string' != typeof source || !source) throw new Error('Missing UI resource source');
83
+ if (/^https?:\/\//.test(source)) assertHttpUrl(source);
84
+ else if (source.startsWith('/') || source.includes('\\') || decodeURIComponent(source).split('/').includes('..') || /^[a-z][\w+.-]*:/i.test(source) || /[?#]/.test(source)) throw new Error(`Invalid relative UI resource: ${source}`);
85
+ };
86
+ for (const remote of Object.values(value.remotes)){
87
+ if (!isRecord(remote)) throw new Error('Invalid MF remote');
88
+ checkSource(remote.entry);
89
+ checkSource(remote.shell);
90
+ }
91
+ for (const [name, view] of Object.entries(value.views)){
92
+ if (!name.trim() || !isRecord(view)) throw new Error('Invalid UI view');
93
+ validateUiMetadata(view.ui);
94
+ if (!('html' in view) || 'remote' in view || 'expose' in view) if (!('html' in view) && 'string' == typeof view.remote && Object.hasOwn(value.remotes, view.remote) && 'string' == typeof view.expose && view.expose.startsWith('./')) continue;
95
+ else throw new Error(`Invalid UI manifest view: ${name}`);
96
+ else checkSource(view.html);
97
+ }
98
+ }
99
+ const manifestCache = new Map();
100
+ const remoteManifestTtl = 60000;
101
+ async function loadUiManifest(source, mode) {
102
+ const url = source instanceof URL ? new URL(source.href) : /^https?:\/\//.test(source) ? new URL(source) : (0, external_node_url_namespaceObject.pathToFileURL)(external_node_path_default().resolve(source));
103
+ const production = 'production' === mode;
104
+ const key = `${production ? 'production' : 'development'}:${url.href}`;
105
+ let version = 'process';
106
+ if (!production && 'file:' === url.protocol) {
107
+ const info = await (0, promises_namespaceObject.stat)((0, external_node_url_namespaceObject.fileURLToPath)(url), {
108
+ bigint: true
109
+ });
110
+ version = `${info.ino}:${info.size}:${info.mtimeNs}:${info.ctimeNs}`;
111
+ }
112
+ const cached = manifestCache.get(key);
113
+ if (cached?.version === version && cached.expiresAt > Date.now()) return cached.value;
114
+ const entry = {
115
+ version,
116
+ expiresAt: 1 / 0,
117
+ value: readSource(url).then((text)=>{
118
+ const manifest = JSON.parse(text);
119
+ validateUiManifest(manifest);
120
+ if (!production && 'file:' !== url.protocol) entry.expiresAt = Date.now() + remoteManifestTtl;
121
+ return {
122
+ manifest,
123
+ location: url
124
+ };
125
+ }).catch((error)=>{
126
+ if (manifestCache.get(key) === entry) manifestCache.delete(key);
127
+ throw error;
128
+ })
129
+ };
130
+ manifestCache.set(key, entry);
131
+ return entry.value;
132
+ }
133
+ async function readSource(url, signal) {
134
+ if ('file:' === url.protocol) return (0, promises_namespaceObject.readFile)((0, external_node_url_namespaceObject.fileURLToPath)(url), {
135
+ encoding: 'utf8',
136
+ signal
137
+ });
138
+ assertHttpUrl(url.href);
139
+ const response = await fetch(url, {
140
+ signal: signal ? AbortSignal.any([
141
+ signal,
142
+ AbortSignal.timeout(30000)
143
+ ]) : AbortSignal.timeout(30000)
144
+ });
145
+ if (!response.ok) throw new Error(`Failed to read UI resource: HTTP ${response.status}`);
146
+ return response.text();
147
+ }
148
+ function publicBase(manifest, options) {
149
+ const configured = options.assetBase ?? manifest.assetBase;
150
+ if ('request' !== configured) {
151
+ const base = assertHttpUrl(configured);
152
+ if (!base.pathname.endsWith('/')) base.pathname += '/';
153
+ return base;
154
+ }
155
+ if (!options.request) throw new Error('Request-relative UI assets require a request or an explicit assetBase');
156
+ return new URL('/', requestOrigin(options.request));
157
+ }
158
+ function requestOrigin(request) {
159
+ const url = new URL(request.url);
160
+ const proto = request.headers.get('x-forwarded-proto')?.split(',')[0].trim();
161
+ if ('https' === proto || 'http' === proto) url.protocol = `${proto}:`;
162
+ return url.origin;
163
+ }
164
+ function escapeAttribute(value) {
165
+ return value.replace(/&/g, '&amp;').replace(/"/g, '&quot;').replace(/</g, '&lt;');
166
+ }
167
+ async function registerUiResources(server, options = {}) {
168
+ const injected = options.request && requestUiOptions.get(options.request);
169
+ options = {
170
+ ...options,
171
+ manifest: options.manifest ?? injected?.manifest,
172
+ mode: options.mode ?? injected?.mode
173
+ };
174
+ if (!options.manifest) throw new Error('MCP UI manifest is required. Use the Modern.js MCP Apps server plugin with a request or pass an explicit manifest path or URL');
175
+ const { manifest, location } = await loadUiManifest(options.manifest, options.mode ?? 'production');
176
+ const base = publicBase(manifest, options);
177
+ const devSocket = 'development' === options.mode && options.request ? new URL(requestOrigin(options.request)) : void 0;
178
+ if (devSocket) devSocket.protocol = 'https:' === devSocket.protocol ? 'wss:' : 'ws:';
179
+ const uris = Object.create(null);
180
+ for (const [name, view] of Object.entries(manifest.views)){
181
+ const uri = `ui://app/${encodeURIComponent(name)}`;
182
+ uris[name] = uri;
183
+ const remote = 'remote' in view ? manifest.remotes[view.remote] : void 0;
184
+ const entry = remote ? new URL(remote.entry, base).href : void 0;
185
+ const ui = {
186
+ ...view.ui,
187
+ csp: {
188
+ ...view.ui.csp,
189
+ resourceDomains: [
190
+ ...new Set([
191
+ ...view.ui.csp?.resourceDomains ?? [],
192
+ base.origin,
193
+ ...entry ? [
194
+ new URL(entry).origin
195
+ ] : []
196
+ ])
197
+ ],
198
+ baseUriDomains: [
199
+ ...new Set([
200
+ ...view.ui.csp?.baseUriDomains ?? [],
201
+ base.origin
202
+ ])
203
+ ],
204
+ connectDomains: [
205
+ ...new Set([
206
+ ...view.ui.csp?.connectDomains ?? [],
207
+ base.origin,
208
+ ...entry ? [
209
+ new URL(entry).origin
210
+ ] : [],
211
+ ...devSocket ? [
212
+ devSocket.origin
213
+ ] : []
214
+ ])
215
+ ]
216
+ }
217
+ };
218
+ (0, ext_apps_server_namespaceObject.registerAppResource)(server, name, uri, {
219
+ _meta: {
220
+ ui
221
+ }
222
+ }, async (_uri, extra)=>{
223
+ const source = 'html' in view ? view.html : remote.shell;
224
+ const sourceUrl = /^https?:\/\//.test(source) ? new URL(source) : new URL(source, location);
225
+ let html = await readSource(sourceUrl, options.request ? AbortSignal.any([
226
+ options.request.signal,
227
+ extra.mcpReq.signal
228
+ ]) : extra.mcpReq.signal);
229
+ if (!/<head\b[^>]*>/i.test(html)) throw new Error('UI HTML requires a head element');
230
+ if (/<base\b/i.test(html)) throw new Error('UI HTML must not contain a base element');
231
+ const config = remote && 'expose' in view ? `<script type="application/json" id="modern-mcp-config">${JSON.stringify({
232
+ name: view.remote,
233
+ entry,
234
+ expose: view.expose,
235
+ view: name
236
+ }).replace(/</g, '\\u003c')}</script>` : '';
237
+ html = html.replace(/<head\b[^>]*>/i, (head)=>`${head}<base href="${escapeAttribute(base.href)}">${config}`);
238
+ return {
239
+ contents: [
240
+ {
241
+ uri,
242
+ mimeType: ext_apps_server_namespaceObject.RESOURCE_MIME_TYPE,
243
+ text: html,
244
+ _meta: {
245
+ ui
246
+ }
247
+ }
248
+ ]
249
+ };
250
+ });
251
+ }
252
+ return uris;
253
+ }
254
+ async function currentContext(useContext) {
255
+ if (useContext) return useContext();
256
+ const { useHonoContext } = await import("@modern-js/server-runtime");
257
+ return useHonoContext();
258
+ }
259
+ function createFetchHandler(createServer, options = {}) {
260
+ const handler = (0, server_namespaceObject.createMcpHandler)(({ requestInfo })=>{
261
+ if (!requestInfo) throw new Error('MCP HTTP request is required');
262
+ return createServer(requestInfo);
263
+ }, {
264
+ legacy: 'stateless',
265
+ onerror: options.onError
266
+ });
267
+ return async (request)=>{
268
+ const origin = request.headers.get('origin');
269
+ if (origin && ![
270
+ new URL(request.url).origin,
271
+ requestOrigin(request),
272
+ ...options.allowedOrigins ?? []
273
+ ].includes(origin)) return new Response('Forbidden origin', {
274
+ status: 403
275
+ });
276
+ return handler.fetch(request);
277
+ };
278
+ }
279
+ function createBffHandlers(createServer, options = {}) {
280
+ const { useContext, ...fetchOptions } = options;
281
+ const handle = createFetchHandler(async (request)=>{
282
+ const uiOptions = (await currentContext(useContext)).get('locals')?.modernMcpApps;
283
+ if (uiOptions) requestUiOptions.set(request, uiOptions);
284
+ return createServer(request);
285
+ }, fetchOptions);
286
+ const route = async (input)=>{
287
+ const context = await currentContext(useContext);
288
+ const raw = context.req.raw;
289
+ const headers = new Headers(raw.headers);
290
+ headers.delete('content-length');
291
+ const request = new Request(raw.url, {
292
+ method: raw.method,
293
+ headers,
294
+ ...'GET' === raw.method || 'HEAD' === raw.method ? {} : {
295
+ body: void 0 === input.data ? '' : JSON.stringify(input.data)
296
+ },
297
+ signal: raw.signal
298
+ });
299
+ return handle(request);
300
+ };
301
+ return {
302
+ POST: route,
303
+ GET: route,
304
+ DELETE: route
305
+ };
306
+ }
307
+ exports.createBffHandlers = __webpack_exports__.createBffHandlers;
308
+ exports.createFetchHandler = __webpack_exports__.createFetchHandler;
309
+ exports.registerUiResources = __webpack_exports__.registerUiResources;
310
+ for(var __rspack_i in __webpack_exports__)if (-1 === [
311
+ "createBffHandlers",
312
+ "createFetchHandler",
313
+ "registerUiResources"
314
+ ].indexOf(__rspack_i)) exports[__rspack_i] = __webpack_exports__[__rspack_i];
315
+ Object.defineProperty(exports, '__esModule', {
316
+ value: true
317
+ });
@@ -0,0 +1,75 @@
1
+ import * as __rspack_external_react from "react";
2
+ import { createElement } from "react";
3
+ import * as __rspack_external_react_dom_client_c9e7316b from "react-dom/client";
4
+ import { createRoot } from "react-dom/client";
5
+ import { createInstance } from "@module-federation/enhanced/runtime";
6
+ import * as __rspack_external_react_jsx_dev_runtime_60d9ad90 from "react/jsx-dev-runtime";
7
+ import * as __rspack_external_react_jsx_runtime_fccef271 from "react/jsx-runtime";
8
+ import * as __rspack_external_react_dom_20ef40a6 from "react-dom";
9
+ import { createMcpView } from "./react.mjs";
10
+ function createMfView(config) {
11
+ const libs = {
12
+ react: __rspack_external_react,
13
+ 'react-dom': __rspack_external_react_dom_20ef40a6,
14
+ 'react-dom/client': __rspack_external_react_dom_client_c9e7316b,
15
+ 'react/jsx-runtime': __rspack_external_react_jsx_runtime_fccef271,
16
+ 'react/jsx-dev-runtime': __rspack_external_react_jsx_dev_runtime_60d9ad90
17
+ };
18
+ const runtime = createInstance({
19
+ name: 'modern-mcp-shell',
20
+ remotes: [
21
+ {
22
+ name: config.name,
23
+ entry: config.entry
24
+ }
25
+ ],
26
+ shared: Object.fromEntries(Object.entries(libs).map(([name, lib])=>[
27
+ name,
28
+ {
29
+ version: __rspack_external_react.version,
30
+ lib: ()=>lib,
31
+ shareConfig: {
32
+ singleton: true,
33
+ requiredVersion: __rspack_external_react.version
34
+ }
35
+ }
36
+ ])),
37
+ shareStrategy: 'loaded-first'
38
+ });
39
+ const Remote = /*#__PURE__*/ __rspack_external_react.lazy(async ()=>{
40
+ const mod = await runtime.loadRemote('./' === config.expose ? config.name : `${config.name}/${config.expose.slice(2)}`);
41
+ if (!mod?.default) throw new Error(`Missing default export for ${config.expose}`);
42
+ return {
43
+ default: createMcpView(mod.default, {
44
+ name: config.view,
45
+ version: '1.0.0'
46
+ }, mod.mcpCapabilities)
47
+ };
48
+ });
49
+ return function() {
50
+ return /*#__PURE__*/ __rspack_external_react.createElement(MfErrorBoundary, null, /*#__PURE__*/ __rspack_external_react.createElement(__rspack_external_react.Suspense, {
51
+ fallback: /*#__PURE__*/ __rspack_external_react.createElement("p", {
52
+ role: "status"
53
+ }, "Loading component…")
54
+ }, /*#__PURE__*/ __rspack_external_react.createElement(Remote, null)));
55
+ };
56
+ }
57
+ class MfErrorBoundary extends __rspack_external_react.Component {
58
+ state = {};
59
+ static getDerivedStateFromError(error) {
60
+ return {
61
+ error
62
+ };
63
+ }
64
+ render() {
65
+ if (this.state.error) return /*#__PURE__*/ __rspack_external_react.createElement("p", {
66
+ role: "alert"
67
+ }, this.state.error.message);
68
+ return this.props.children;
69
+ }
70
+ }
71
+ const root = document.getElementById('root');
72
+ const configElement = document.getElementById('modern-mcp-config');
73
+ if (!root || !configElement?.textContent) throw new Error('MCP shell requires a root element and MF configuration');
74
+ const mf_entry_config = JSON.parse(configElement.textContent);
75
+ createRoot(root).render(createElement(createMfView(mf_entry_config)));
@@ -0,0 +1,80 @@
1
+ import { useApp, useDocumentTheme, useHostFonts, useHostStyleVariables, useHostStyles } from "@modelcontextprotocol/ext-apps/react";
2
+ import { createElement, useReducer } from "react";
3
+ function reduceViewState(_state, event) {
4
+ if ('result' === event.type) return {
5
+ result: event.result,
6
+ cancelled: false
7
+ };
8
+ return {
9
+ cancelled: 'cancelled' === event.type
10
+ };
11
+ }
12
+ function resultProps(result) {
13
+ const output = result?.isError ? void 0 : result?.structuredContent;
14
+ return output && 'object' == typeof output && !Array.isArray(output) ? output : {};
15
+ }
16
+ function useMcpApp(options) {
17
+ const [state, dispatch] = useReducer(reduceViewState, {
18
+ cancelled: false
19
+ });
20
+ const connection = useApp({
21
+ ...options,
22
+ onAppCreated (app) {
23
+ app.ontoolinput = ()=>dispatch({
24
+ type: 'input'
25
+ });
26
+ app.ontoolinputpartial = ()=>dispatch({
27
+ type: 'input'
28
+ });
29
+ app.ontoolresult = (result)=>dispatch({
30
+ type: 'result',
31
+ result
32
+ });
33
+ app.ontoolcancelled = ()=>dispatch({
34
+ type: 'cancelled'
35
+ });
36
+ app.onteardown = async ()=>{
37
+ dispatch({
38
+ type: 'teardown'
39
+ });
40
+ return {};
41
+ };
42
+ }
43
+ });
44
+ useHostStyles(connection.app, connection.app?.getHostContext());
45
+ return {
46
+ ...connection,
47
+ ...state,
48
+ viewProps: resultProps(state.result)
49
+ };
50
+ }
51
+ function createMcpView(View, appInfo, capabilities = {}) {
52
+ const appCapabilities = {
53
+ availableDisplayModes: [
54
+ 'inline',
55
+ 'fullscreen'
56
+ ],
57
+ ...capabilities
58
+ };
59
+ return function() {
60
+ const { app, isConnected, error, cancelled, result, viewProps } = useMcpApp({
61
+ appInfo,
62
+ capabilities: appCapabilities
63
+ });
64
+ if (error) return createElement('p', {
65
+ role: 'alert'
66
+ }, error.message);
67
+ if (cancelled) return createElement('p', {
68
+ role: 'status'
69
+ }, 'Tool cancelled');
70
+ if (result?.isError) return createElement('p', {
71
+ role: 'alert'
72
+ }, 'Tool execution failed');
73
+ if (!app || !isConnected || !result) return null;
74
+ return createElement(View, {
75
+ ...viewProps,
76
+ mcpApp: app
77
+ });
78
+ };
79
+ }
80
+ export { createMcpView, useApp, useDocumentTheme, useHostFonts, useHostStyleVariables, useHostStyles, useMcpApp };
@@ -0,0 +1,81 @@
1
+ function isRecord(value) {
2
+ return null !== value && 'object' == typeof value && !Array.isArray(value);
3
+ }
4
+ function validateUiMetadata(value) {
5
+ if (!isRecord(value)) throw new Error('UI metadata must be an object');
6
+ if (void 0 !== value.prefersBorder && 'boolean' != typeof value.prefersBorder) throw new Error('prefersBorder must be a boolean');
7
+ if (void 0 !== value.domain && 'string' != typeof value.domain) throw new Error('domain must be a string');
8
+ if (void 0 !== value.permissions && !isRecord(value.permissions)) throw new Error('permissions must be an object');
9
+ if (void 0 !== value.csp) {
10
+ if (!isRecord(value.csp)) throw new Error('csp must be an object');
11
+ for (const [key, domains] of Object.entries(value.csp))if (![
12
+ 'resourceDomains',
13
+ 'connectDomains',
14
+ 'frameDomains',
15
+ 'baseUriDomains'
16
+ ].includes(key) || !Array.isArray(domains) || domains.some((domain)=>'string' != typeof domain)) throw new Error(`Invalid CSP field: ${key}`);
17
+ }
18
+ }
19
+ function validateViewsConfig(value) {
20
+ if (!isRecord(value) || !isRecord(value.views)) throw new Error('mcp-views.json requires a views object');
21
+ validateUiMetadata(value);
22
+ if (void 0 !== value.remote) {
23
+ if ('string' != typeof value.remote) throw new Error('remote must be a string');
24
+ assertRemoteManifest(value.remote);
25
+ }
26
+ for (const key of Object.keys(value))if (![
27
+ 'views',
28
+ 'remote',
29
+ 'csp',
30
+ 'prefersBorder',
31
+ 'permissions',
32
+ 'domain'
33
+ ].includes(key)) throw new Error(`Unknown MCP views option: ${key}`);
34
+ for (const [name, view] of Object.entries(value.views)){
35
+ if (!name.trim()) throw new Error('View names must be non-empty');
36
+ if (!('string' == typeof view && view.trim())) {
37
+ if (isRecord(view) && 'string' == typeof view.expose && view.expose.startsWith('./') && Object.keys(view).every((key)=>'expose' === key || 'remote' === key)) {
38
+ if (!('remote' in view)) continue;
39
+ if ('string' == typeof view.remote) {
40
+ assertRemoteManifest(view.remote);
41
+ continue;
42
+ }
43
+ }
44
+ throw new Error(`Invalid view: ${name}. Use a component path, { expose: './Name' } or { remote: 'https://host/mf-manifest.json', expose: './Name' }`);
45
+ }
46
+ }
47
+ }
48
+ function assertRemoteManifest(remote) {
49
+ if (!/^https?:\/\//.test(remote) || !assertHttpUrl(remote).pathname.endsWith('.json')) throw new Error(`Invalid MF remote: ${remote}. Use https://host/mf-manifest.json`);
50
+ }
51
+ function assertHttpUrl(source) {
52
+ const url = new URL(source);
53
+ if (![
54
+ 'http:',
55
+ 'https:'
56
+ ].includes(url.protocol) || url.username || url.password) throw new Error(`Invalid UI URL: ${source}`);
57
+ return url;
58
+ }
59
+ function validateUiManifest(value) {
60
+ if (!isRecord(value) || 1 !== value.version) throw new Error('Unsupported MCP UI manifest version');
61
+ if ('string' != typeof value.assetBase || !value.assetBase || !isRecord(value.remotes) || !isRecord(value.views)) throw new Error('Invalid MCP UI manifest');
62
+ if ('request' !== value.assetBase) assertHttpUrl(value.assetBase);
63
+ const checkSource = (source)=>{
64
+ if ('string' != typeof source || !source) throw new Error('Missing UI resource source');
65
+ if (/^https?:\/\//.test(source)) assertHttpUrl(source);
66
+ else if (source.startsWith('/') || source.includes('\\') || decodeURIComponent(source).split('/').includes('..') || /^[a-z][\w+.-]*:/i.test(source) || /[?#]/.test(source)) throw new Error(`Invalid relative UI resource: ${source}`);
67
+ };
68
+ for (const remote of Object.values(value.remotes)){
69
+ if (!isRecord(remote)) throw new Error('Invalid MF remote');
70
+ checkSource(remote.entry);
71
+ checkSource(remote.shell);
72
+ }
73
+ for (const [name, view] of Object.entries(value.views)){
74
+ if (!name.trim() || !isRecord(view)) throw new Error('Invalid UI view');
75
+ validateUiMetadata(view.ui);
76
+ if (!('html' in view) || 'remote' in view || 'expose' in view) if (!('html' in view) && 'string' == typeof view.remote && Object.hasOwn(value.remotes, view.remote) && 'string' == typeof view.expose && view.expose.startsWith('./')) continue;
77
+ else throw new Error(`Invalid UI manifest view: ${name}`);
78
+ else checkSource(view.html);
79
+ }
80
+ }
81
+ export { assertHttpUrl, validateUiManifest, validateUiMetadata, validateViewsConfig };