@lark-apaas/coding-preset-vite-react 1.0.23 → 1.0.24-alpha.20260916155508
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/lib/index.d.ts.map +1 -1
- package/lib/index.js +7 -0
- package/lib/index.js.map +1 -1
- package/lib/plugins/dev-proxy.d.ts.map +1 -1
- package/lib/plugins/dev-proxy.js +6 -0
- package/lib/plugins/dev-proxy.js.map +1 -1
- package/lib/plugins/html-output.d.ts +1 -25
- package/lib/plugins/html-output.d.ts.map +1 -1
- package/lib/plugins/html-output.js +13 -95
- package/lib/plugins/html-output.js.map +1 -1
- package/lib/plugins/mcp-skill.d.ts +3 -0
- package/lib/plugins/mcp-skill.d.ts.map +1 -0
- package/lib/plugins/mcp-skill.js +194 -0
- package/lib/plugins/mcp-skill.js.map +1 -0
- package/lib/plugins/mcp-ui-boundary.d.ts +29 -0
- package/lib/plugins/mcp-ui-boundary.d.ts.map +1 -0
- package/lib/plugins/mcp-ui-boundary.js +232 -0
- package/lib/plugins/mcp-ui-boundary.js.map +1 -0
- package/lib/plugins/mcp-ui.d.ts +70 -0
- package/lib/plugins/mcp-ui.d.ts.map +1 -0
- package/lib/plugins/mcp-ui.js +292 -0
- package/lib/plugins/mcp-ui.js.map +1 -0
- package/package.json +3 -1
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { Plugin } from 'vite';
|
|
2
|
+
/** 构建约束,不替代宿主 iframe 的权限和运行时隔离。 */
|
|
3
|
+
export declare function createMcpUiDependencyCheck(rootDir: string, uiDir: string): (id: string) => void;
|
|
4
|
+
export declare function mcpUiBoundary(rootDir: string, entryDir: string): {
|
|
5
|
+
plugin: Plugin<any>;
|
|
6
|
+
postcss: {
|
|
7
|
+
postcssPlugin: string;
|
|
8
|
+
Once(root: {
|
|
9
|
+
walkDecls(callback: (decl: {
|
|
10
|
+
value: string;
|
|
11
|
+
source?: {
|
|
12
|
+
input: {
|
|
13
|
+
file?: string;
|
|
14
|
+
};
|
|
15
|
+
};
|
|
16
|
+
}) => void): void;
|
|
17
|
+
}): void;
|
|
18
|
+
OnceExit(_root: unknown, { result, }: {
|
|
19
|
+
result: {
|
|
20
|
+
messages: Array<{
|
|
21
|
+
type: string;
|
|
22
|
+
file?: unknown;
|
|
23
|
+
}>;
|
|
24
|
+
};
|
|
25
|
+
}): void;
|
|
26
|
+
};
|
|
27
|
+
check: (id: string) => void;
|
|
28
|
+
};
|
|
29
|
+
//# sourceMappingURL=mcp-ui-boundary.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcp-ui-boundary.d.ts","sourceRoot":"","sources":["../../src/plugins/mcp-ui-boundary.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AAEnC,mCAAmC;AACnC,wBAAgB,0BAA0B,CACxC,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,MAAM,GACZ,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAmCtB;AAED,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM;;;;mBAmJhD;YACT,SAAS,CACP,QAAQ,EAAE,CAAC,IAAI,EAAE;gBACf,KAAK,EAAE,MAAM,CAAC;gBACd,MAAM,CAAC,EAAE;oBAAE,KAAK,EAAE;wBAAE,IAAI,CAAC,EAAE,MAAM,CAAA;qBAAE,CAAA;iBAAE,CAAC;aACvC,KAAK,IAAI,GACT,IAAI,CAAC;SACT;wBAOQ,OAAO,eAGX;YAAE,MAAM,EAAE;gBAAE,QAAQ,EAAE,KAAK,CAAC;oBAAE,IAAI,EAAE,MAAM,CAAC;oBAAC,IAAI,CAAC,EAAE,OAAO,CAAA;iBAAE,CAAC,CAAA;aAAE,CAAA;SAAE;;gBAzMlE,MAAM,KAAK,IAAI;EAiNtB"}
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.createMcpUiDependencyCheck = createMcpUiDependencyCheck;
|
|
40
|
+
exports.mcpUiBoundary = mcpUiBoundary;
|
|
41
|
+
const fs = __importStar(require("node:fs"));
|
|
42
|
+
const path = __importStar(require("node:path"));
|
|
43
|
+
const node_module_1 = require("node:module");
|
|
44
|
+
const parse5_1 = require("parse5");
|
|
45
|
+
const parser_1 = require("@babel/parser");
|
|
46
|
+
const traverse_1 = __importDefault(require("@babel/traverse"));
|
|
47
|
+
const postcss_value_parser_1 = __importDefault(require("postcss-value-parser"));
|
|
48
|
+
/** 构建约束,不替代宿主 iframe 的权限和运行时隔离。 */
|
|
49
|
+
function createMcpUiDependencyCheck(rootDir, uiDir) {
|
|
50
|
+
const canonical = (file) => fs.existsSync(file) ? fs.realpathSync(file) : path.resolve(file);
|
|
51
|
+
const roots = [canonical(uiDir), canonical(path.join(rootDir, 'shared'))];
|
|
52
|
+
const builtins = new Set(node_module_1.builtinModules.flatMap(name => [name, `node:${name}`]));
|
|
53
|
+
return id => {
|
|
54
|
+
if (builtins.has(id) ||
|
|
55
|
+
id.startsWith('node:') ||
|
|
56
|
+
id.includes('__vite-browser-external')) {
|
|
57
|
+
throw new Error(`MCP UI 不能引用 Node.js 模块:${id}`);
|
|
58
|
+
}
|
|
59
|
+
const file = id.split(/[?#]/, 1)[0];
|
|
60
|
+
if (!path.isAbsolute(file))
|
|
61
|
+
return; // 构建器虚拟模块
|
|
62
|
+
const resolved = canonical(file);
|
|
63
|
+
if (resolved.split(path.sep).includes('node_modules'))
|
|
64
|
+
return;
|
|
65
|
+
if (roots.some(root => {
|
|
66
|
+
const relative = path.relative(root, resolved);
|
|
67
|
+
return (relative === '' ||
|
|
68
|
+
(relative !== '..' &&
|
|
69
|
+
!relative.startsWith(`..${path.sep}`) &&
|
|
70
|
+
!path.isAbsolute(relative)));
|
|
71
|
+
}))
|
|
72
|
+
return;
|
|
73
|
+
throw new Error(`MCP UI 不能引用应用或服务端代码:${path.relative(rootDir, resolved)};请使用 server/mcp/ui 或 shared 中的独立浏览器代码。`);
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
function mcpUiBoundary(rootDir, entryDir) {
|
|
77
|
+
const check = createMcpUiDependencyCheck(rootDir, path.dirname(entryDir));
|
|
78
|
+
const checkUrl = (url, importer) => {
|
|
79
|
+
if (/^(?:[a-z][a-z\d+.-]*:|\/\/|#)/i.test(url))
|
|
80
|
+
return;
|
|
81
|
+
const file = decodeURIComponent(url.split(/[?#]/, 1)[0]);
|
|
82
|
+
if (!file)
|
|
83
|
+
return;
|
|
84
|
+
check(file.startsWith('@shared/')
|
|
85
|
+
? path.resolve(rootDir, 'shared', file.slice(8))
|
|
86
|
+
: file.startsWith('/')
|
|
87
|
+
? path.join(entryDir, file)
|
|
88
|
+
: path.resolve(path.dirname(importer), file));
|
|
89
|
+
};
|
|
90
|
+
const checkCss = (value, importer) => {
|
|
91
|
+
(0, postcss_value_parser_1.default)(value).walk(node => {
|
|
92
|
+
if (node.type !== 'function')
|
|
93
|
+
return;
|
|
94
|
+
if (['image-set', '-webkit-image-set'].includes(node.value.toLowerCase())) {
|
|
95
|
+
for (const child of node.nodes)
|
|
96
|
+
if (child.type === 'string')
|
|
97
|
+
checkUrl(child.value, importer);
|
|
98
|
+
}
|
|
99
|
+
if (node.value.toLowerCase() !== 'url')
|
|
100
|
+
return;
|
|
101
|
+
const raw = node.nodes.length === 1 && node.nodes[0].type === 'string'
|
|
102
|
+
? node.nodes[0].value
|
|
103
|
+
: postcss_value_parser_1.default.stringify(node.nodes);
|
|
104
|
+
const url = raw.replace(/\\([\da-f]{1,6})\s?|\\([^\r\n])/gi, (_match, hex, char) => hex ? String.fromCodePoint(parseInt(hex, 16) || 0xfffd) : char);
|
|
105
|
+
checkUrl(url, importer);
|
|
106
|
+
});
|
|
107
|
+
};
|
|
108
|
+
const plugin = {
|
|
109
|
+
name: 'miaoda:mcp-ui-boundary',
|
|
110
|
+
enforce: 'pre',
|
|
111
|
+
async resolveId(source, importer, options) {
|
|
112
|
+
const resolved = await this.resolve(source, importer, {
|
|
113
|
+
...options,
|
|
114
|
+
skipSelf: true,
|
|
115
|
+
});
|
|
116
|
+
if (resolved)
|
|
117
|
+
check(resolved.id); // 先应用 browser 映射,不误杀合法浏览器替代实现
|
|
118
|
+
return resolved;
|
|
119
|
+
},
|
|
120
|
+
load(id) {
|
|
121
|
+
check(id);
|
|
122
|
+
return null;
|
|
123
|
+
},
|
|
124
|
+
transformIndexHtml: {
|
|
125
|
+
order: 'pre',
|
|
126
|
+
handler(html, context) {
|
|
127
|
+
const visit = (node) => {
|
|
128
|
+
if ('attrs' in node)
|
|
129
|
+
for (const attr of node.attrs ?? []) {
|
|
130
|
+
const isResource = (attr.name === 'src' &&
|
|
131
|
+
[
|
|
132
|
+
'img',
|
|
133
|
+
'script',
|
|
134
|
+
'iframe',
|
|
135
|
+
'audio',
|
|
136
|
+
'video',
|
|
137
|
+
'source',
|
|
138
|
+
'track',
|
|
139
|
+
'embed',
|
|
140
|
+
'input',
|
|
141
|
+
].includes(node.nodeName)) ||
|
|
142
|
+
(attr.name === 'href' &&
|
|
143
|
+
['link', 'use', 'image'].includes(node.nodeName)) ||
|
|
144
|
+
(attr.name === 'poster' && node.nodeName === 'video') ||
|
|
145
|
+
(attr.name === 'data' && node.nodeName === 'object');
|
|
146
|
+
if (isResource)
|
|
147
|
+
checkUrl(attr.value, context.filename);
|
|
148
|
+
if (attr.name === 'style')
|
|
149
|
+
checkCss(attr.value, context.filename);
|
|
150
|
+
if (attr.name === 'srcset' &&
|
|
151
|
+
['img', 'source'].includes(node.nodeName)) {
|
|
152
|
+
// URL token 可包含 data URI 的逗号;描述符的逗号才分隔下一项。
|
|
153
|
+
let rest = attr.value;
|
|
154
|
+
while (rest) {
|
|
155
|
+
rest = rest.replace(/^[\s,]+/, '');
|
|
156
|
+
const match = /^([^\s]+)/.exec(rest);
|
|
157
|
+
if (!match)
|
|
158
|
+
break;
|
|
159
|
+
const url = match[1];
|
|
160
|
+
checkUrl(url.replace(/,+$/, ''), context.filename);
|
|
161
|
+
rest = rest.slice(url.length);
|
|
162
|
+
if (!url.endsWith(','))
|
|
163
|
+
rest = rest.replace(/^[^,]*(?:,|$)/, '');
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
if ('childNodes' in node)
|
|
168
|
+
for (const child of node.childNodes)
|
|
169
|
+
visit(child);
|
|
170
|
+
if ('content' in node && node.content)
|
|
171
|
+
visit(node.content);
|
|
172
|
+
if (node.nodeName === 'style' && 'childNodes' in node) {
|
|
173
|
+
for (const child of node.childNodes)
|
|
174
|
+
if ('value' in child)
|
|
175
|
+
checkCss(child.value, context.filename);
|
|
176
|
+
}
|
|
177
|
+
};
|
|
178
|
+
visit((0, parse5_1.parse)(html));
|
|
179
|
+
},
|
|
180
|
+
},
|
|
181
|
+
transform(code, id) {
|
|
182
|
+
if (!/\.[cm]?[jt]sx?(?:\?|$)/.test(id) || !code.includes('import.meta'))
|
|
183
|
+
return;
|
|
184
|
+
const ast = (0, parser_1.parse)(code, {
|
|
185
|
+
sourceType: 'unambiguous',
|
|
186
|
+
plugins: /\.[cm]?ts(?:\?|$)/.test(id)
|
|
187
|
+
? ['typescript']
|
|
188
|
+
: ['typescript', 'jsx'],
|
|
189
|
+
});
|
|
190
|
+
(0, traverse_1.default)(ast, {
|
|
191
|
+
NewExpression({ node }) {
|
|
192
|
+
if (node.callee.type !== 'Identifier' || node.callee.name !== 'URL')
|
|
193
|
+
return;
|
|
194
|
+
const base = node.arguments[1];
|
|
195
|
+
if (base?.type !== 'MemberExpression' ||
|
|
196
|
+
base.object.type !== 'MetaProperty' ||
|
|
197
|
+
base.object.meta.name !== 'import')
|
|
198
|
+
return;
|
|
199
|
+
const url = node.arguments[0];
|
|
200
|
+
if (url?.type === 'StringLiteral')
|
|
201
|
+
checkUrl(url.value, id.split('?', 1)[0]);
|
|
202
|
+
if (url?.type === 'TemplateLiteral' && url.expressions.length === 0)
|
|
203
|
+
checkUrl(url.quasis[0].value.cooked ?? url.quasis[0].value.raw, id.split('?', 1)[0]);
|
|
204
|
+
},
|
|
205
|
+
});
|
|
206
|
+
},
|
|
207
|
+
generateBundle(_options, bundle) {
|
|
208
|
+
for (const item of Object.values(bundle)) {
|
|
209
|
+
if (item.type !== 'asset')
|
|
210
|
+
continue;
|
|
211
|
+
for (const source of item.originalFileNames ?? [])
|
|
212
|
+
check(path.resolve(entryDir, source));
|
|
213
|
+
}
|
|
214
|
+
},
|
|
215
|
+
};
|
|
216
|
+
const postcss = {
|
|
217
|
+
postcssPlugin: 'miaoda-mcp-ui-css-boundary',
|
|
218
|
+
Once(root) {
|
|
219
|
+
root.walkDecls(decl => {
|
|
220
|
+
if (decl.source?.input.file)
|
|
221
|
+
checkCss(decl.value, decl.source.input.file);
|
|
222
|
+
});
|
|
223
|
+
},
|
|
224
|
+
OnceExit(_root, { result, }) {
|
|
225
|
+
for (const message of result.messages)
|
|
226
|
+
if (message.type === 'dependency' && typeof message.file === 'string')
|
|
227
|
+
check(message.file);
|
|
228
|
+
},
|
|
229
|
+
};
|
|
230
|
+
return { plugin, postcss, check };
|
|
231
|
+
}
|
|
232
|
+
//# sourceMappingURL=mcp-ui-boundary.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcp-ui-boundary.js","sourceRoot":"","sources":["../../src/plugins/mcp-ui-boundary.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import type { Plugin, PluginOption, InlineConfig } from 'vite';
|
|
2
|
+
/**
|
|
3
|
+
* MCP Apps 界面源码目录(相对工程根)。每个子目录一个界面入口:
|
|
4
|
+
* server/mcp/ui/<entry>/index.html
|
|
5
|
+
* 与 tools、skills 按 MCP 能力归集,但仍是独立浏览器代码。
|
|
6
|
+
* 用户工程的服务端 tsconfig 必须排除 server/mcp/ui,并用独立 UI tsconfig 检查。
|
|
7
|
+
* 构建后输出为单文件 HTML:
|
|
8
|
+
* dist/mcp-ui/<entry>.html
|
|
9
|
+
* 服务端通过 `readMcpUiTemplate('<entry>')`(@lark-apaas/nestjs-mcp)读取并作为 `ui://` 资源下发。
|
|
10
|
+
*/
|
|
11
|
+
export declare const MCP_UI_SOURCE_DIR = "server/mcp/ui";
|
|
12
|
+
export declare const MCP_UI_OUT_DIR = "dist/mcp-ui";
|
|
13
|
+
export interface McpUiPluginOptions {
|
|
14
|
+
rootDir: string;
|
|
15
|
+
isDev: boolean;
|
|
16
|
+
/** 覆盖源码目录(相对 rootDir),默认 server/mcp/ui */
|
|
17
|
+
sourceDir?: string;
|
|
18
|
+
/** 覆盖输出目录(相对 rootDir),默认 dist/mcp-ui */
|
|
19
|
+
outDir?: string;
|
|
20
|
+
/** 注入给每个界面构建的额外插件(通常是 react()) */
|
|
21
|
+
plugins?: () => PluginOption[];
|
|
22
|
+
/** 仅供测试:替换 vite.build */
|
|
23
|
+
_build?: (config: InlineConfig) => Promise<unknown>;
|
|
24
|
+
log?: (message: string) => void;
|
|
25
|
+
}
|
|
26
|
+
export interface McpUiEntry {
|
|
27
|
+
/** 目录名,即入口名 */
|
|
28
|
+
name: string;
|
|
29
|
+
/** 目录绝对路径 */
|
|
30
|
+
dir: string;
|
|
31
|
+
/** index.html 绝对路径 */
|
|
32
|
+
html: string;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* 列出所有界面入口:sourceDir 下每个含 index.html 的子目录。
|
|
36
|
+
*/
|
|
37
|
+
export declare function listMcpUiEntries(rootDir: string, sourceDir?: string): McpUiEntry[];
|
|
38
|
+
/**
|
|
39
|
+
* 把 rollup 产出的 HTML 中引用的 JS / CSS 内联进 HTML,并删除独立文件,使产物成为单文件。
|
|
40
|
+
* MCP Apps 宿主以 iframe + srcdoc 方式渲染资源,不能再发起相对路径请求,因此必须内联。
|
|
41
|
+
*/
|
|
42
|
+
export declare function inlineHtmlAssets(html: string, assets: Record<string, string>): {
|
|
43
|
+
html: string;
|
|
44
|
+
consumed: string[];
|
|
45
|
+
};
|
|
46
|
+
/**
|
|
47
|
+
* 内联插件:在 generateBundle 阶段把 chunk / css 塞进 HTML。
|
|
48
|
+
*/
|
|
49
|
+
export declare function singleFilePlugin(): Plugin;
|
|
50
|
+
export interface BuildMcpUiEntryOptions {
|
|
51
|
+
rootDir: string;
|
|
52
|
+
outDir: string;
|
|
53
|
+
isDev: boolean;
|
|
54
|
+
plugins: PluginOption[];
|
|
55
|
+
build: (config: InlineConfig) => Promise<unknown>;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* 构建单个界面入口为 `<outDir>/<entry>.html`。
|
|
59
|
+
*/
|
|
60
|
+
export declare function buildMcpUiEntry(entry: McpUiEntry, options: BuildMcpUiEntryOptions): Promise<string>;
|
|
61
|
+
/**
|
|
62
|
+
* 生成 MCP Apps 单文件界面的 Vite 插件。
|
|
63
|
+
*
|
|
64
|
+
* - 生产构建:主构建结束后(closeBundle)依次构建每个入口。
|
|
65
|
+
* - 开发模式:dev server 启动时构建一次,并监听 `server/mcp/ui/**` 变更增量重建,
|
|
66
|
+
* 服务端 `readMcpUiTemplate` 在开发态不缓存,刷新即生效。
|
|
67
|
+
* - 没有 UI 时跳过界面构建;生产仍同步固定的应用 Skill 文件。
|
|
68
|
+
*/
|
|
69
|
+
export declare function mcpUiPlugin(options: McpUiPluginOptions): Plugin;
|
|
70
|
+
//# sourceMappingURL=mcp-ui.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcp-ui.d.ts","sourceRoot":"","sources":["../../src/plugins/mcp-ui.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,MAAM,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,MAAM,CAAC;AAE/D;;;;;;;;GAQG;AACH,eAAO,MAAM,iBAAiB,kBAAkB,CAAC;AACjD,eAAO,MAAM,cAAc,gBAAgB,CAAC;AAE5C,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,OAAO,CAAC;IACf,0CAA0C;IAC1C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,wCAAwC;IACxC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,kCAAkC;IAClC,OAAO,CAAC,EAAE,MAAM,YAAY,EAAE,CAAC;IAC/B,yBAAyB;IACzB,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,YAAY,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IACpD,GAAG,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CACjC;AAED,MAAM,WAAW,UAAU;IACzB,eAAe;IACf,IAAI,EAAE,MAAM,CAAC;IACb,aAAa;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,sBAAsB;IACtB,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,EAAE,SAAS,GAAE,MAA0B,GAAG,UAAU,EAAE,CASrG;AAOD;;;GAGG;AACH,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAC7B;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,EAAE,CAAA;CAAE,CAgCtC;AAED;;GAEG;AACH,wBAAgB,gBAAgB,IAAI,MAAM,CAkBzC;AAYD,MAAM,WAAW,sBAAsB;IACrC,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,OAAO,CAAC;IACf,OAAO,EAAE,YAAY,EAAE,CAAC;IACxB,KAAK,EAAE,CAAC,MAAM,EAAE,YAAY,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;CACnD;AAED;;GAEG;AACH,wBAAsB,eAAe,CAAC,KAAK,EAAE,UAAU,EAAE,OAAO,EAAE,sBAAsB,GAAG,OAAO,CAAC,MAAM,CAAC,CAkDzG;AAED;;;;;;;GAOG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,kBAAkB,GAAG,MAAM,CA8E/D"}
|
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.MCP_UI_OUT_DIR = exports.MCP_UI_SOURCE_DIR = void 0;
|
|
37
|
+
exports.listMcpUiEntries = listMcpUiEntries;
|
|
38
|
+
exports.inlineHtmlAssets = inlineHtmlAssets;
|
|
39
|
+
exports.singleFilePlugin = singleFilePlugin;
|
|
40
|
+
exports.buildMcpUiEntry = buildMcpUiEntry;
|
|
41
|
+
exports.mcpUiPlugin = mcpUiPlugin;
|
|
42
|
+
const mcp_skill_1 = require("./mcp-skill");
|
|
43
|
+
const fs = __importStar(require("node:fs"));
|
|
44
|
+
const path = __importStar(require("node:path"));
|
|
45
|
+
const mcp_ui_boundary_1 = require("./mcp-ui-boundary");
|
|
46
|
+
/**
|
|
47
|
+
* MCP Apps 界面源码目录(相对工程根)。每个子目录一个界面入口:
|
|
48
|
+
* server/mcp/ui/<entry>/index.html
|
|
49
|
+
* 与 tools、skills 按 MCP 能力归集,但仍是独立浏览器代码。
|
|
50
|
+
* 用户工程的服务端 tsconfig 必须排除 server/mcp/ui,并用独立 UI tsconfig 检查。
|
|
51
|
+
* 构建后输出为单文件 HTML:
|
|
52
|
+
* dist/mcp-ui/<entry>.html
|
|
53
|
+
* 服务端通过 `readMcpUiTemplate('<entry>')`(@lark-apaas/nestjs-mcp)读取并作为 `ui://` 资源下发。
|
|
54
|
+
*/
|
|
55
|
+
exports.MCP_UI_SOURCE_DIR = 'server/mcp/ui';
|
|
56
|
+
exports.MCP_UI_OUT_DIR = 'dist/mcp-ui';
|
|
57
|
+
/**
|
|
58
|
+
* 列出所有界面入口:sourceDir 下每个含 index.html 的子目录。
|
|
59
|
+
*/
|
|
60
|
+
function listMcpUiEntries(rootDir, sourceDir = exports.MCP_UI_SOURCE_DIR) {
|
|
61
|
+
const base = path.resolve(rootDir, sourceDir);
|
|
62
|
+
if (!fs.existsSync(base))
|
|
63
|
+
return [];
|
|
64
|
+
return fs
|
|
65
|
+
.readdirSync(base, { withFileTypes: true })
|
|
66
|
+
.filter((d) => d.isDirectory() && /^[A-Za-z0-9_-]+$/.test(d.name))
|
|
67
|
+
.map((d) => ({ name: d.name, dir: path.join(base, d.name), html: path.join(base, d.name, 'index.html') }))
|
|
68
|
+
.filter((e) => fs.existsSync(e.html))
|
|
69
|
+
.sort((a, b) => a.name.localeCompare(b.name));
|
|
70
|
+
}
|
|
71
|
+
function escapeInlineScript(code) {
|
|
72
|
+
// 内联脚本里出现 </script 会提前闭合标签;<!-- 会让浏览器进入 script 转义态,两者都要转义
|
|
73
|
+
return code.replace(/<\/script/gi, '<\\/script').replace(/<!--/g, '<\\!--');
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* 把 rollup 产出的 HTML 中引用的 JS / CSS 内联进 HTML,并删除独立文件,使产物成为单文件。
|
|
77
|
+
* MCP Apps 宿主以 iframe + srcdoc 方式渲染资源,不能再发起相对路径请求,因此必须内联。
|
|
78
|
+
*/
|
|
79
|
+
function inlineHtmlAssets(html, assets) {
|
|
80
|
+
const consumed = [];
|
|
81
|
+
const lookup = (href) => {
|
|
82
|
+
const cleaned = href.replace(/^\.?\//, '').split(/[?#]/)[0];
|
|
83
|
+
for (const key of Object.keys(assets)) {
|
|
84
|
+
if (key === cleaned || key.endsWith(`/${cleaned}`) || cleaned.endsWith(`/${key}`)) {
|
|
85
|
+
consumed.push(key);
|
|
86
|
+
return assets[key];
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return undefined;
|
|
90
|
+
};
|
|
91
|
+
let out = html.replace(/<script([^>]*?)\ssrc=["']([^"']+)["']([^>]*)><\/script>/gi, (match, before, src, after) => {
|
|
92
|
+
const code = lookup(src);
|
|
93
|
+
if (code === undefined)
|
|
94
|
+
return match;
|
|
95
|
+
const attrs = `${before}${after}`.replace(/\s*crossorigin(=["'][^"']*["'])?/gi, '');
|
|
96
|
+
return `<script${attrs}>${escapeInlineScript(code)}</script>`;
|
|
97
|
+
});
|
|
98
|
+
out = out.replace(/<link([^>]*?)\srel=["']stylesheet["']([^>]*?)>/gi, (match, before, after) => {
|
|
99
|
+
const hrefMatch = /href=["']([^"']+)["']/i.exec(`${before} ${after}`);
|
|
100
|
+
if (!hrefMatch)
|
|
101
|
+
return match;
|
|
102
|
+
const css = lookup(hrefMatch[1]);
|
|
103
|
+
if (css === undefined)
|
|
104
|
+
return match;
|
|
105
|
+
return `<style>${css}</style>`;
|
|
106
|
+
});
|
|
107
|
+
// modulepreload 在单文件里没有意义,顺手移除
|
|
108
|
+
out = out.replace(/\s*<link[^>]*rel=["']modulepreload["'][^>]*>/gi, '');
|
|
109
|
+
return { html: out, consumed: Array.from(new Set(consumed)) };
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* 内联插件:在 generateBundle 阶段把 chunk / css 塞进 HTML。
|
|
113
|
+
*/
|
|
114
|
+
function singleFilePlugin() {
|
|
115
|
+
return {
|
|
116
|
+
name: 'miaoda:mcp-ui-single-file',
|
|
117
|
+
enforce: 'post',
|
|
118
|
+
generateBundle(_options, bundle) {
|
|
119
|
+
const assets = {};
|
|
120
|
+
for (const [fileName, item] of Object.entries(bundle)) {
|
|
121
|
+
if (item.type === 'chunk')
|
|
122
|
+
assets[fileName] = item.code;
|
|
123
|
+
else if (item.type === 'asset' && fileName.endsWith('.css'))
|
|
124
|
+
assets[fileName] = String(item.source);
|
|
125
|
+
}
|
|
126
|
+
for (const [fileName, item] of Object.entries(bundle)) {
|
|
127
|
+
if (item.type !== 'asset' || !fileName.endsWith('.html'))
|
|
128
|
+
continue;
|
|
129
|
+
const { html, consumed } = inlineHtmlAssets(String(item.source), assets);
|
|
130
|
+
item.source = html;
|
|
131
|
+
for (const key of consumed)
|
|
132
|
+
delete bundle[key];
|
|
133
|
+
}
|
|
134
|
+
},
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
async function loadViteBuild() {
|
|
138
|
+
// 本包以 CommonJS 发布,而 Vite 6+ 为纯 ESM;用原生 dynamic import 才能在所有 Node 版本加载。
|
|
139
|
+
// eslint-disable-next-line @typescript-eslint/no-implied-eval
|
|
140
|
+
const dynamicImport = new Function('specifier', 'return import(specifier)');
|
|
141
|
+
const vite = await dynamicImport('vite');
|
|
142
|
+
return vite.build;
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* 构建单个界面入口为 `<outDir>/<entry>.html`。
|
|
146
|
+
*/
|
|
147
|
+
async function buildMcpUiEntry(entry, options) {
|
|
148
|
+
const outDir = path.resolve(options.rootDir, options.outDir);
|
|
149
|
+
const tmpDir = path.join(outDir, `.${entry.name}.tmp`);
|
|
150
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
151
|
+
const boundary = (0, mcp_ui_boundary_1.mcpUiBoundary)(options.rootDir, entry.dir);
|
|
152
|
+
let assetError;
|
|
153
|
+
await options.build({
|
|
154
|
+
configFile: false,
|
|
155
|
+
envFile: false,
|
|
156
|
+
root: fs.realpathSync(entry.dir),
|
|
157
|
+
base: './',
|
|
158
|
+
mode: options.isDev ? 'development' : 'production',
|
|
159
|
+
logLevel: 'warn',
|
|
160
|
+
clearScreen: false,
|
|
161
|
+
plugins: [boundary.plugin, ...options.plugins, singleFilePlugin()],
|
|
162
|
+
publicDir: false,
|
|
163
|
+
css: { postcss: { plugins: [boundary.postcss] } },
|
|
164
|
+
resolve: {
|
|
165
|
+
alias: {
|
|
166
|
+
'@shared': path.resolve(options.rootDir, 'shared'),
|
|
167
|
+
},
|
|
168
|
+
},
|
|
169
|
+
build: {
|
|
170
|
+
outDir: tmpDir,
|
|
171
|
+
emptyOutDir: true,
|
|
172
|
+
sourcemap: false,
|
|
173
|
+
minify: !options.isDev,
|
|
174
|
+
cssCodeSplit: false,
|
|
175
|
+
modulePreload: false,
|
|
176
|
+
assetsInlineLimit: (file) => {
|
|
177
|
+
try {
|
|
178
|
+
boundary.check(file);
|
|
179
|
+
}
|
|
180
|
+
catch (error) {
|
|
181
|
+
assetError = error;
|
|
182
|
+
throw error;
|
|
183
|
+
}
|
|
184
|
+
return true;
|
|
185
|
+
},
|
|
186
|
+
// Vite 8 仍兼容此选项,同时保持仓库 Vite 7 类型检查兼容。
|
|
187
|
+
rollupOptions: { input: fs.realpathSync(entry.html), output: { inlineDynamicImports: true } },
|
|
188
|
+
},
|
|
189
|
+
});
|
|
190
|
+
// Vite 对 new URL 的资源转换会吞掉异常,构建完成后再次确认。
|
|
191
|
+
if (assetError)
|
|
192
|
+
throw assetError;
|
|
193
|
+
const produced = path.join(tmpDir, 'index.html');
|
|
194
|
+
if (!fs.existsSync(produced)) {
|
|
195
|
+
throw new Error(`MCP Apps 界面「${entry.name}」构建未产出 index.html`);
|
|
196
|
+
}
|
|
197
|
+
fs.mkdirSync(outDir, { recursive: true });
|
|
198
|
+
const target = path.join(outDir, `${entry.name}.html`);
|
|
199
|
+
fs.renameSync(produced, target);
|
|
200
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
201
|
+
return target;
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* 生成 MCP Apps 单文件界面的 Vite 插件。
|
|
205
|
+
*
|
|
206
|
+
* - 生产构建:主构建结束后(closeBundle)依次构建每个入口。
|
|
207
|
+
* - 开发模式:dev server 启动时构建一次,并监听 `server/mcp/ui/**` 变更增量重建,
|
|
208
|
+
* 服务端 `readMcpUiTemplate` 在开发态不缓存,刷新即生效。
|
|
209
|
+
* - 没有 UI 时跳过界面构建;生产仍同步固定的应用 Skill 文件。
|
|
210
|
+
*/
|
|
211
|
+
function mcpUiPlugin(options) {
|
|
212
|
+
const { rootDir, isDev, sourceDir = exports.MCP_UI_SOURCE_DIR, outDir = exports.MCP_UI_OUT_DIR, plugins = () => [], log = (m) => console.log(`[mcp-ui] ${m}`), } = options;
|
|
213
|
+
const sourceRoot = path.resolve(rootDir, sourceDir);
|
|
214
|
+
let buildFn = options._build;
|
|
215
|
+
const buildAll = async (entries) => {
|
|
216
|
+
if (entries.length === 0)
|
|
217
|
+
return;
|
|
218
|
+
buildFn ?? (buildFn = await loadViteBuild());
|
|
219
|
+
for (const entry of entries) {
|
|
220
|
+
const started = Date.now();
|
|
221
|
+
const target = await buildMcpUiEntry(entry, { rootDir, outDir, isDev, plugins: plugins(), build: buildFn });
|
|
222
|
+
log(`已构建 ${path.relative(rootDir, target)}(${Date.now() - started}ms)`);
|
|
223
|
+
}
|
|
224
|
+
};
|
|
225
|
+
return {
|
|
226
|
+
name: 'miaoda:mcp-ui',
|
|
227
|
+
async closeBundle() {
|
|
228
|
+
if (isDev)
|
|
229
|
+
return;
|
|
230
|
+
(0, mcp_skill_1.syncMcpSkill)(rootDir);
|
|
231
|
+
const output = path.resolve(rootDir, outDir);
|
|
232
|
+
fs.rmSync(output, { recursive: true, force: true });
|
|
233
|
+
await buildAll(listMcpUiEntries(rootDir, sourceDir));
|
|
234
|
+
// 两种现存 NRF 发布根:dist 与 dist/server。运行时统一读取 cwd/dist/mcp-ui。
|
|
235
|
+
for (const relative of ['dist/dist/mcp-ui', 'dist/server/dist/mcp-ui']) {
|
|
236
|
+
const deployed = path.resolve(rootDir, relative);
|
|
237
|
+
fs.rmSync(deployed, { recursive: true, force: true });
|
|
238
|
+
if (fs.existsSync(output))
|
|
239
|
+
fs.cpSync(output, deployed, { recursive: true });
|
|
240
|
+
}
|
|
241
|
+
},
|
|
242
|
+
async configureServer(server) {
|
|
243
|
+
if (!isDev)
|
|
244
|
+
return;
|
|
245
|
+
let stopped = false;
|
|
246
|
+
let timer;
|
|
247
|
+
const rebuild = async () => {
|
|
248
|
+
if (stopped)
|
|
249
|
+
return;
|
|
250
|
+
const entries = listMcpUiEntries(rootDir, sourceDir);
|
|
251
|
+
const output = path.resolve(rootDir, outDir);
|
|
252
|
+
const names = new Set(entries.map((entry) => `${entry.name}.html`));
|
|
253
|
+
if (fs.existsSync(output)) {
|
|
254
|
+
for (const name of fs.readdirSync(output)) {
|
|
255
|
+
if (name.endsWith('.html') && !names.has(name))
|
|
256
|
+
fs.rmSync(path.join(output, name));
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
await buildAll(entries);
|
|
260
|
+
};
|
|
261
|
+
const report = (error) => log(`构建失败:${error instanceof Error ? error.message : String(error)}`);
|
|
262
|
+
let ready = rebuild().catch(report);
|
|
263
|
+
server.watcher.add([sourceRoot, path.resolve(rootDir, 'shared')]);
|
|
264
|
+
const schedule = (file) => {
|
|
265
|
+
const relevant = [sourceRoot, path.resolve(rootDir, 'shared')].some((dir) => {
|
|
266
|
+
const relative = path.relative(dir, path.resolve(file));
|
|
267
|
+
return !relative.startsWith('..') && !path.isAbsolute(relative);
|
|
268
|
+
});
|
|
269
|
+
if (!relevant || stopped)
|
|
270
|
+
return;
|
|
271
|
+
if (timer)
|
|
272
|
+
clearTimeout(timer);
|
|
273
|
+
timer = setTimeout(() => {
|
|
274
|
+
timer = undefined;
|
|
275
|
+
ready = ready.then(rebuild).catch(report);
|
|
276
|
+
}, 200);
|
|
277
|
+
};
|
|
278
|
+
const events = ['change', 'add', 'unlink', 'addDir', 'unlinkDir'];
|
|
279
|
+
for (const event of events)
|
|
280
|
+
server.watcher.on(event, schedule);
|
|
281
|
+
server.httpServer?.once('close', () => {
|
|
282
|
+
stopped = true;
|
|
283
|
+
if (timer)
|
|
284
|
+
clearTimeout(timer);
|
|
285
|
+
for (const event of events)
|
|
286
|
+
server.watcher.off(event, schedule);
|
|
287
|
+
});
|
|
288
|
+
await ready;
|
|
289
|
+
},
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
//# sourceMappingURL=mcp-ui.js.map
|