@bleedingdev/modern-js-mcp-apps 0.0.0-bootstrap → 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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2021-present Modern.js
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,69 @@
1
- # Namespace bootstrap only
1
+ # @modern-js/mcp-apps
2
2
 
3
- This deprecated placeholder reserves the UltraModern MCP Apps package identity for GitHub trusted publishing. It contains no implementation and must not be installed as a framework release.
3
+ Build React MCP cards with Modern.js and write servers with the official MCP SDK.
4
+ The UI build and the server exchange only `ui-manifest.json`.
5
+
6
+ Install `react`, `react-dom`, `@modelcontextprotocol/server` and `@modelcontextprotocol/ext-apps` in the application. They are required peers so cards use the application's React and resource registration and HTTP handling use the application's SDK versions. `@modern-js/server-runtime` is an optional peer that provides the default `useContext` for `createBffHandlers`, loaded when a BFF handler is invoked. Standalone Fetch servers do not need it. `@module-federation/enhanced` is an optional peer that provides the default MF shell runtime.
7
+
8
+ - `@modern-js/mcp-apps/cli` exports the `mcpAppsPlugin` build plugin
9
+ - `@modern-js/mcp-apps/server-plugin` is the server plugin, loaded by name by the framework
10
+ - `@modern-js/mcp-apps/server` exports `registerUiResources`, `createFetchHandler` and `createBffHandlers`
11
+ - `@modern-js/mcp-apps/react` exports `McpViewProps`, `createMcpView`, `useMcpApp` and official host styling hooks
12
+
13
+ ## Build plugin
14
+
15
+ Declare React cards in `mcp-views.json` and enable `mcpAppsPlugin({ views: './mcp-views.json' })`.
16
+
17
+ ```json
18
+ {
19
+ "views": {
20
+ "todos": "./src/components/Todos.tsx",
21
+ "stats": { "expose": "./Stats" }
22
+ },
23
+ "csp": { "resourceDomains": [], "connectDomains": [] },
24
+ "prefersBorder": true
25
+ }
26
+ ```
27
+
28
+ Component paths resolve relative to the JSON file. Components must have a default export. MF exposes refer to this application's MF manifest output and must also have a default component export. `{ "remote": "https://host/mf-manifest.json", "expose": "./Name" }` refers to a deployed remote instead. Set `remote` at the top level alongside `csp` to share it across all expose views, for example `{ "remote": "https://host/mf-manifest.json", "views": { "todos": { "expose": "./Todos" }, "stats": { "expose": "./Stats" } } }`. A view-level `remote` overrides this default. Component paths remain local. Without either remote setting, exposes refer to this application. With a top-level `remote`, expose views in the same config cannot refer to this application's exposes, so set `remote` per view to mix both. The URL must point to the remote's manifest, with a path ending in `.json`, and the runtime reads the remote's name from it. The shell loads the remote manifest when the card opens, so republishing the remote needs no MCP server rebuild. Its exposes are not checked at build time, and the application needs no MF plugin. The manifest origin is added to CSP. Declare other origins serving the remote's chunks in `csp`. Local cards are added as application entries and reuse the framework's multi-entry compilation, including its loaders, styles and build plugins. Only the MF shell uses a separate Rsbuild compilation with the application's React dependencies, with its startup scripts and styles inlined. The plugin emits `dist/mcp-apps/ui-manifest.json` and never registers HTTP routes.
29
+
30
+ - `mf.manifestFile` is the MF manifest filename in the web build output. It defaults to `mf-manifest.json` and must match exactly one emitted file.
31
+ - `mf.runtime` is the module, resolved from the application, that the MF shell imports `createInstance` from. It defaults to `@module-federation/enhanced/runtime`.
32
+ - `csp` adds CSP domains to every view, merged with the `csp` in `mcp-views.json`. Use it for domains computed in the framework or application config.
33
+
34
+ The build also adds the origin of an absolute or protocol-relative asset prefix (`//cdn.example/app/` becomes `https://cdn.example`) to every view's `resourceDomains` and `connectDomains`, because card scripts and chunks load from it. A protocol-relative prefix keeps `assetBase` request-relative, so the UI manifest and MF manifest still load from the server.
35
+
36
+ Local card entries mount React directly through `createMcpView` without the framework page runtime, because an opaque MCP Apps sandbox cannot read page cookies. With `source.enableAsyncEntry`, which the MF plugin enables, each entry adds a dynamic import boundary for MF shared initialization.
37
+
38
+ `createBffHandlers(createServer, { useContext })` accepts the function returning the current Hono context. It defaults to `useHonoContext` from `@modern-js/server-runtime`. Frameworks wrapping Modern.js pass their own implementation.
39
+
40
+ `registerUiResources()` loads the manifest asynchronously. The Modern.js server plugin provides the manifest path and runtime mode through the request created by `createBffHandlers`. Call `registerUiResources(server, { request })` to resolve them automatically. Explicit options override injected configuration. Standalone services must set `manifest` to a path or HTTP(S) URL and may set `mode` to `development` for live updates. The default mode is `production`. Relative HTML and shell files resolve against that location. Parsed manifests are cached per source URL and environment. Production keeps successful loads for the process lifetime, so updating a manifest at the same location requires a process restart. Development checks local file metadata on each registration and caches remote manifests for 60 seconds. Concurrent loads share one request and failures are retried. HTML is still read when a resource is requested, and request-specific origins are never cached.
41
+
42
+ `await registerUiResources(server, { request })` returns view names mapped to `ui://app/<encoded-name>`. Register tools with the official `registerAppTool` helper and put the chosen URI in `_meta.ui.resourceUri`. MF selection is injected into the resource HTML rather than tool results.
43
+
44
+ `assetBase` is a public directory URL or `request`. Request mode uses the request origin, honoring a sanitized `x-forwarded-proto`. Override `assetBase` at registration for an external static origin or path prefix. Resources add their required asset, connection and base URI origins to CSP while preserving configured metadata. Other chunk/CDN origins must be declared in `csp` or `mcp-views.json`.
45
+
46
+ `createFetchHandler(createServer)` returns a standard Request/Response handler. It uses the SDK's stateless HTTP serving and rejects foreign browser origins unless configured through `allowedOrigins`. Authentication and authorization belong to the surrounding middleware or server factory. `createServer` must return a fresh official `McpServer` per request. `createBffHandlers` adapts the same factory to Modern BFF's consumed request body and exports POST, GET and DELETE handlers. The stateless SDK rejects unsupported session operations.
47
+
48
+ Card component props are the latest successful `structuredContent` plus the reserved `mcpApp` field. Input arguments are not merged into props. New input, cancellation and teardown clear previous output. The default wrapper shows loading, cancellation and error states.
49
+
50
+ See the MCP Apps guide in the Modern.js documentation for configuration and deployment examples.
51
+
52
+ Cards advertise `inline` and `fullscreen` display modes by default. A local component module or MF expose can export `mcpCapabilities` to override display modes or declare additional SDK capabilities:
53
+
54
+ <!-- prettier-ignore -->
55
+ ```tsx
56
+ import type { McpCapabilities } from '@modern-js/mcp-apps/react'
57
+
58
+ export const mcpCapabilities = {
59
+ availableDisplayModes: ['inline'],
60
+ } satisfies McpCapabilities
61
+
62
+ export default function Card() {
63
+ return <div>My card</div>
64
+ }
65
+ ```
66
+
67
+ `createMcpView(View, appInfo, capabilities)` accepts the same options directly. Properties are merged with the defaults, and an explicit `availableDisplayModes` array replaces the default array. MF modules are loaded before the handshake so their capabilities are included in initialization. Declaring fullscreen support does not add a fullscreen control or change the display mode automatically.
68
+
69
+ Development resource CSP includes the request origin's `ws:` or `wss:` equivalent for the application HMR connection. Custom HMR hosts still need explicit `connectDomains` configuration. Production resources do not add this permission automatically.
@@ -0,0 +1,471 @@
1
+ "use strict";
2
+ const __rslib_import_meta_url__ = /*#__PURE__*/ function() {
3
+ return "u" < typeof document ? new (require('url'.replace('', ''))).URL('file:' + __filename).href : document.currentScript && document.currentScript.src || new URL('main.js', document.baseURI).href;
4
+ }();
5
+ var __webpack_require__ = {};
6
+ (()=>{
7
+ __webpack_require__.n = (module)=>{
8
+ var getter = module && module.__esModule ? ()=>module['default'] : ()=>module;
9
+ __webpack_require__.d(getter, {
10
+ a: getter
11
+ });
12
+ return getter;
13
+ };
14
+ })();
15
+ (()=>{
16
+ __webpack_require__.d = (exports1, getters, values)=>{
17
+ var define = (defs, kind)=>{
18
+ for(var key in defs)if (__webpack_require__.o(defs, key) && !__webpack_require__.o(exports1, key)) Object.defineProperty(exports1, key, {
19
+ enumerable: true,
20
+ [kind]: defs[key]
21
+ });
22
+ };
23
+ define(getters, "get");
24
+ define(values, "value");
25
+ };
26
+ })();
27
+ (()=>{
28
+ __webpack_require__.o = (obj, prop)=>Object.prototype.hasOwnProperty.call(obj, prop);
29
+ })();
30
+ (()=>{
31
+ __webpack_require__.r = (exports1)=>{
32
+ if ("u" > typeof Symbol && Symbol.toStringTag) Object.defineProperty(exports1, Symbol.toStringTag, {
33
+ value: 'Module'
34
+ });
35
+ Object.defineProperty(exports1, '__esModule', {
36
+ value: true
37
+ });
38
+ };
39
+ })();
40
+ var __webpack_exports__ = {};
41
+ __webpack_require__.r(__webpack_exports__);
42
+ __webpack_require__.d(__webpack_exports__, {
43
+ default: ()=>cli,
44
+ mcpAppsPlugin: ()=>mcpAppsPlugin
45
+ });
46
+ const promises_namespaceObject = require("node:fs/promises");
47
+ var promises_default = /*#__PURE__*/ __webpack_require__.n(promises_namespaceObject);
48
+ const external_node_module_namespaceObject = require("node:module");
49
+ const external_node_path_namespaceObject = require("node:path");
50
+ var external_node_path_default = /*#__PURE__*/ __webpack_require__.n(external_node_path_namespaceObject);
51
+ const external_node_url_namespaceObject = require("node:url");
52
+ const core_namespaceObject = require("@rsbuild/core");
53
+ function isRecord(value) {
54
+ return null !== value && 'object' == typeof value && !Array.isArray(value);
55
+ }
56
+ function validateUiMetadata(value) {
57
+ if (!isRecord(value)) throw new Error('UI metadata must be an object');
58
+ if (void 0 !== value.prefersBorder && 'boolean' != typeof value.prefersBorder) throw new Error('prefersBorder must be a boolean');
59
+ if (void 0 !== value.domain && 'string' != typeof value.domain) throw new Error('domain must be a string');
60
+ if (void 0 !== value.permissions && !isRecord(value.permissions)) throw new Error('permissions must be an object');
61
+ if (void 0 !== value.csp) {
62
+ if (!isRecord(value.csp)) throw new Error('csp must be an object');
63
+ for (const [key, domains] of Object.entries(value.csp))if (![
64
+ 'resourceDomains',
65
+ 'connectDomains',
66
+ 'frameDomains',
67
+ 'baseUriDomains'
68
+ ].includes(key) || !Array.isArray(domains) || domains.some((domain)=>'string' != typeof domain)) throw new Error(`Invalid CSP field: ${key}`);
69
+ }
70
+ }
71
+ function validateViewsConfig(value) {
72
+ if (!isRecord(value) || !isRecord(value.views)) throw new Error('mcp-views.json requires a views object');
73
+ validateUiMetadata(value);
74
+ if (void 0 !== value.remote) {
75
+ if ('string' != typeof value.remote) throw new Error('remote must be a string');
76
+ assertRemoteManifest(value.remote);
77
+ }
78
+ for (const key of Object.keys(value))if (![
79
+ 'views',
80
+ 'remote',
81
+ 'csp',
82
+ 'prefersBorder',
83
+ 'permissions',
84
+ 'domain'
85
+ ].includes(key)) throw new Error(`Unknown MCP views option: ${key}`);
86
+ for (const [name, view] of Object.entries(value.views)){
87
+ if (!name.trim()) throw new Error('View names must be non-empty');
88
+ if (!('string' == typeof view && view.trim())) {
89
+ if (isRecord(view) && 'string' == typeof view.expose && view.expose.startsWith('./') && Object.keys(view).every((key)=>'expose' === key || 'remote' === key)) {
90
+ if (!('remote' in view)) continue;
91
+ if ('string' == typeof view.remote) {
92
+ assertRemoteManifest(view.remote);
93
+ continue;
94
+ }
95
+ }
96
+ throw new Error(`Invalid view: ${name}. Use a component path, { expose: './Name' } or { remote: 'https://host/mf-manifest.json', expose: './Name' }`);
97
+ }
98
+ }
99
+ }
100
+ function assertRemoteManifest(remote) {
101
+ if (!/^https?:\/\//.test(remote) || !assertHttpUrl(remote).pathname.endsWith('.json')) throw new Error(`Invalid MF remote: ${remote}. Use https://host/mf-manifest.json`);
102
+ }
103
+ function assertHttpUrl(source) {
104
+ const url = new URL(source);
105
+ if (![
106
+ 'http:',
107
+ 'https:'
108
+ ].includes(url.protocol) || url.username || url.password) throw new Error(`Invalid UI URL: ${source}`);
109
+ return url;
110
+ }
111
+ const external_node_crypto_namespaceObject = require("node:crypto");
112
+ function assetPrefixOrigin(prefix) {
113
+ if ('string' != typeof prefix) return;
114
+ if (prefix.startsWith('//')) return new URL(`https:${prefix}`).origin;
115
+ if (/^https?:\/\//.test(prefix)) return new URL(prefix).origin;
116
+ }
117
+ function mergeCsp(...sources) {
118
+ const merged = {};
119
+ for (const source of sources)for (const [key, domains] of Object.entries(source ?? {}))merged[key] = [
120
+ ...new Set([
121
+ ...merged[key] ?? [],
122
+ ...domains ?? []
123
+ ])
124
+ ];
125
+ return Object.keys(merged).length ? merged : void 0;
126
+ }
127
+ function viewEntryName(name) {
128
+ const label = name.toLowerCase().replace(/[^a-z0-9-]+/g, '-').replace(/-+/g, '-').slice(0, 48).replace(/^-+|-+$/g, '') || 'view';
129
+ const hash = (0, external_node_crypto_namespaceObject.createHash)('sha256').update(name).digest('hex').slice(0, 12);
130
+ return `mcp-ui-${label}-${hash}`;
131
+ }
132
+ function findMfManifest(assets, manifestFile) {
133
+ const matches = assets.filter((asset)=>external_node_path_default().posix.basename(asset.name) === manifestFile);
134
+ if (1 !== matches.length) throw new Error(`Expected one MF manifest named ${manifestFile} in the web compilation, found ${matches.length}${matches.length ? `: ${matches.map((asset)=>asset.name).join(', ')}` : ''}. Set mf.manifestFile to the manifest filename`);
135
+ const [asset] = matches;
136
+ const manifest = JSON.parse(String(asset.source.source()));
137
+ if (!manifest || 'string' != typeof manifest.name || !Array.isArray(manifest.exposes)) throw new Error(`Invalid MF manifest: ${asset.name}`);
138
+ return {
139
+ filename: asset.name,
140
+ manifest: manifest
141
+ };
142
+ }
143
+ function createUiManifest(config, prefix, mf, csp) {
144
+ validateViewsConfig(config);
145
+ if (void 0 !== csp) validateUiMetadata({
146
+ csp
147
+ });
148
+ const { views, remote: defaultRemote, ...metadata } = config;
149
+ const assetOrigin = assetPrefixOrigin(prefix);
150
+ const mergedCsp = mergeCsp(metadata.csp, csp, assetOrigin ? {
151
+ resourceDomains: [
152
+ assetOrigin
153
+ ],
154
+ connectDomains: [
155
+ assetOrigin
156
+ ]
157
+ } : void 0);
158
+ const ui = {
159
+ prefersBorder: true,
160
+ ...metadata,
161
+ ...mergedCsp ? {
162
+ csp: mergedCsp
163
+ } : {}
164
+ };
165
+ const manifest = {
166
+ version: 1,
167
+ assetBase: 'string' == typeof prefix && /^https?:\/\//.test(prefix) ? `${prefix.replace(/\/+$/, '')}/` : 'request',
168
+ remotes: Object.create(null),
169
+ views: Object.create(null)
170
+ };
171
+ for (const [name, source] of Object.entries(views)){
172
+ if ('string' == typeof source) {
173
+ manifest.views[name] = {
174
+ html: `./ui/${viewEntryName(name)}.html`,
175
+ ui
176
+ };
177
+ continue;
178
+ }
179
+ let remote;
180
+ let entry;
181
+ const deployedRemote = source.remote ?? defaultRemote;
182
+ if (void 0 !== deployedRemote) {
183
+ entry = deployedRemote;
184
+ remote = `mcp-remote-${(0, external_node_crypto_namespaceObject.createHash)('sha256').update(entry).digest('hex').slice(0, 12)}`;
185
+ } else {
186
+ if (!mf) throw new Error(`View ${name} requires a Module Federation manifest. Enable the MF plugin and manifest output`);
187
+ if (!mf.manifest.exposes.some((item)=>item.name.replace(/^\.\//, '') === source.expose.slice(2))) throw new Error(`View ${name} references missing MF expose: ${source.expose}`);
188
+ const assetPath = 'string' == typeof prefix && prefix.startsWith('/') && !prefix.startsWith('//') ? prefix.replace(/^\/+|\/+$/g, '') : '';
189
+ entry = [
190
+ assetPath,
191
+ mf.filename
192
+ ].filter(Boolean).join('/');
193
+ remote = mf.manifest.name;
194
+ }
195
+ manifest.remotes[remote] = {
196
+ entry,
197
+ shell: './ui/mcp-shell.html'
198
+ };
199
+ manifest.views[name] = {
200
+ remote,
201
+ expose: source.expose,
202
+ ui
203
+ };
204
+ }
205
+ return manifest;
206
+ }
207
+ async function writeArtifact(filename, contents) {
208
+ await promises_default().mkdir(external_node_path_default().dirname(filename), {
209
+ recursive: true
210
+ });
211
+ await promises_default().writeFile(`${filename}.tmp`, contents);
212
+ await promises_default().rename(`${filename}.tmp`, filename);
213
+ }
214
+ const packageFile = (filename)=>external_node_path_default().resolve(external_node_path_default().dirname((0, external_node_url_namespaceObject.fileURLToPath)(__rslib_import_meta_url__)), '../..', 'dist/esm', filename);
215
+ const mcpAppsPlugin = (options = {})=>({
216
+ name: '@modern-js/mcp-apps',
217
+ post: [
218
+ '@modern-js/runtime'
219
+ ],
220
+ setup (api) {
221
+ api._internalServerPlugins(({ plugins })=>{
222
+ const ctx = api.getAppContext();
223
+ plugins.push({
224
+ name: '@modern-js/mcp-apps/server-plugin',
225
+ options: [
226
+ 'dev',
227
+ 'start'
228
+ ].includes(ctx.command) ? {
229
+ developmentManifest: external_node_path_default().join(ctx.internalDirectory, 'mcp-apps/ui-manifest.json')
230
+ } : {}
231
+ });
232
+ return {
233
+ plugins
234
+ };
235
+ });
236
+ let definition;
237
+ const attachedCompilers = new WeakSet();
238
+ const shellHtml = new Map();
239
+ const shellAssets = new Map();
240
+ let buildShell;
241
+ let shellDirty = true;
242
+ let shellBuild;
243
+ const localEntries = new Set();
244
+ const configPath = ()=>external_node_path_default().resolve(api.getAppContext().appDirectory, options.views ?? 'mcp-views.json');
245
+ api.addWatchFiles(()=>[
246
+ configPath()
247
+ ]);
248
+ api.modifyEntrypoints(async ({ entrypoints: current })=>{
249
+ const ctx = api.getAppContext();
250
+ const source = JSON.parse(await promises_default().readFile(configPath(), 'utf8'));
251
+ validateViewsConfig(source);
252
+ definition = source;
253
+ const appRequire = (0, external_node_module_namespaceObject.createRequire)(external_node_path_default().join(ctx.appDirectory, 'package.json'));
254
+ const needsMf = Object.values(definition.views).some((view)=>'string' != typeof view);
255
+ localEntries.clear();
256
+ shellHtml.clear();
257
+ shellAssets.clear();
258
+ buildShell = void 0;
259
+ shellDirty = true;
260
+ const shellEntries = {};
261
+ const entryDir = external_node_path_default().join(ctx.internalDirectory, 'mcp-apps/entries');
262
+ const entrypoints = current.filter((entry)=>entry.absoluteEntryDir !== entryDir);
263
+ const { enableAsyncEntry } = api.getNormalizedConfig().source;
264
+ for (const [name, source] of Object.entries(definition.views)){
265
+ if ('string' != typeof source) continue;
266
+ const entryName = viewEntryName(name);
267
+ if (entrypoints.some((entry)=>entry.entryName === entryName)) throw new Error(`MCP entry conflicts with application entry: ${entryName}`);
268
+ const entry = external_node_path_default().join(entryDir, `${entryName}.jsx`);
269
+ const mountEntry = enableAsyncEntry ? external_node_path_default().join(entryDir, `${entryName}-mount.jsx`) : entry;
270
+ await writeArtifact(mountEntry, `import React from ${JSON.stringify(appRequire.resolve('react'))};\nimport { createRoot } from ${JSON.stringify(appRequire.resolve('react-dom/client'))};\nimport { createMcpView } from ${JSON.stringify(packageFile('react.mjs'))};\nimport View, * as viewModule from ${JSON.stringify(external_node_path_default().resolve(external_node_path_default().dirname(configPath()), source))};\ncreateRoot(document.getElementById('root')).render(React.createElement(createMcpView(View, ${JSON.stringify({
271
+ name,
272
+ version: '1.0.0'
273
+ })}, Reflect.get(viewModule, 'mcpCapabilities'))));\n`);
274
+ if (enableAsyncEntry) await writeArtifact(entry, `import(${JSON.stringify(mountEntry)});\n`);
275
+ localEntries.add(entryName);
276
+ entrypoints.push({
277
+ entryName,
278
+ entry,
279
+ isMainEntry: false,
280
+ isAutoMount: false,
281
+ absoluteEntryDir: entryDir
282
+ });
283
+ }
284
+ if (needsMf) shellEntries['mcp-shell'] = packageFile('mf-entry.mjs');
285
+ if (Object.keys(shellEntries).length) {
286
+ const shellOutput = external_node_path_default().join(ctx.internalDirectory, 'mcp-apps/shell');
287
+ const config = api.getNormalizedConfig();
288
+ const development = [
289
+ 'dev',
290
+ 'start'
291
+ ].includes(ctx.command);
292
+ const assetPrefix = development ? config.dev.assetPrefix : config.output.assetPrefix;
293
+ const shell = await (0, core_namespaceObject.createRsbuild)({
294
+ cwd: ctx.appDirectory,
295
+ rsbuildConfig: {
296
+ mode: 'production',
297
+ html: {
298
+ inject: 'body',
299
+ title: 'MCP App'
300
+ },
301
+ source: {
302
+ entry: shellEntries,
303
+ define: {
304
+ ...config.source.define,
305
+ 'process.env.NODE_ENV': JSON.stringify(development ? 'development' : 'production')
306
+ }
307
+ },
308
+ resolve: {
309
+ alias: {
310
+ ...config.source.alias,
311
+ ...Object.fromEntries([
312
+ 'react',
313
+ 'react-dom',
314
+ 'react-dom/client',
315
+ 'react/jsx-runtime',
316
+ 'react/jsx-dev-runtime'
317
+ ].map((name)=>[
318
+ name + '$',
319
+ appRequire.resolve(name)
320
+ ])),
321
+ ...needsMf ? {
322
+ '@module-federation/enhanced/runtime$': appRequire.resolve(options.mf?.runtime ?? '@module-federation/enhanced/runtime')
323
+ } : {}
324
+ }
325
+ },
326
+ output: {
327
+ distPath: {
328
+ root: shellOutput
329
+ },
330
+ assetPrefix: 'string' == typeof assetPrefix ? assetPrefix : void 0,
331
+ inlineScripts: true,
332
+ inlineStyles: true,
333
+ legalComments: 'inline'
334
+ },
335
+ tools: {
336
+ swc: {
337
+ jsc: {
338
+ transform: {
339
+ react: {
340
+ runtime: 'automatic'
341
+ }
342
+ }
343
+ }
344
+ },
345
+ rspack: {
346
+ optimization: {
347
+ splitChunks: false,
348
+ nodeEnv: false
349
+ },
350
+ module: {
351
+ parser: {
352
+ javascript: {
353
+ dynamicImportMode: 'eager'
354
+ }
355
+ }
356
+ }
357
+ }
358
+ }
359
+ }
360
+ });
361
+ const compileShell = async ()=>{
362
+ const result = await shell.build();
363
+ await result.close();
364
+ shellAssets.clear();
365
+ for (const filename of (await promises_default().readdir(shellOutput, {
366
+ recursive: true
367
+ }))){
368
+ const absolute = external_node_path_default().join(shellOutput, filename);
369
+ if (!filename.endsWith('.html') && (await promises_default().stat(absolute)).isFile()) shellAssets.set(filename.replaceAll(external_node_path_default().sep, '/'), await promises_default().readFile(absolute));
370
+ }
371
+ for (const name of Object.keys(shellEntries))shellHtml.set(name, await promises_default().readFile(external_node_path_default().join(shellOutput, `${name}.html`), 'utf8'));
372
+ };
373
+ buildShell = ()=>{
374
+ if (shellBuild) return shellBuild;
375
+ if (!shellDirty) return Promise.resolve();
376
+ shellDirty = false;
377
+ shellBuild = compileShell().catch((error)=>{
378
+ shellDirty = true;
379
+ throw error;
380
+ }).finally(()=>{
381
+ shellBuild = void 0;
382
+ });
383
+ return shellBuild;
384
+ };
385
+ }
386
+ return {
387
+ entrypoints
388
+ };
389
+ });
390
+ api.modifyRsbuildConfig((config)=>{
391
+ const lazy = config.dev?.lazyCompilation;
392
+ if (!lazy) return config;
393
+ const current = 'object' == typeof lazy ? lazy : {};
394
+ const test = current.test;
395
+ const internal = api.getAppContext().internalDirectory;
396
+ const eager = [
397
+ external_node_path_default().join(internal, 'mcp-apps/entries') + external_node_path_default().sep
398
+ ];
399
+ config.dev = {
400
+ ...config.dev,
401
+ lazyCompilation: {
402
+ ...current,
403
+ test (module) {
404
+ const resource = module.resource;
405
+ if (resource && eager.some((directory)=>resource.startsWith(directory))) return false;
406
+ return 'function' == typeof test ? test(module) : test instanceof RegExp ? test.test(resource ?? '') : true;
407
+ }
408
+ }
409
+ };
410
+ return config;
411
+ });
412
+ api.onAfterCreateCompiler(({ compiler, environments })=>{
413
+ api.onBeforeRestart(()=>new Promise((resolve, reject)=>compiler.close((error)=>error ? reject(error) : resolve())));
414
+ const compilers = 'compilers' in compiler ? compiler.compilers : [
415
+ compiler
416
+ ];
417
+ for (const child of compilers){
418
+ if (attachedCompilers.has(child)) continue;
419
+ const environment = environments[child.name ?? 'client'];
420
+ if (environment && 'web' === environment.config.output.target) {
421
+ attachedCompilers.add(child);
422
+ child.hooks.invalid.tap('modern-mcp-card-invalidate', ()=>{
423
+ shellDirty = true;
424
+ });
425
+ child.hooks.beforeCompile.tapPromise('modern-mcp-card-build', async ()=>{
426
+ await buildShell?.();
427
+ });
428
+ child.hooks.thisCompilation.tap('modern-mcp-card-html', (compilation)=>{
429
+ compilation.hooks.processAssets.tap({
430
+ name: 'modern-mcp-card-html',
431
+ stage: child.webpack.Compilation.PROCESS_ASSETS_STAGE_REPORT
432
+ }, ()=>{
433
+ for (const [filename, contents] of shellAssets)if (!compilation.getAsset(filename)) compilation.emitAsset(filename, new child.webpack.sources.RawSource(contents));
434
+ });
435
+ });
436
+ child.hooks.afterEmit.tapPromise('modern-mcp-ui-manifest', async (compilation)=>{
437
+ if (compilation.errors.length) return;
438
+ const ctx = api.getAppContext();
439
+ const development = [
440
+ 'dev',
441
+ 'start'
442
+ ].includes(ctx.command);
443
+ const output = external_node_path_default().join(development ? ctx.internalDirectory : ctx.distDirectory, 'mcp-apps');
444
+ let mf;
445
+ if (Object.values(definition.views).some((view)=>'string' != typeof view && void 0 === view.remote && void 0 === definition.remote)) mf = findMfManifest(compilation.getAssets(), options.mf?.manifestFile ?? 'mf-manifest.json');
446
+ const config = api.getNormalizedConfig();
447
+ const manifest = createUiManifest(definition, development ? config.dev.assetPrefix : config.output.assetPrefix, mf, options.csp);
448
+ for (const name of localEntries){
449
+ const htmlPath = environment.htmlPaths[name];
450
+ const html = htmlPath && compilation.getAsset(htmlPath);
451
+ if (!html) throw new Error(`Missing MCP HTML output: ${name}`);
452
+ await writeArtifact(external_node_path_default().join(output, 'ui', `${name}.html`), html.source.source());
453
+ }
454
+ for (const [name, html] of shellHtml)await writeArtifact(external_node_path_default().join(output, 'ui', `${name}.html`), html);
455
+ await writeArtifact(external_node_path_default().join(output, 'ui-manifest.json'), `${JSON.stringify(manifest, null, 2)}\n`);
456
+ });
457
+ }
458
+ }
459
+ });
460
+ }
461
+ });
462
+ const cli = mcpAppsPlugin;
463
+ exports["default"] = __webpack_exports__["default"];
464
+ exports.mcpAppsPlugin = __webpack_exports__.mcpAppsPlugin;
465
+ for(var __rspack_i in __webpack_exports__)if (-1 === [
466
+ "default",
467
+ "mcpAppsPlugin"
468
+ ].indexOf(__rspack_i)) exports[__rspack_i] = __webpack_exports__[__rspack_i];
469
+ Object.defineProperty(exports, '__esModule', {
470
+ value: true
471
+ });
@@ -0,0 +1,79 @@
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
+ default: ()=>server_plugin
41
+ });
42
+ const external_node_path_namespaceObject = require("node:path");
43
+ var external_node_path_default = /*#__PURE__*/ __webpack_require__.n(external_node_path_namespaceObject);
44
+ const server_plugin = (options = {})=>({
45
+ name: '@modern-js/mcp-apps/server-plugin',
46
+ setup (api) {
47
+ api.onPrepare(()=>{
48
+ const { middlewares, distDirectory } = api.getServerContext();
49
+ const uiOptions = options.developmentManifest ? {
50
+ manifest: options.developmentManifest,
51
+ mode: 'development'
52
+ } : {
53
+ manifest: external_node_path_default().join(distDirectory, 'mcp-apps/ui-manifest.json'),
54
+ mode: 'production'
55
+ };
56
+ middlewares.push({
57
+ name: 'mcp-apps-context',
58
+ before: [
59
+ 'bind-bff',
60
+ 'hono-bff-api'
61
+ ],
62
+ handler: async (context, next)=>{
63
+ context.set('locals', {
64
+ ...context.get('locals'),
65
+ modernMcpApps: uiOptions
66
+ });
67
+ await next();
68
+ }
69
+ });
70
+ });
71
+ }
72
+ });
73
+ exports["default"] = __webpack_exports__["default"];
74
+ for(var __rspack_i in __webpack_exports__)if (-1 === [
75
+ "default"
76
+ ].indexOf(__rspack_i)) exports[__rspack_i] = __webpack_exports__[__rspack_i];
77
+ Object.defineProperty(exports, '__esModule', {
78
+ value: true
79
+ });