@modern-js/plugin-mcp-apps 0.0.0-canary-20260924035651

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 ADDED
@@ -0,0 +1,110 @@
1
+ # Modern.js MCP Apps plugin (experimental)
2
+
3
+ 场景化接入与调试见 [MCP Apps 使用指南](../../../docs/guides/mcp-apps-usage.md),包含独立 Hono、可选 MF 和 ngrok。
4
+
5
+ ```ts
6
+ import { appTools, defineConfig } from '@modern-js/app-tools';
7
+ import { bffPlugin } from '@modern-js/plugin-bff';
8
+ import { mcpAppsPlugin } from '@modern-js/plugin-mcp-apps';
9
+
10
+ export default defineConfig({
11
+ bff: { prefix: '/mcp' },
12
+ plugins: [appTools(), bffPlugin(), mcpAppsPlugin()],
13
+ });
14
+ ```
15
+
16
+ The plugin compiles MCP definitions and handlers and binds artifacts during BFF
17
+ initialization. HTTP routing belongs to BFF; add the following API entry and install `@modern-js/plugin-bff` and
18
+ `@modern-js/mcp-apps` in dependencies.
19
+
20
+ ```ts
21
+ // api/lambda/index.ts
22
+ import { mcpApps } from '@modern-js/plugin-mcp-apps/bff';
23
+
24
+ export const { POST, GET, DELETE, PUT, PATCH, OPTIONS } = mcpApps();
25
+ ```
26
+
27
+ For an existing BFF application, keep its prefix and put the route in
28
+ `api/lambda/mcp.ts` (for example `/api/mcp`). Do not register BFF twice.
29
+ Pass `serverInfo` or `onError` to `mcpApps()`. The integration binds the artifact
30
+ using the BFF runtime context; route depth, custom output directories and CJS/ESM
31
+ API modules do not require application-level path resolution. Export the returned
32
+ functions directly so BFF can recognize them; apply authentication in BFF middleware.
33
+ For manually hosted artifacts, the lower-level `@modern-js/mcp-apps/bff` adapter
34
+ remains available with explicit root/entry options.
35
+
36
+ ## Options
37
+
38
+ | Option | Default | Meaning |
39
+ | --- | --- | --- |
40
+ | `config` | `mcp_apps.ts` | Trusted definition path, relative to project root |
41
+ | `tsconfig` | esbuild's nearest tsconfig | Config/handler compilation settings |
42
+ | `alias` | None | Explicit static server import aliases; tsconfig paths also work |
43
+
44
+ `modern dev` bundles the definition and local handlers into the framework internal MCP directory (normally `node_modules/.modern-js/mcp-apps/`). Config, handler and imported source changes trigger debounced
45
+ recompilation. A new module generation is published atomically after successful
46
+ compilation; invalid changes log an error and retain the last working generation.
47
+ Development uses a development React renderer; production uses a production
48
+ renderer, so dev-compiled remote JSX runs against a matching React dispatcher.
49
+ The watcher is disposed on CLI exit/restart. Artifact revisions cancel obsolete
50
+ requests when a new generation is loaded. BFF owns API runtime reloads.
51
+
52
+ `modern build` emits `dist/mcp-apps/`, including compiled handler dependencies,
53
+ the definition entry, and packaged resource HTML. `modern serve` uses those files,
54
+ not source modules. `modern deploy` includes the same artifacts in `.output/`.
55
+ Both CJS and ESM application configurations are supported by the package exports;
56
+ the generated MCP server modules are ESM on Node 20+.
57
+
58
+ Configuration is evaluated at runtime so remote URLs can depend on environment
59
+ variables. Local handler module references must be unchanged between build and
60
+ runtime; changing that set requires rebuilding. The compiler bundles imported
61
+ local code/JSON; separately read files, native addons and other runtime assets
62
+ still need application deployment handling.
63
+
64
+ ## UI and server separation
65
+
66
+ UI continues to use existing MF/Vmok exposes and manifests. The integrated endpoint uses BFF and does not require a UI entry or React rendering. The `mcp-server`
67
+ template uses an `api/` directory with no `src/` to select Modern.js API-only mode.
68
+ For UI integration, use the `mcp-apps` template or an existing federation setup.
69
+
70
+ The underlying core is also usable without app-tools: see
71
+ `examples/mcp-apps-modern` and the [Hono adapter documentation](../../toolkit/mcp-apps/README.md#default-local-view-and-hono). For a local integrated app,
72
+ run `pnpm --filter @examples/mcp-apps-modern dev` in this repository after building
73
+ the core and this plugin.
74
+
75
+ ## HTTP behavior
76
+
77
+ The MCP endpoint is a BFF API route. Register authentication/origin policy before
78
+ BFF dispatch. The adapter rebuilds the HTTP request from the already parsed BFF
79
+ input and returns the SDK Response directly, including notification status 202.
80
+ Handler `context.context` is the request's Hono Context, allowing access to
81
+ middleware-provided identity; contexts are isolated between concurrent requests.
82
+ The transport remains stateless and POST-only; GET/DELETE return 405.
83
+
84
+ No server plugin `onClose` hook is assumed. Per-request transports are released
85
+ by the core, request disconnects abort handlers, and compiler watchers belong to
86
+ the CLI lifecycle. Custom `server/modern.server.ts` middleware and ordinary pages
87
+ remain independent of MCP registration.
88
+
89
+ ## Tests
90
+
91
+ ```sh
92
+ pnpm --filter @modern-js/mcp-apps build
93
+ pnpm --filter @modern-js/plugin-mcp-apps build
94
+ pnpm --filter @modern-js/plugin-mcp-apps test
95
+ pnpm --filter tests test:framework integration/mcp-apps/tests/index.test.ts --retry=0
96
+ ```
97
+
98
+ ## Local views and Hono
99
+
100
+ The default template uses local `view.module` components, compiled into standalone
101
+ HTML in `dist/mcp-apps/`. MF is selected explicitly with `--template mcp-apps --mf`.
102
+ The BFF runtime uses `@modern-js/mcp-apps/bff` internally; standalone Hono uses
103
+ `@modern-js/mcp-apps/hono`. Both share the artifact loader and SDK transport. UI source changes participate in MCP development recompilation.
104
+
105
+ The legacy `server-plugin` export remains available for explicit Web Server
106
+ integration, but `mcpAppsPlugin()` no longer installs it or registers HTTP routes.
107
+
108
+ Include `ts-node` (`^10.9.2`) in devDependencies for BFF TypeScript loading and
109
+ API module hot reload on Node 20+. For ESM projects, use `"type": "module"` and
110
+ TypeScript `module: "esnext"`, `moduleResolution: "bundler"` together.
@@ -0,0 +1,92 @@
1
+ "use strict";
2
+ var __webpack_require__ = {};
3
+ (()=>{
4
+ __webpack_require__.d = (exports1, getters, values)=>{
5
+ var define = (defs, kind)=>{
6
+ for(var key in defs)if (__webpack_require__.o(defs, key) && !__webpack_require__.o(exports1, key)) Object.defineProperty(exports1, key, {
7
+ enumerable: true,
8
+ [kind]: defs[key]
9
+ });
10
+ };
11
+ define(getters, "get");
12
+ define(values, "value");
13
+ };
14
+ })();
15
+ (()=>{
16
+ __webpack_require__.o = (obj, prop)=>Object.prototype.hasOwnProperty.call(obj, prop);
17
+ })();
18
+ (()=>{
19
+ __webpack_require__.r = (exports1)=>{
20
+ if ("u" > typeof Symbol && Symbol.toStringTag) Object.defineProperty(exports1, Symbol.toStringTag, {
21
+ value: 'Module'
22
+ });
23
+ Object.defineProperty(exports1, '__esModule', {
24
+ value: true
25
+ });
26
+ };
27
+ })();
28
+ var __webpack_exports__ = {};
29
+ __webpack_require__.r(__webpack_exports__);
30
+ __webpack_require__.d(__webpack_exports__, {
31
+ default: ()=>mcpBffRuntime
32
+ });
33
+ const bff_namespaceObject = require("@modern-js/mcp-apps/bff");
34
+ const server_core_namespaceObject = require("@modern-js/server-core");
35
+ const MCP_BFF_ENDPOINT = Symbol.for('@modern-js/plugin-mcp-apps/bff');
36
+ function mcpBffRuntime(options) {
37
+ return {
38
+ name: '@modern-js/plugin-mcp-apps/bff-runtime',
39
+ pre: [
40
+ '@modern-js/plugin-bff'
41
+ ],
42
+ required: [
43
+ '@modern-js/plugin-bff'
44
+ ],
45
+ setup (api) {
46
+ const bind = ()=>{
47
+ const ctx = api.getServerContext();
48
+ const root = options.development ? ctx.appDirectory : ctx.distDirectory;
49
+ if (!root) throw new Error('MCP Apps requires a BFF application directory');
50
+ const handlers = new Map();
51
+ const infos = ctx.apiHandlerInfos ?? [];
52
+ const apiHandlerInfos = infos.map((info)=>{
53
+ const endpointOptions = Reflect.get(info.handler, MCP_BFF_ENDPOINT);
54
+ if (!endpointOptions) return info;
55
+ let handler = handlers.get(info.handler);
56
+ if (!handler) {
57
+ const handle = (0, bff_namespaceObject.createMcpBffHandler)({
58
+ ...endpointOptions,
59
+ root,
60
+ development: options.development,
61
+ entry: options.development ? options.devEntry : options.entry
62
+ });
63
+ handler = (input)=>handle(input, (0, server_core_namespaceObject.useHonoContext)());
64
+ handlers.set(info.handler, handler);
65
+ }
66
+ return {
67
+ ...info,
68
+ handler
69
+ };
70
+ });
71
+ api.updateServerContext({
72
+ ...ctx,
73
+ apiHandlerInfos
74
+ });
75
+ };
76
+ api.prepareApiServer((input, next)=>{
77
+ bind();
78
+ next(input);
79
+ });
80
+ api.onReset(({ event })=>{
81
+ if ('file-change' === event.type) bind();
82
+ });
83
+ }
84
+ };
85
+ }
86
+ exports["default"] = __webpack_exports__["default"];
87
+ for(var __rspack_i in __webpack_exports__)if (-1 === [
88
+ "default"
89
+ ].indexOf(__rspack_i)) exports[__rspack_i] = __webpack_exports__[__rspack_i];
90
+ Object.defineProperty(exports, '__esModule', {
91
+ value: true
92
+ });
@@ -0,0 +1,56 @@
1
+ "use strict";
2
+ var __webpack_require__ = {};
3
+ (()=>{
4
+ __webpack_require__.d = (exports1, getters, values)=>{
5
+ var define = (defs, kind)=>{
6
+ for(var key in defs)if (__webpack_require__.o(defs, key) && !__webpack_require__.o(exports1, key)) Object.defineProperty(exports1, key, {
7
+ enumerable: true,
8
+ [kind]: defs[key]
9
+ });
10
+ };
11
+ define(getters, "get");
12
+ define(values, "value");
13
+ };
14
+ })();
15
+ (()=>{
16
+ __webpack_require__.o = (obj, prop)=>Object.prototype.hasOwnProperty.call(obj, prop);
17
+ })();
18
+ (()=>{
19
+ __webpack_require__.r = (exports1)=>{
20
+ if ("u" > typeof Symbol && Symbol.toStringTag) Object.defineProperty(exports1, Symbol.toStringTag, {
21
+ value: 'Module'
22
+ });
23
+ Object.defineProperty(exports1, '__esModule', {
24
+ value: true
25
+ });
26
+ };
27
+ })();
28
+ var __webpack_exports__ = {};
29
+ __webpack_require__.r(__webpack_exports__);
30
+ __webpack_require__.d(__webpack_exports__, {
31
+ mcpApps: ()=>mcpApps
32
+ });
33
+ const MCP_BFF_ENDPOINT = Symbol.for('@modern-js/plugin-mcp-apps/bff');
34
+ function mcpApps(options = {}) {
35
+ const endpoint = (_input)=>{
36
+ throw new Error('MCP endpoint is not initialized. Enable bffPlugin() and mcpAppsPlugin(), and export the handlers returned by mcpApps() from an API route.');
37
+ };
38
+ Object.defineProperty(endpoint, MCP_BFF_ENDPOINT, {
39
+ value: options
40
+ });
41
+ return {
42
+ POST: endpoint,
43
+ GET: endpoint,
44
+ DELETE: endpoint,
45
+ PUT: endpoint,
46
+ PATCH: endpoint,
47
+ OPTIONS: endpoint
48
+ };
49
+ }
50
+ exports.mcpApps = __webpack_exports__.mcpApps;
51
+ for(var __rspack_i in __webpack_exports__)if (-1 === [
52
+ "mcpApps"
53
+ ].indexOf(__rspack_i)) exports[__rspack_i] = __webpack_exports__[__rspack_i];
54
+ Object.defineProperty(exports, '__esModule', {
55
+ value: true
56
+ });
@@ -0,0 +1,154 @@
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: ()=>cli,
41
+ mcpAppsPlugin: ()=>mcpAppsPlugin
42
+ });
43
+ const external_node_path_namespaceObject = require("node:path");
44
+ var external_node_path_default = /*#__PURE__*/ __webpack_require__.n(external_node_path_namespaceObject);
45
+ const build_namespaceObject = require("@modern-js/mcp-apps/build");
46
+ const utils_namespaceObject = require("@modern-js/utils");
47
+ const mcpAppsPlugin = (options = {})=>({
48
+ name: '@modern-js/plugin-mcp-apps',
49
+ required: [
50
+ '@modern-js/plugin-bff'
51
+ ],
52
+ setup (api) {
53
+ let dependencies = new Set();
54
+ let watcher;
55
+ let timer;
56
+ let pending = Promise.resolve();
57
+ let closed = false;
58
+ let rebuildFailed = false;
59
+ const paths = ()=>{
60
+ const ctx = api.getAppContext();
61
+ return {
62
+ root: ctx.appDirectory,
63
+ source: external_node_path_default().resolve(ctx.appDirectory, options.config ?? 'mcp_apps.ts'),
64
+ devOut: external_node_path_default().join(ctx.internalDirectory, 'mcp-apps'),
65
+ prodOut: external_node_path_default().join(ctx.distDirectory, 'mcp-apps')
66
+ };
67
+ };
68
+ const compile = async (dev)=>{
69
+ const { root, source, devOut, prodOut } = paths();
70
+ const result = await (0, build_namespaceObject.compileMcpApps)({
71
+ configPath: source,
72
+ outDir: dev ? devOut : prodOut,
73
+ tsconfig: options.tsconfig ? external_node_path_default().resolve(root, options.tsconfig) : void 0,
74
+ alias: options.alias,
75
+ development: dev
76
+ });
77
+ dependencies = new Set(result.dependencies.map((file)=>external_node_path_default().resolve(file)));
78
+ rebuildFailed = false;
79
+ if (dev) watcher?.add([
80
+ ...dependencies
81
+ ]);
82
+ if (dev) utils_namespaceObject.logger.info('[MCP Apps] definition and handlers ready');
83
+ };
84
+ const close = async ()=>{
85
+ closed = true;
86
+ clearTimeout(timer);
87
+ await watcher?.close();
88
+ await pending;
89
+ };
90
+ api.onBeforeExit(close);
91
+ api.onBeforeRestart(close);
92
+ api.onBeforeDev(async ()=>{
93
+ closed = false;
94
+ await compile(true);
95
+ const { root, devOut, prodOut } = paths();
96
+ watcher = utils_namespaceObject.chokidar.watch([
97
+ root,
98
+ ...dependencies
99
+ ], {
100
+ ignoreInitial: true,
101
+ ignored: [
102
+ /[\\/]node_modules(?:[\\/]|$)/,
103
+ /[\\/]\.git(?:[\\/]|$)/,
104
+ external_node_path_default().join(root, '.output'),
105
+ devOut,
106
+ prodOut,
107
+ external_node_path_default().dirname(prodOut)
108
+ ]
109
+ });
110
+ watcher.on('error', (error)=>utils_namespaceObject.logger.error('[MCP Apps] watcher failed', error));
111
+ watcher.on('all', (event, file)=>{
112
+ if (closed || !dependencies.has(external_node_path_default().resolve(file)) && !(rebuildFailed && 'add' === event)) return;
113
+ if (!dependencies.has(external_node_path_default().resolve(file)) && !/\.(?:[cm]?[jt]sx?|json)$/.test(file)) return;
114
+ clearTimeout(timer);
115
+ timer = setTimeout(()=>{
116
+ pending = pending.then(async ()=>{
117
+ if (!closed) await compile(true);
118
+ }).catch((error)=>{
119
+ rebuildFailed = true;
120
+ utils_namespaceObject.logger.error('[MCP Apps] rebuild failed; keeping last successful build', error);
121
+ });
122
+ }, 60);
123
+ });
124
+ });
125
+ api.onAfterBuild(()=>compile(false));
126
+ api._internalServerPlugins(({ plugins })=>{
127
+ const { root, devOut } = paths();
128
+ plugins.push({
129
+ name: '@modern-js/plugin-mcp-apps/bff-runtime',
130
+ options: {
131
+ development: [
132
+ 'dev',
133
+ 'start'
134
+ ].includes(api.getAppContext().command),
135
+ devEntry: external_node_path_default().relative(root, external_node_path_default().join(devOut, 'mcp_apps.mjs')).split(external_node_path_default().sep).join('/'),
136
+ entry: 'mcp-apps/mcp_apps.mjs'
137
+ }
138
+ });
139
+ return {
140
+ plugins
141
+ };
142
+ });
143
+ }
144
+ });
145
+ const cli = mcpAppsPlugin;
146
+ exports["default"] = __webpack_exports__["default"];
147
+ exports.mcpAppsPlugin = __webpack_exports__.mcpAppsPlugin;
148
+ for(var __rspack_i in __webpack_exports__)if (-1 === [
149
+ "default",
150
+ "mcpAppsPlugin"
151
+ ].indexOf(__rspack_i)) exports[__rspack_i] = __webpack_exports__[__rspack_i];
152
+ Object.defineProperty(exports, '__esModule', {
153
+ value: true
154
+ });
@@ -0,0 +1,96 @@
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
+ createArtifactHandler: ()=>server_namespaceObject.createArtifactHandler,
41
+ default: ()=>mcpAppsServerPlugin
42
+ });
43
+ const external_node_path_namespaceObject = require("node:path");
44
+ var external_node_path_default = /*#__PURE__*/ __webpack_require__.n(external_node_path_namespaceObject);
45
+ const hono_namespaceObject = require("@modern-js/mcp-apps/hono");
46
+ const server_namespaceObject = require("@modern-js/mcp-apps/server");
47
+ function endpointPath(endpoint = '/mcp') {
48
+ if (!/^\/(?:[a-zA-Z0-9_.~-]+\/?)+$/.test(endpoint) || endpoint.endsWith('/') || endpoint.split('/').some((part)=>'.' === part || '..' === part)) throw new Error('MCP endpoint must be an exact absolute path without wildcards, parameters or trailing slash');
49
+ return endpoint;
50
+ }
51
+ function mcpAppsServerPlugin(options = {}) {
52
+ const endpoint = endpointPath(options.endpoint);
53
+ return {
54
+ name: '@modern-js/plugin-mcp-apps/server-plugin',
55
+ setup (api) {
56
+ let handler;
57
+ api.onPrepare(()=>{
58
+ const ctx = api.getServerContext();
59
+ const development = options.development ?? 'development' === process.env.NODE_ENV;
60
+ const base = development ? ctx.appDirectory : ctx.distDirectory;
61
+ if (!base) throw new Error('MCP Apps requires a server application/artifact directory');
62
+ const entry = external_node_path_default().resolve(base, development && options.devEntry ? options.devEntry : options.entry ?? 'mcp-apps/mcp_apps.mjs');
63
+ handler = (0, server_namespaceObject.createArtifactHandler)(entry, {
64
+ development,
65
+ serverInfo: options.serverInfo
66
+ });
67
+ ctx.middlewares.push({
68
+ name: 'mcp-apps',
69
+ path: endpoint,
70
+ method: 'all',
71
+ order: 'post',
72
+ before: [
73
+ 'bind-bff',
74
+ 'render'
75
+ ],
76
+ handler: (0, hono_namespaceObject.mcpApps)({
77
+ handler,
78
+ endpoint
79
+ })
80
+ });
81
+ });
82
+ api.onReset(({ event })=>{
83
+ if ('file-change' === event.type) handler?.reset();
84
+ });
85
+ }
86
+ };
87
+ }
88
+ exports.createArtifactHandler = __webpack_exports__.createArtifactHandler;
89
+ exports["default"] = __webpack_exports__["default"];
90
+ for(var __rspack_i in __webpack_exports__)if (-1 === [
91
+ "createArtifactHandler",
92
+ "default"
93
+ ].indexOf(__rspack_i)) exports[__rspack_i] = __webpack_exports__[__rspack_i];
94
+ Object.defineProperty(exports, '__esModule', {
95
+ value: true
96
+ });
@@ -0,0 +1,54 @@
1
+ import { createMcpBffHandler } from "@modern-js/mcp-apps/bff";
2
+ import { useHonoContext } from "@modern-js/server-core";
3
+ import { MCP_BFF_ENDPOINT } from "./z~0.mjs";
4
+ function mcpBffRuntime(options) {
5
+ return {
6
+ name: '@modern-js/plugin-mcp-apps/bff-runtime',
7
+ pre: [
8
+ '@modern-js/plugin-bff'
9
+ ],
10
+ required: [
11
+ '@modern-js/plugin-bff'
12
+ ],
13
+ setup (api) {
14
+ const bind = ()=>{
15
+ const ctx = api.getServerContext();
16
+ const root = options.development ? ctx.appDirectory : ctx.distDirectory;
17
+ if (!root) throw new Error('MCP Apps requires a BFF application directory');
18
+ const handlers = new Map();
19
+ const infos = ctx.apiHandlerInfos ?? [];
20
+ const apiHandlerInfos = infos.map((info)=>{
21
+ const endpointOptions = Reflect.get(info.handler, MCP_BFF_ENDPOINT);
22
+ if (!endpointOptions) return info;
23
+ let handler = handlers.get(info.handler);
24
+ if (!handler) {
25
+ const handle = createMcpBffHandler({
26
+ ...endpointOptions,
27
+ root,
28
+ development: options.development,
29
+ entry: options.development ? options.devEntry : options.entry
30
+ });
31
+ handler = (input)=>handle(input, useHonoContext());
32
+ handlers.set(info.handler, handler);
33
+ }
34
+ return {
35
+ ...info,
36
+ handler
37
+ };
38
+ });
39
+ api.updateServerContext({
40
+ ...ctx,
41
+ apiHandlerInfos
42
+ });
43
+ };
44
+ api.prepareApiServer((input, next)=>{
45
+ bind();
46
+ next(input);
47
+ });
48
+ api.onReset(({ event })=>{
49
+ if ('file-change' === event.type) bind();
50
+ });
51
+ }
52
+ };
53
+ }
54
+ export default mcpBffRuntime;
@@ -0,0 +1,18 @@
1
+ import { MCP_BFF_ENDPOINT } from "./z~0.mjs";
2
+ function mcpApps(options = {}) {
3
+ const endpoint = (_input)=>{
4
+ throw new Error('MCP endpoint is not initialized. Enable bffPlugin() and mcpAppsPlugin(), and export the handlers returned by mcpApps() from an API route.');
5
+ };
6
+ Object.defineProperty(endpoint, MCP_BFF_ENDPOINT, {
7
+ value: options
8
+ });
9
+ return {
10
+ POST: endpoint,
11
+ GET: endpoint,
12
+ DELETE: endpoint,
13
+ PUT: endpoint,
14
+ PATCH: endpoint,
15
+ OPTIONS: endpoint
16
+ };
17
+ }
18
+ export { mcpApps };
@@ -0,0 +1,104 @@
1
+ import node_path from "node:path";
2
+ import { compileMcpApps } from "@modern-js/mcp-apps/build";
3
+ import { chokidar, logger } from "@modern-js/utils";
4
+ const mcpAppsPlugin = (options = {})=>({
5
+ name: '@modern-js/plugin-mcp-apps',
6
+ required: [
7
+ '@modern-js/plugin-bff'
8
+ ],
9
+ setup (api) {
10
+ let dependencies = new Set();
11
+ let watcher;
12
+ let timer;
13
+ let pending = Promise.resolve();
14
+ let closed = false;
15
+ let rebuildFailed = false;
16
+ const paths = ()=>{
17
+ const ctx = api.getAppContext();
18
+ return {
19
+ root: ctx.appDirectory,
20
+ source: node_path.resolve(ctx.appDirectory, options.config ?? 'mcp_apps.ts'),
21
+ devOut: node_path.join(ctx.internalDirectory, 'mcp-apps'),
22
+ prodOut: node_path.join(ctx.distDirectory, 'mcp-apps')
23
+ };
24
+ };
25
+ const compile = async (dev)=>{
26
+ const { root, source, devOut, prodOut } = paths();
27
+ const result = await compileMcpApps({
28
+ configPath: source,
29
+ outDir: dev ? devOut : prodOut,
30
+ tsconfig: options.tsconfig ? node_path.resolve(root, options.tsconfig) : void 0,
31
+ alias: options.alias,
32
+ development: dev
33
+ });
34
+ dependencies = new Set(result.dependencies.map((file)=>node_path.resolve(file)));
35
+ rebuildFailed = false;
36
+ if (dev) watcher?.add([
37
+ ...dependencies
38
+ ]);
39
+ if (dev) logger.info('[MCP Apps] definition and handlers ready');
40
+ };
41
+ const close = async ()=>{
42
+ closed = true;
43
+ clearTimeout(timer);
44
+ await watcher?.close();
45
+ await pending;
46
+ };
47
+ api.onBeforeExit(close);
48
+ api.onBeforeRestart(close);
49
+ api.onBeforeDev(async ()=>{
50
+ closed = false;
51
+ await compile(true);
52
+ const { root, devOut, prodOut } = paths();
53
+ watcher = chokidar.watch([
54
+ root,
55
+ ...dependencies
56
+ ], {
57
+ ignoreInitial: true,
58
+ ignored: [
59
+ /[\\/]node_modules(?:[\\/]|$)/,
60
+ /[\\/]\.git(?:[\\/]|$)/,
61
+ node_path.join(root, '.output'),
62
+ devOut,
63
+ prodOut,
64
+ node_path.dirname(prodOut)
65
+ ]
66
+ });
67
+ watcher.on('error', (error)=>logger.error('[MCP Apps] watcher failed', error));
68
+ watcher.on('all', (event, file)=>{
69
+ if (closed || !dependencies.has(node_path.resolve(file)) && !(rebuildFailed && 'add' === event)) return;
70
+ if (!dependencies.has(node_path.resolve(file)) && !/\.(?:[cm]?[jt]sx?|json)$/.test(file)) return;
71
+ clearTimeout(timer);
72
+ timer = setTimeout(()=>{
73
+ pending = pending.then(async ()=>{
74
+ if (!closed) await compile(true);
75
+ }).catch((error)=>{
76
+ rebuildFailed = true;
77
+ logger.error('[MCP Apps] rebuild failed; keeping last successful build', error);
78
+ });
79
+ }, 60);
80
+ });
81
+ });
82
+ api.onAfterBuild(()=>compile(false));
83
+ api._internalServerPlugins(({ plugins })=>{
84
+ const { root, devOut } = paths();
85
+ plugins.push({
86
+ name: '@modern-js/plugin-mcp-apps/bff-runtime',
87
+ options: {
88
+ development: [
89
+ 'dev',
90
+ 'start'
91
+ ].includes(api.getAppContext().command),
92
+ devEntry: node_path.relative(root, node_path.join(devOut, 'mcp_apps.mjs')).split(node_path.sep).join('/'),
93
+ entry: 'mcp-apps/mcp_apps.mjs'
94
+ }
95
+ });
96
+ return {
97
+ plugins
98
+ };
99
+ });
100
+ }
101
+ });
102
+ const cli = mcpAppsPlugin;
103
+ export default cli;
104
+ export { mcpAppsPlugin };
@@ -0,0 +1,46 @@
1
+ import node_path from "node:path";
2
+ import { mcpApps } from "@modern-js/mcp-apps/hono";
3
+ import { createArtifactHandler } from "@modern-js/mcp-apps/server";
4
+ function endpointPath(endpoint = '/mcp') {
5
+ if (!/^\/(?:[a-zA-Z0-9_.~-]+\/?)+$/.test(endpoint) || endpoint.endsWith('/') || endpoint.split('/').some((part)=>'.' === part || '..' === part)) throw new Error('MCP endpoint must be an exact absolute path without wildcards, parameters or trailing slash');
6
+ return endpoint;
7
+ }
8
+ function mcpAppsServerPlugin(options = {}) {
9
+ const endpoint = endpointPath(options.endpoint);
10
+ return {
11
+ name: '@modern-js/plugin-mcp-apps/server-plugin',
12
+ setup (api) {
13
+ let handler;
14
+ api.onPrepare(()=>{
15
+ const ctx = api.getServerContext();
16
+ const development = options.development ?? 'development' === process.env.NODE_ENV;
17
+ const base = development ? ctx.appDirectory : ctx.distDirectory;
18
+ if (!base) throw new Error('MCP Apps requires a server application/artifact directory');
19
+ const entry = node_path.resolve(base, development && options.devEntry ? options.devEntry : options.entry ?? 'mcp-apps/mcp_apps.mjs');
20
+ handler = createArtifactHandler(entry, {
21
+ development,
22
+ serverInfo: options.serverInfo
23
+ });
24
+ ctx.middlewares.push({
25
+ name: 'mcp-apps',
26
+ path: endpoint,
27
+ method: 'all',
28
+ order: 'post',
29
+ before: [
30
+ 'bind-bff',
31
+ 'render'
32
+ ],
33
+ handler: mcpApps({
34
+ handler,
35
+ endpoint
36
+ })
37
+ });
38
+ });
39
+ api.onReset(({ event })=>{
40
+ if ('file-change' === event.type) handler?.reset();
41
+ });
42
+ }
43
+ };
44
+ }
45
+ export default mcpAppsServerPlugin;
46
+ export { createArtifactHandler };
@@ -0,0 +1,2 @@
1
+ const MCP_BFF_ENDPOINT = Symbol.for('@modern-js/plugin-mcp-apps/bff');
2
+ export { MCP_BFF_ENDPOINT };
@@ -0,0 +1,4 @@
1
+ import { type ServerPlugin } from '@modern-js/server-core';
2
+ import { type McpAppsBffRuntimeOptions } from './shared.js';
3
+ /** Bind artifacts to BFF-discovered functions. Never registers HTTP middleware. */
4
+ export default function mcpBffRuntime(options: McpAppsBffRuntimeOptions): ServerPlugin;
@@ -0,0 +1,23 @@
1
+ import type { McpBffOptions } from '@modern-js/mcp-apps/bff';
2
+ export type McpEndpointOptions = Pick<McpBffOptions, 'serverInfo' | 'onError'>;
3
+ /** Declare a BFF endpoint. Its compiled artifact is bound by the BFF lifecycle. */
4
+ export declare function mcpApps(options?: McpEndpointOptions): {
5
+ POST: (_input: {
6
+ data?: unknown;
7
+ }) => Promise<Response>;
8
+ GET: (_input: {
9
+ data?: unknown;
10
+ }) => Promise<Response>;
11
+ DELETE: (_input: {
12
+ data?: unknown;
13
+ }) => Promise<Response>;
14
+ PUT: (_input: {
15
+ data?: unknown;
16
+ }) => Promise<Response>;
17
+ PATCH: (_input: {
18
+ data?: unknown;
19
+ }) => Promise<Response>;
20
+ OPTIONS: (_input: {
21
+ data?: unknown;
22
+ }) => Promise<Response>;
23
+ };
@@ -0,0 +1,5 @@
1
+ import type { AppTools, CliPlugin } from '@modern-js/app-tools';
2
+ import type { McpAppsPluginOptions } from './options.js';
3
+ export type { McpAppsPluginOptions } from './options.js';
4
+ export declare const mcpAppsPlugin: (options?: McpAppsPluginOptions) => CliPlugin<AppTools>;
5
+ export default mcpAppsPlugin;
@@ -0,0 +1,21 @@
1
+ export interface McpAppsPluginOptions {
2
+ /** Trusted application definition, relative to the project root. */
3
+ config?: string;
4
+ /** Optional tsconfig for config/handler compilation. */
5
+ tsconfig?: string;
6
+ /** Static server-only import aliases in addition to tsconfig paths. */
7
+ alias?: Record<string, string>;
8
+ }
9
+ export interface McpAppsServerOptions {
10
+ development?: boolean;
11
+ endpoint?: string;
12
+ /** Relative to appDirectory, only used in development. */
13
+ devEntry?: string;
14
+ /** Relative to distDirectory; serialized into the production launcher. */
15
+ entry?: string;
16
+ serverInfo?: {
17
+ name: string;
18
+ version: string;
19
+ };
20
+ }
21
+ export declare function endpointPath(endpoint?: string): string;
@@ -0,0 +1,5 @@
1
+ import type { ServerPlugin } from '@modern-js/server-core';
2
+ import { type McpAppsServerOptions } from './options.js';
3
+ export type { McpAppsServerOptions } from './options.js';
4
+ export { createArtifactHandler } from '@modern-js/mcp-apps/server';
5
+ export default function mcpAppsServerPlugin(options?: McpAppsServerOptions): ServerPlugin;
@@ -0,0 +1,8 @@
1
+ export declare const MCP_BFF_ENDPOINT: unique symbol;
2
+ export interface McpAppsBffRuntimeOptions {
3
+ development: boolean;
4
+ /** Relative to the project root. */
5
+ devEntry: string;
6
+ /** Relative to the compiled output root. */
7
+ entry: string;
8
+ }
package/package.json ADDED
@@ -0,0 +1,79 @@
1
+ {
2
+ "name": "@modern-js/plugin-mcp-apps",
3
+ "version": "0.0.0-canary-20260924035651",
4
+ "description": "Modern.js integration for independently deployable MCP Apps",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "https://github.com/web-infra-dev/modern.js",
9
+ "directory": "packages/cli/plugin-mcp-apps"
10
+ },
11
+ "files": [
12
+ "dist",
13
+ "README.md"
14
+ ],
15
+ "sideEffects": false,
16
+ "engines": {
17
+ "node": ">=20"
18
+ },
19
+ "exports": {
20
+ ".": {
21
+ "types": "./dist/types/cli.d.ts",
22
+ "import": "./dist/esm-node/cli.mjs",
23
+ "require": "./dist/cjs/cli.cjs"
24
+ },
25
+ "./server-plugin": {
26
+ "types": "./dist/types/server.d.ts",
27
+ "import": "./dist/esm-node/server.mjs",
28
+ "require": "./dist/cjs/server.cjs"
29
+ },
30
+ "./bff": {
31
+ "types": "./dist/types/bff.d.ts",
32
+ "import": "./dist/esm-node/bff.mjs",
33
+ "require": "./dist/cjs/bff.cjs"
34
+ },
35
+ "./bff-runtime": {
36
+ "types": "./dist/types/bff-runtime.d.ts",
37
+ "import": "./dist/esm-node/bff-runtime.mjs",
38
+ "require": "./dist/cjs/bff-runtime.cjs"
39
+ }
40
+ },
41
+ "dependencies": {
42
+ "@modern-js/mcp-apps": "0.0.0-canary-20260924035651",
43
+ "@modern-js/utils": "0.0.0-canary-20260924035651",
44
+ "@modern-js/server-core": "0.0.0-canary-20260924035651"
45
+ },
46
+ "devDependencies": {
47
+ "@rslib/core": "1.0.1",
48
+ "@types/node": "^20",
49
+ "typescript": "^5",
50
+ "@scripts/rstest-config": "0.0.0-canary-20260924035651",
51
+ "@modern-js/app-tools": "0.0.0-canary-20260924035651"
52
+ },
53
+ "publishConfig": {
54
+ "access": "public",
55
+ "registry": "https://registry.npmjs.org/"
56
+ },
57
+ "typesVersions": {
58
+ "*": {
59
+ ".": [
60
+ "dist/types/cli.d.ts"
61
+ ],
62
+ "server-plugin": [
63
+ "dist/types/server.d.ts"
64
+ ],
65
+ "bff": [
66
+ "dist/types/bff.d.ts"
67
+ ],
68
+ "bff-runtime": [
69
+ "dist/types/bff-runtime.d.ts"
70
+ ]
71
+ }
72
+ },
73
+ "types": "./dist/types/cli.d.ts",
74
+ "scripts": {
75
+ "build": "rslib build",
76
+ "test": "rstest",
77
+ "typecheck": "tsc --noEmit"
78
+ }
79
+ }