aui-mcp-server 0.0.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.
Files changed (62) hide show
  1. package/README.md +122 -0
  2. package/dist/cli.cjs +1088 -0
  3. package/dist/cli.cjs.map +1 -0
  4. package/dist/cli.d.cts +1 -0
  5. package/dist/cli.d.ts +1 -0
  6. package/dist/cli.js +1076 -0
  7. package/dist/cli.js.map +1 -0
  8. package/dist/client/index.cjs +619 -0
  9. package/dist/client/index.cjs.map +1 -0
  10. package/dist/client/index.d.cts +194 -0
  11. package/dist/client/index.d.ts +194 -0
  12. package/dist/client/index.js +584 -0
  13. package/dist/client/index.js.map +1 -0
  14. package/dist/index.cjs +1053 -0
  15. package/dist/index.cjs.map +1 -0
  16. package/dist/index.d.cts +163 -0
  17. package/dist/index.d.ts +163 -0
  18. package/dist/index.js +1036 -0
  19. package/dist/index.js.map +1 -0
  20. package/dist/rsbuild.cjs +1049 -0
  21. package/dist/rsbuild.cjs.map +1 -0
  22. package/dist/rsbuild.d.cts +12 -0
  23. package/dist/rsbuild.d.ts +12 -0
  24. package/dist/rsbuild.js +1038 -0
  25. package/dist/rsbuild.js.map +1 -0
  26. package/dist/rspack.cjs +1016 -0
  27. package/dist/rspack.cjs.map +1 -0
  28. package/dist/rspack.d.cts +40 -0
  29. package/dist/rspack.d.ts +40 -0
  30. package/dist/rspack.js +1005 -0
  31. package/dist/rspack.js.map +1 -0
  32. package/dist/server.cjs +304 -0
  33. package/dist/server.cjs.map +1 -0
  34. package/dist/server.d.cts +16 -0
  35. package/dist/server.d.ts +16 -0
  36. package/dist/server.js +297 -0
  37. package/dist/server.js.map +1 -0
  38. package/package.json +72 -0
  39. package/src/catalog/build.ts +89 -0
  40. package/src/catalog/entry.ts +183 -0
  41. package/src/catalog/parser.ts +173 -0
  42. package/src/catalog/tool_parser.ts +145 -0
  43. package/src/cli.ts +318 -0
  44. package/src/client/handshake.ts +166 -0
  45. package/src/client/index.ts +6 -0
  46. package/src/client/registry.tsx +184 -0
  47. package/src/client/types.ts +136 -0
  48. package/src/client/useA2UIStream.ts +378 -0
  49. package/src/client/useLogger.ts +147 -0
  50. package/src/generator.ts +100 -0
  51. package/src/index.ts +17 -0
  52. package/src/mcp-app-poc.html +69 -0
  53. package/src/poc.ts +88 -0
  54. package/src/rsbuild.ts +46 -0
  55. package/src/rspack.ts +282 -0
  56. package/src/server.ts +391 -0
  57. package/src/templates.ts +51 -0
  58. package/src/types.ts +195 -0
  59. package/src/utils.ts +29 -0
  60. package/test.js +16 -0
  61. package/tsconfig.json +19 -0
  62. package/tsup.config.ts +27 -0
@@ -0,0 +1,1038 @@
1
+ import fs4, { promises, existsSync, readFileSync } from 'fs';
2
+ import path2, { join, dirname, isAbsolute } from 'path';
3
+ import { ModuleFederationPlugin } from '@module-federation/enhanced/rspack';
4
+ import { fileURLToPath } from 'url';
5
+ import esbuild from 'esbuild';
6
+ import http from 'http';
7
+ import chokidar from 'chokidar';
8
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
9
+ import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js';
10
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
11
+ import { ListToolsRequestSchema, CallToolRequestSchema } from '@modelcontextprotocol/sdk/types.js';
12
+ import { init, loadRemote } from '@module-federation/runtime';
13
+ import { createRequire } from 'module';
14
+
15
+ /* aui-mcp-server */
16
+
17
+ function parseToolLogic(toolName, logicFilePath, baseDir) {
18
+ let targetPath = "";
19
+ const extensions = [".ts", ".tsx", ".js", ".jsx"];
20
+ const absPath = isAbsolute(logicFilePath) ? logicFilePath : join(baseDir, logicFilePath);
21
+ for (const ext of ["", ...extensions]) {
22
+ if (existsSync(absPath + ext)) {
23
+ targetPath = absPath + ext;
24
+ break;
25
+ }
26
+ }
27
+ if (!targetPath) {
28
+ console.error(`[AUI-X] Tool logic file not found: ${logicFilePath} (base: ${baseDir})`);
29
+ return null;
30
+ }
31
+ const content = readFileSync(targetPath, "utf-8");
32
+ let description = `Automatically generated tool for ${toolName}.`;
33
+ let jsdocContent = "";
34
+ let paramsContent = "";
35
+ let finalToolName = toolName;
36
+ const jsdocRegex = /\/\*\*([\s\S]*?)\*\/[\s\S]*?(?:export\s+)?(?:async\s+)?function\s+(\w+)/g;
37
+ let match;
38
+ while ((match = jsdocRegex.exec(content)) !== null) {
39
+ const block = match[1];
40
+ const funcName = match[2];
41
+ const toolMatch = block.match(/@tool\s+(\w+)/);
42
+ if (toolMatch) {
43
+ if (toolMatch[1] === toolName || toolName.includes("artistevents") || toolName.endsWith("_data")) {
44
+ jsdocContent = block;
45
+ finalToolName = toolMatch[1];
46
+ const pRegex = new RegExp(`(?:export\\s+)?(?:async\\s+)?function\\s+${funcName}\\s*\\(\\s*(?:args|\\{[\\s\\S]*?\\}|)\\s*:\\s*\\{([\\s\\S]*?)\\}\\s*\\)`, "m");
47
+ const pMatch = content.match(pRegex);
48
+ if (pMatch) {
49
+ paramsContent = pMatch[1] || "";
50
+ }
51
+ break;
52
+ }
53
+ }
54
+ if (funcName === "loader" && !jsdocContent) {
55
+ jsdocContent = block;
56
+ const pRegex = /(?:export\s+)?(?:async\s+)?function\s+loader\s*\(\s*(?:args|\{[\s\S]*?\}|)\s*:\s*\{([\s\S]*?)\}\s*\)/m;
57
+ const pMatch = content.match(pRegex);
58
+ if (pMatch) {
59
+ paramsContent = pMatch[1] || "";
60
+ }
61
+ }
62
+ }
63
+ const properties = {};
64
+ const required = [];
65
+ if (jsdocContent) {
66
+ const descriptionOverride = jsdocContent.match(/@description\s+([^\n@]+)/);
67
+ if (descriptionOverride?.[1]) {
68
+ description = descriptionOverride[1].trim();
69
+ } else {
70
+ description = jsdocContent.split("\n").map((l) => l.replace(/^\s*\*\s?/, "").trim()).filter((l) => l && !l.startsWith("@") && !l.startsWith("/") && !l.endsWith("/")).join("\n").trim();
71
+ description = description.split("\n").filter((l) => !l.trim().startsWith("@")).join(" ").trim();
72
+ }
73
+ if (paramsContent) {
74
+ const fields = paramsContent.split(",").map((f) => f.trim()).filter(Boolean);
75
+ for (const field of fields) {
76
+ const fieldMatch = field.match(/^(\w+)(\??):\s*(\w+)/);
77
+ if (fieldMatch) {
78
+ const name = fieldMatch[1];
79
+ const optional = fieldMatch[2] === "?";
80
+ const rawType = fieldMatch[3];
81
+ let type = "string";
82
+ if (rawType === "number") type = "number";
83
+ if (rawType === "boolean") type = "boolean";
84
+ properties[name] = { type };
85
+ if (!optional) required.push(name);
86
+ }
87
+ }
88
+ }
89
+ }
90
+ return {
91
+ name: finalToolName,
92
+ description,
93
+ logicFilePath: targetPath,
94
+ parameters: {
95
+ type: "object",
96
+ properties,
97
+ required: required.length > 0 ? required : void 0
98
+ }
99
+ };
100
+ }
101
+
102
+ // src/catalog/entry.ts
103
+ function propsToSchemaProperties(props) {
104
+ const result = {};
105
+ for (const prop of props) {
106
+ result[prop.name] = {
107
+ type: "object",
108
+ ...prop.description ? { description: prop.description } : {},
109
+ properties: {
110
+ literalString: { type: "string" },
111
+ path: { type: "string" }
112
+ }
113
+ };
114
+ }
115
+ return result;
116
+ }
117
+ function getRequiredProps(props) {
118
+ return props.filter((p) => !p.optional).map((p) => p.name);
119
+ }
120
+ function buildXLoader(ctx) {
121
+ return {
122
+ type: "module-federation",
123
+ url: ctx.remoteEntryUrl,
124
+ scope: ctx.stats.id,
125
+ module: ctx.expose.path
126
+ };
127
+ }
128
+ function buildFallbackExample(ctx) {
129
+ const surfaceId = `${ctx.componentName.toLowerCase()}-surface-1`;
130
+ const componentId = `${ctx.componentName.toLowerCase()}-card`;
131
+ const required = getRequiredProps(ctx.props);
132
+ const propsPayload = {};
133
+ for (const name of required) {
134
+ propsPayload[name] = { literalString: `<!-- ${name} -->` };
135
+ }
136
+ return [
137
+ {
138
+ beginRendering: {
139
+ surfaceId,
140
+ root: "root-column"
141
+ }
142
+ },
143
+ {
144
+ surfaceUpdate: {
145
+ surfaceId,
146
+ components: [
147
+ {
148
+ id: "root-column",
149
+ component: {
150
+ Column: {
151
+ children: { explicitList: [componentId] }
152
+ }
153
+ }
154
+ },
155
+ {
156
+ id: componentId,
157
+ component: {
158
+ [ctx.componentName]: propsPayload
159
+ }
160
+ }
161
+ ]
162
+ }
163
+ }
164
+ ];
165
+ }
166
+ function generateCatalogEntry(ctx) {
167
+ const schemaProps = propsToSchemaProperties(ctx.props);
168
+ const required = getRequiredProps(ctx.props);
169
+ if (ctx.jsdoc.actions && ctx.jsdoc.actions.length > 0) {
170
+ for (const actionDef of ctx.jsdoc.actions) {
171
+ const propName = `${actionDef.name}Action`;
172
+ const desc = actionDef.description ? `${actionDef.description}${actionDef.replyWith ? ` (Expected reply: ${actionDef.replyWith})` : ""}` : `Trigger server event: ${actionDef.name}`;
173
+ schemaProps[propName] = {
174
+ type: "object",
175
+ description: desc,
176
+ properties: {
177
+ name: { type: "string" },
178
+ context: {
179
+ type: "array",
180
+ items: {
181
+ type: "object",
182
+ properties: {
183
+ key: { type: "string" },
184
+ value: { type: "object" }
185
+ },
186
+ required: ["key", "value"]
187
+ }
188
+ }
189
+ },
190
+ required: ["name"]
191
+ };
192
+ }
193
+ }
194
+ const entry = {
195
+ type: "object",
196
+ description: ctx.jsdoc.description ?? `AUI-X MF Remote component: ${ctx.componentName}.`,
197
+ properties: schemaProps,
198
+ ...required.length > 0 ? { required } : {},
199
+ "x-loader": buildXLoader(ctx),
200
+ "x-example": ctx.jsdoc.example ?? buildFallbackExample(ctx),
201
+ ...ctx.jsdoc.authRequired !== void 0 ? { "x-auth-required": ctx.jsdoc.authRequired } : {},
202
+ ...ctx.jsdoc.fallback ? { "x-fallback": ctx.jsdoc.fallback } : {}
203
+ };
204
+ const xTools = [];
205
+ if (ctx.jsdoc.tools && ctx.jsdoc.tools.length > 0) {
206
+ for (const tool of ctx.jsdoc.tools) {
207
+ const sourceFile = ctx.expose.file;
208
+ const baseDir = join(process.cwd(), dirname(sourceFile));
209
+ const parsed = parseToolLogic(tool.name, tool.logicFile, baseDir);
210
+ if (parsed && ctx.loaderExpose?.path) {
211
+ xTools.push({
212
+ name: parsed.name,
213
+ description: parsed.description,
214
+ loader: ctx.loaderExpose.path,
215
+ parameters: parsed.parameters,
216
+ logicFilePath: parsed.logicFilePath
217
+ });
218
+ }
219
+ }
220
+ }
221
+ if (ctx.loaderExpose) {
222
+ const loaderFile = ctx.loaderExpose.file;
223
+ const baseDir = process.cwd();
224
+ const defaultToolName = `get_${ctx.componentName.toLowerCase()}_data`;
225
+ const parsed = parseToolLogic(defaultToolName, loaderFile, baseDir);
226
+ if (parsed) {
227
+ if (!xTools.find((t) => t.name === parsed.name || t.logicFilePath === parsed.logicFilePath)) {
228
+ xTools.push({
229
+ name: parsed.name,
230
+ description: parsed.description,
231
+ loader: ctx.loaderExpose.path,
232
+ parameters: parsed.parameters,
233
+ logicFilePath: parsed.logicFilePath
234
+ });
235
+ }
236
+ }
237
+ }
238
+ if (xTools.length > 0) {
239
+ entry["x-tools"] = xTools;
240
+ }
241
+ return entry;
242
+ }
243
+
244
+ // src/catalog/parser.ts
245
+ function parseJsDoc(dtsContent) {
246
+ const jsdocBlockRegex = /\/\*\*([\s\S]*?)\*\//g;
247
+ let targetBlock = null;
248
+ for (const match of dtsContent.matchAll(jsdocBlockRegex)) {
249
+ const block = match[1];
250
+ if (block?.includes("@aui-component")) {
251
+ targetBlock = block;
252
+ break;
253
+ }
254
+ }
255
+ if (!targetBlock) {
256
+ return { isAuiComponent: false };
257
+ }
258
+ const result = { isAuiComponent: true };
259
+ const lines = targetBlock.split("\n").map((l) => l.replace(/^\s*\*\s?/, "").trimEnd());
260
+ const descMatch = lines.find((l) => l.startsWith("@description"));
261
+ if (descMatch) {
262
+ result.description = descMatch.replace("@description", "").trim();
263
+ }
264
+ const authMatch = lines.find((l) => l.startsWith("@auth-required"));
265
+ if (authMatch) {
266
+ const val = authMatch.replace("@auth-required", "").trim().toLowerCase();
267
+ result.authRequired = val === "true";
268
+ }
269
+ const fallbackMatch = lines.find((l) => l.startsWith("@fallback"));
270
+ if (fallbackMatch) {
271
+ result.fallback = fallbackMatch.replace("@fallback", "").trim();
272
+ }
273
+ const exampleStartIdx = lines.findIndex((l) => l.startsWith("@example"));
274
+ if (exampleStartIdx !== -1) {
275
+ const exampleLine = lines[exampleStartIdx];
276
+ if (exampleLine) {
277
+ const exampleFirstLine = exampleLine.replace("@example", "").trim();
278
+ const continuationLines = [];
279
+ for (let i = exampleStartIdx + 1; i < lines.length; i++) {
280
+ const l = lines[i];
281
+ if (!l) break;
282
+ if (/^@\w/.test(l)) break;
283
+ if (l && !/^[\s[\]{},"'0-9\-+tfn]/.test(l)) break;
284
+ continuationLines.push(l);
285
+ }
286
+ const rawJson = [exampleFirstLine, ...continuationLines].join("\n").trim();
287
+ if (rawJson) {
288
+ try {
289
+ result.example = JSON.parse(rawJson);
290
+ } catch {
291
+ }
292
+ }
293
+ }
294
+ }
295
+ const toolLines = lines.filter((l) => l.startsWith("@tool "));
296
+ if (toolLines.length > 0) {
297
+ result.tools = toolLines.map((l) => {
298
+ const parts = l.replace("@tool", "").trim().split(/\s+/);
299
+ return {
300
+ name: parts[0] ?? "",
301
+ logicFile: parts[1] ?? ""
302
+ };
303
+ });
304
+ }
305
+ const actionLines = lines.filter((l) => l.startsWith("@action "));
306
+ if (actionLines.length > 0) {
307
+ result.actions = actionLines.map((l) => {
308
+ const raw = l.replace("@action", "").trim();
309
+ const action = {};
310
+ const pairs = raw.match(/(\w+)="([^"]*)"/g) || [];
311
+ for (const p of pairs) {
312
+ const [k, v] = p.split('="');
313
+ const key = k?.trim();
314
+ const val = v?.replace(/"$/, "");
315
+ if (key === "name") action.name = val;
316
+ if (key === "description") action.description = val;
317
+ if (key === "replyWith") action.replyWith = val;
318
+ if (key === "contextKeys") action.contextKeys = val?.split(",").map((s) => s.trim());
319
+ }
320
+ return action;
321
+ });
322
+ }
323
+ return result;
324
+ }
325
+ function needsJsDocFill(dtsContent) {
326
+ return !dtsContent.includes("@aui-component");
327
+ }
328
+ function parsePropsInterface(dtsContent, componentName) {
329
+ const patterns = [
330
+ new RegExp(`interface\\s+${componentName}Properties\\s*\\{([\\s\\S]*?)\\}`, "m"),
331
+ new RegExp(`interface\\s+${componentName}Props\\s*\\{([\\s\\S]*?)\\}`, "m")
332
+ ];
333
+ let interfaceBody = null;
334
+ for (const pattern of patterns) {
335
+ const m = dtsContent.match(pattern);
336
+ const body = m?.[1];
337
+ if (body) {
338
+ interfaceBody = body;
339
+ break;
340
+ }
341
+ }
342
+ if (!interfaceBody) {
343
+ return [];
344
+ }
345
+ const nodeMatch = interfaceBody.match(/node\s*:\s*\{\s*properties\s*:\s*\{([\s\S]*?)\}\s*\}/m);
346
+ if (nodeMatch) {
347
+ interfaceBody = nodeMatch[1];
348
+ }
349
+ const props = [];
350
+ const fieldRegex = /^\s*(\w+)(\??):\s*([\s\S]+?)(?:;|$)/;
351
+ const reserved = ["node", "properties", "literalString", "path"];
352
+ for (const line of interfaceBody.split("\n")) {
353
+ const m = line.match(fieldRegex);
354
+ if (!m) continue;
355
+ const name = m[1];
356
+ if (!name || nodeMatch && reserved.includes(name)) continue;
357
+ const questionMark = m[2];
358
+ let rawType = m[3]?.trim() ?? "";
359
+ if (rawType.startsWith("{")) {
360
+ rawType = "object";
361
+ }
362
+ const optional = questionMark === "?";
363
+ const type = rawType;
364
+ props.push({ name, type, optional });
365
+ }
366
+ return props;
367
+ }
368
+
369
+ // src/catalog/build.ts
370
+ function buildCatalogFromStats(options) {
371
+ const { stats, readCompiledTypes } = options;
372
+ const catalog = {};
373
+ const issues = [];
374
+ const missingJsdocComponents = [];
375
+ const { publicPath, remoteEntry } = stats.metaData;
376
+ const remoteEntryUrl = publicPath.replace(/\/$/, "") + "/" + [remoteEntry.path, "mf-manifest.json"].filter(Boolean).join("/");
377
+ const loaderExposes = stats.exposes.filter((e) => e.path.endsWith(".data"));
378
+ const componentExposes = stats.exposes.filter((e) => !e.path.endsWith(".data"));
379
+ for (const expose of componentExposes) {
380
+ const componentName = expose.name;
381
+ const loaderExpose = loaderExposes.find((le) => le.path === `${expose.path}.data`);
382
+ const dtsContent = readCompiledTypes(componentName);
383
+ if (!dtsContent) {
384
+ issues.push({ componentName, message: "Missing compiled d.ts for component" });
385
+ continue;
386
+ }
387
+ if (needsJsDocFill(dtsContent)) {
388
+ missingJsdocComponents.push(componentName);
389
+ issues.push({
390
+ componentName,
391
+ message: "Missing @aui-component JSDoc (use skills/aui-jsdoc-gen)"
392
+ });
393
+ continue;
394
+ }
395
+ const jsdoc = parseJsDoc(dtsContent);
396
+ const props = parsePropsInterface(dtsContent, componentName);
397
+ const ctx = {
398
+ stats,
399
+ expose,
400
+ jsdoc,
401
+ props,
402
+ componentName,
403
+ remoteEntryUrl,
404
+ loaderExpose
405
+ };
406
+ try {
407
+ catalog[componentName] = generateCatalogEntry(ctx);
408
+ } catch (err) {
409
+ console.error(`[AUI-X] Failed to generate catalog entry for ${componentName}:`, err);
410
+ issues.push({
411
+ componentName,
412
+ message: "Failed to generate catalog entry",
413
+ detail: err instanceof Error ? err.message : String(err)
414
+ });
415
+ }
416
+ }
417
+ return { catalog, issues, missingJsdocComponents };
418
+ }
419
+ async function fileExists(filePath) {
420
+ try {
421
+ await promises.access(filePath);
422
+ return true;
423
+ } catch {
424
+ return false;
425
+ }
426
+ }
427
+ async function readJsonFile(filePath) {
428
+ const raw = await promises.readFile(filePath, "utf-8");
429
+ return JSON.parse(raw);
430
+ }
431
+ function toSnakeCase(input) {
432
+ return input.trim().replace(/[\s-]+/g, "_").replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/([A-Z]+)([A-Z][a-z])/g, "$1_$2").replace(/_+/g, "_").toLowerCase();
433
+ }
434
+ function toToolName(componentName) {
435
+ return `get_${toSnakeCase(componentName)}`;
436
+ }
437
+
438
+ // src/templates.ts
439
+ function buildServerSource() {
440
+ return `import { readFileSync } from 'node:fs';
441
+ import { dirname, join } from 'node:path';
442
+ import { fileURLToPath } from 'node:url';
443
+
444
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
445
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
446
+ import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';
447
+
448
+ const __dirname = dirname(fileURLToPath(import.meta.url));
449
+ const manifestPath = process.env.AUI_MCP_MANIFEST ?? join(__dirname, 'manifest.json');
450
+ const manifest = JSON.parse(readFileSync(manifestPath, 'utf-8'));
451
+
452
+ const server = new Server(
453
+ { name: 'aui-x-catalog', version: '0.1.0' },
454
+ { capabilities: { tools: {} } }
455
+ );
456
+
457
+ server.setRequestHandler(ListToolsRequestSchema, async () => {
458
+ return {
459
+ tools: (manifest.tools ?? []).map((tool) => ({
460
+ name: tool.name,
461
+ description: tool.description,
462
+ inputSchema: tool.inputSchema ?? { type: 'object', properties: {}, required: [] },
463
+ })),
464
+ };
465
+ });
466
+
467
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
468
+ const toolName = request.params?.name;
469
+ const tool = (manifest.tools ?? []).find((t) => t.name === toolName);
470
+ if (!tool) {
471
+ throw new Error('Tool not found: ' + toolName);
472
+ }
473
+
474
+ return {
475
+ content: [
476
+ {
477
+ type: 'text',
478
+ text: JSON.stringify(tool.entry, null, 2),
479
+ },
480
+ ],
481
+ };
482
+ });
483
+
484
+ const transport = new StdioServerTransport();
485
+ await server.connect(transport);
486
+ `;
487
+ }
488
+
489
+ // src/generator.ts
490
+ function assertCatalogShape(catalog) {
491
+ if (!catalog || typeof catalog !== "object" || Array.isArray(catalog)) {
492
+ throw new Error("Invalid aui-x-catalog.json: expected an object");
493
+ }
494
+ }
495
+ async function loadCatalog(options) {
496
+ const catalogPath = options.catalogPath ?? "aui-x-catalog.json";
497
+ if (!await fileExists(catalogPath)) {
498
+ throw new Error(`catalog not found: ${catalogPath}`);
499
+ }
500
+ const catalog = await readJsonFile(catalogPath);
501
+ assertCatalogShape(catalog);
502
+ return catalog;
503
+ }
504
+ function buildAuiMcpManifest(catalog) {
505
+ const tools = Object.entries(catalog).map(([componentName, entry]) => {
506
+ return {
507
+ name: toToolName(componentName),
508
+ componentName,
509
+ description: entry.description,
510
+ inputSchema: { type: "object", properties: {}, required: [] },
511
+ entry
512
+ };
513
+ });
514
+ return {
515
+ schemaVersion: 1,
516
+ tools
517
+ };
518
+ }
519
+ async function bundleAuiMcpServer() {
520
+ const serverSource = buildServerSource();
521
+ const result = await esbuild.build({
522
+ stdin: {
523
+ contents: serverSource,
524
+ // Resolve from package root so pnpm symlinked deps can be bundled.
525
+ resolveDir: path2.resolve(path2.dirname(fileURLToPath(import.meta.url)), ".."),
526
+ sourcefile: "aui-mcp-server.generated.ts",
527
+ loader: "ts"
528
+ },
529
+ bundle: true,
530
+ platform: "node",
531
+ format: "esm",
532
+ target: "node18",
533
+ write: false,
534
+ outfile: "server.mjs",
535
+ sourcemap: false,
536
+ logLevel: "silent"
537
+ });
538
+ const outputFile = result.outputFiles?.[0];
539
+ if (!outputFile) {
540
+ throw new Error("Failed to bundle MCP server");
541
+ }
542
+ return outputFile.text;
543
+ }
544
+ async function generateAuiMcpAssets(options = {}) {
545
+ const outDir = options.outDir ?? "dist";
546
+ const mcpDirname = options.mcpDirname ?? "mcp";
547
+ const mcpDir = path2.join(outDir, mcpDirname);
548
+ const catalog = await loadCatalog(options);
549
+ const manifest = buildAuiMcpManifest(catalog);
550
+ await promises.mkdir(mcpDir, { recursive: true });
551
+ const manifestPath = path2.join(mcpDir, "manifest.json");
552
+ await promises.writeFile(manifestPath, JSON.stringify(manifest, null, 2), "utf-8");
553
+ let serverPath;
554
+ if (options.emitServer !== false) {
555
+ serverPath = path2.join(mcpDir, "server.mjs");
556
+ const serverCode = await bundleAuiMcpServer();
557
+ await promises.writeFile(serverPath, serverCode, "utf-8");
558
+ }
559
+ return { manifestPath, serverPath };
560
+ }
561
+ var __mfGlobal = globalThis;
562
+ if (typeof __mfGlobal.self === "undefined") __mfGlobal.self = __mfGlobal;
563
+ if (typeof __mfGlobal.window === "undefined") __mfGlobal.window = __mfGlobal;
564
+ var EMPTY_INPUT_SCHEMA = {
565
+ type: "object",
566
+ properties: {},
567
+ required: []
568
+ };
569
+ async function loadManifestFromFile(manifestPath) {
570
+ const manifest = await readJsonFile(manifestPath);
571
+ if (!manifest || typeof manifest !== "object" || manifest.schemaVersion !== 1) {
572
+ throw new Error(`Invalid manifest: ${manifestPath}`);
573
+ }
574
+ return manifest;
575
+ }
576
+ async function loadManifestFromCatalog(catalogPath) {
577
+ const catalog = await readJsonFile(catalogPath);
578
+ if (!catalog || typeof catalog !== "object" || Array.isArray(catalog)) {
579
+ throw new Error(`Invalid aui-x-catalog.json: ${catalogPath}`);
580
+ }
581
+ return {
582
+ schemaVersion: 1,
583
+ tools: Object.entries(catalog).map(([componentName, entry]) => ({
584
+ name: toToolName(componentName),
585
+ componentName,
586
+ description: entry.description,
587
+ inputSchema: EMPTY_INPUT_SCHEMA,
588
+ entry
589
+ }))
590
+ };
591
+ }
592
+ function buildToolMap(manifest) {
593
+ const map = /* @__PURE__ */ new Map();
594
+ for (const tool of manifest.tools ?? []) {
595
+ map.set(tool.name, tool);
596
+ }
597
+ return map;
598
+ }
599
+ async function executeCatalogTool(manifest, toolName, args) {
600
+ const tools = buildToolMap(manifest);
601
+ const tool = tools.get(toolName);
602
+ if (!tool) {
603
+ return { ok: false, message: `Tool not found: ${toolName}` };
604
+ }
605
+ const entry = tool.entry;
606
+ const xLoader = entry["x-loader"];
607
+ const xTools = entry["x-tools"] ?? [];
608
+ if (!xLoader) {
609
+ return { ok: false, message: `No x-loader config for tool: ${toolName}` };
610
+ }
611
+ if (!xTools.length) {
612
+ return { ok: false, message: `No x-tools defined for component: ${tool.componentName}` };
613
+ }
614
+ const selectedTool = xTools.find((t) => t.name === toolName) ?? xTools[0];
615
+ if (!selectedTool) {
616
+ return { ok: false, message: `No matching x-tools entry for tool: ${toolName}` };
617
+ }
618
+ const loaderPath = selectedTool.loader.slice(2);
619
+ const logicFilePath = selectedTool.logicFilePath;
620
+ try {
621
+ const inputArgs = args && typeof args === "object" ? args : {};
622
+ if (loaderPath) {
623
+ const scope = xLoader.scope;
624
+ const remoteEntryUrl = xLoader.url;
625
+ try {
626
+ init({
627
+ name: "aui-mcp-mf-host",
628
+ remotes: [
629
+ {
630
+ name: scope,
631
+ entry: remoteEntryUrl
632
+ }
633
+ ]
634
+ });
635
+ } catch {
636
+ }
637
+ const remoteKey = `${scope}/${loaderPath}`;
638
+ const mod = await loadRemote(remoteKey);
639
+ const loaderFn = mod?.loader ?? mod?.default;
640
+ if (typeof loaderFn !== "function") {
641
+ return {
642
+ ok: false,
643
+ message: `Loaded MF module '${remoteKey}' does not export a callable loader`
644
+ };
645
+ }
646
+ const result = await loaderFn(inputArgs);
647
+ return { ok: true, value: result };
648
+ }
649
+ if (logicFilePath) {
650
+ const moduleUrl = new URL(`file://${logicFilePath}`);
651
+ const mod = await import(moduleUrl.href);
652
+ const loaderFn = mod?.loader ?? mod?.default;
653
+ if (typeof loaderFn !== "function") {
654
+ return {
655
+ ok: false,
656
+ message: `Local logic file '${logicFilePath}' does not export a callable loader`
657
+ };
658
+ }
659
+ const result = await loaderFn(inputArgs);
660
+ return { ok: true, value: result };
661
+ }
662
+ return { ok: false, message: `No executable loader found for tool: ${toolName}` };
663
+ } catch (error) {
664
+ console.error("[aui-mcp] tool execution failed:", error);
665
+ return {
666
+ ok: false,
667
+ message: error instanceof Error ? error.message : String(error)
668
+ };
669
+ }
670
+ }
671
+ async function createServer(getManifest) {
672
+ const server = new Server(
673
+ { name: "aui-x-catalog", version: "0.1.0" },
674
+ { capabilities: { tools: {} } }
675
+ );
676
+ server.setRequestHandler(ListToolsRequestSchema, async () => {
677
+ const manifest = getManifest();
678
+ return {
679
+ tools: (manifest.tools ?? []).map((t) => ({
680
+ name: t.name,
681
+ description: t.description,
682
+ inputSchema: t.inputSchema ?? EMPTY_INPUT_SCHEMA
683
+ }))
684
+ };
685
+ });
686
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
687
+ const manifest = getManifest();
688
+ const exec = await executeCatalogTool(manifest, request.params.name, request.params.arguments);
689
+ if (!exec.ok) {
690
+ return {
691
+ content: [{ type: "text", text: exec.message }],
692
+ isError: true
693
+ };
694
+ }
695
+ return {
696
+ // Return raw loader result as JSON for the Agent to consume.
697
+ content: [{ type: "text", text: JSON.stringify(exec.value) }]
698
+ };
699
+ });
700
+ return server;
701
+ }
702
+ async function startWatch(filePath, onReload, onReloadError) {
703
+ await promises.access(filePath);
704
+ const watcher = chokidar.watch(filePath, { ignoreInitial: true });
705
+ watcher.on("change", async () => {
706
+ try {
707
+ await onReload();
708
+ } catch (err) {
709
+ onReloadError(err);
710
+ }
711
+ });
712
+ return watcher;
713
+ }
714
+ async function serveAuiMcpServer(options = {}) {
715
+ const transport = options.transport ?? "stdio";
716
+ const port = options.port ?? 8001;
717
+ const manifestPath = options.manifestPath;
718
+ const catalogPath = options.catalogPath;
719
+ if (!manifestPath && !catalogPath) {
720
+ throw new Error("Either manifestPath or catalogPath is required");
721
+ }
722
+ const resolvedCatalogPath = catalogPath ?? "";
723
+ let currentManifest = manifestPath ? await loadManifestFromFile(manifestPath) : await loadManifestFromCatalog(resolvedCatalogPath);
724
+ const getRawCatalog = () => {
725
+ const catalog = {};
726
+ for (const tool of currentManifest.tools ?? []) {
727
+ if (tool.entry) {
728
+ catalog[tool.componentName] = tool.entry;
729
+ }
730
+ }
731
+ return catalog;
732
+ };
733
+ const server = await createServer(() => currentManifest);
734
+ if (options.watch) {
735
+ const watchPath = manifestPath ?? resolvedCatalogPath;
736
+ await startWatch(
737
+ watchPath,
738
+ async () => {
739
+ currentManifest = manifestPath ? await loadManifestFromFile(manifestPath) : await loadManifestFromCatalog(resolvedCatalogPath);
740
+ await server.sendToolListChanged();
741
+ console.error(
742
+ `[aui-mcp] reloaded: ${(currentManifest.tools ?? []).length} tools from ${watchPath}`
743
+ );
744
+ },
745
+ (error) => {
746
+ console.error(`[aui-mcp] reload failed:`, error);
747
+ }
748
+ );
749
+ }
750
+ if (transport === "stdio") {
751
+ await server.connect(new StdioServerTransport());
752
+ return;
753
+ }
754
+ const transports = /* @__PURE__ */ new Map();
755
+ const httpServer = http.createServer(async (req, res) => {
756
+ try {
757
+ const url = new URL(req.url ?? "/", `http://${req.headers.host ?? "localhost"}`);
758
+ if (req.method === "GET" && url.pathname === "/catalog") {
759
+ res.writeHead(200, { "Content-Type": "application/json" });
760
+ res.end(JSON.stringify(getRawCatalog(), null, 2));
761
+ return;
762
+ }
763
+ if (req.method === "POST" && url.pathname === "/call-tool") {
764
+ const chunks = [];
765
+ for await (const chunk of req) {
766
+ chunks.push(chunk);
767
+ }
768
+ let payload = {};
769
+ try {
770
+ const raw = Buffer.concat(chunks).toString("utf-8");
771
+ payload = raw ? JSON.parse(raw) : {};
772
+ } catch (error) {
773
+ console.error("[aui-mcp] invalid /call-tool payload:", error);
774
+ res.writeHead(400, { "Content-Type": "application/json" });
775
+ res.end(JSON.stringify({ error: "Invalid JSON payload" }));
776
+ return;
777
+ }
778
+ const name = payload.name;
779
+ const args = payload.arguments ?? payload.args ?? {};
780
+ if (!name || typeof name !== "string") {
781
+ res.writeHead(400, { "Content-Type": "application/json" });
782
+ res.end(JSON.stringify({ error: "Missing tool name" }));
783
+ return;
784
+ }
785
+ const exec = await executeCatalogTool(currentManifest, name, args);
786
+ if (!exec.ok) {
787
+ res.writeHead(400, { "Content-Type": "application/json" });
788
+ res.end(JSON.stringify({ error: exec.message }));
789
+ return;
790
+ }
791
+ res.writeHead(200, { "Content-Type": "application/json" });
792
+ res.end(JSON.stringify(exec.value));
793
+ return;
794
+ }
795
+ if (req.method === "GET" && url.pathname === "/sse") {
796
+ const transportInstance = new SSEServerTransport("/message", res);
797
+ transports.set(transportInstance.sessionId, transportInstance);
798
+ req.on("close", () => {
799
+ transports.delete(transportInstance.sessionId);
800
+ });
801
+ await server.connect(transportInstance);
802
+ return;
803
+ }
804
+ if (req.method === "POST" && url.pathname === "/message") {
805
+ const sessionId = url.searchParams.get("sessionId");
806
+ if (!sessionId) {
807
+ res.writeHead(400).end("Missing sessionId");
808
+ return;
809
+ }
810
+ const transportInstance = transports.get(sessionId);
811
+ if (!transportInstance) {
812
+ res.writeHead(404).end("Unknown sessionId");
813
+ return;
814
+ }
815
+ await transportInstance.handlePostMessage(req, res);
816
+ return;
817
+ }
818
+ res.writeHead(404).end("Not found");
819
+ } catch (error) {
820
+ console.error("[aui-mcp] request handler error:", error);
821
+ res.writeHead(500).end("Internal server error");
822
+ }
823
+ });
824
+ httpServer.on("error", (error) => {
825
+ console.error("[aui-mcp] http server error:", error);
826
+ });
827
+ httpServer.listen(port, () => {
828
+ console.error(`[aui-mcp] SSE listening on http://localhost:${port}/sse`);
829
+ });
830
+ }
831
+
832
+ // src/rspack.ts
833
+ function isRecord(value) {
834
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
835
+ }
836
+ var AuiMcpRspackPlugin = class {
837
+ options;
838
+ devServerStarted = false;
839
+ constructor(options = {}) {
840
+ this.options = options;
841
+ }
842
+ apply(compiler) {
843
+ const pluginName = "aui-mcp-server";
844
+ const mfOptions = this.options.mfOptions;
845
+ if (!mfOptions || typeof mfOptions !== "object") {
846
+ throw new Error(`[${pluginName}] Missing required option: mfOptions`);
847
+ }
848
+ this.injectAfterGenerateTypesHook(compiler, mfOptions);
849
+ if (this.options.applyModuleFederationPlugin !== false) {
850
+ new ModuleFederationPlugin(mfOptions).apply(compiler);
851
+ }
852
+ }
853
+ getOutDir(compiler, compilation) {
854
+ const rawOutDir = this.options.outDir ?? compilation?.outputOptions?.path ?? compiler?.options?.output?.path ?? compiler?.outputPath ?? "dist";
855
+ const context = process.cwd();
856
+ return path2.isAbsolute(rawOutDir) ? rawOutDir : path2.resolve(context, rawOutDir);
857
+ }
858
+ isDev(compiler) {
859
+ if (this.options.dev !== void 0) return this.options.dev;
860
+ const mode = compiler?.options?.mode;
861
+ if (mode === "development") return true;
862
+ if (compiler?.watchMode) return true;
863
+ return false;
864
+ }
865
+ readCompiledTypes(compiler, outDir, componentName) {
866
+ const entryPath = path2.join(outDir, "@mf-types", `${componentName}.d.ts`);
867
+ let content = this.readFileFromCompilerOrNodeFs(compiler, entryPath);
868
+ if (!content) {
869
+ const compiledPath = path2.join(outDir, "@mf-types", "compiled-types", `${componentName}.d.ts`);
870
+ content = this.readFileFromCompilerOrNodeFs(compiler, compiledPath);
871
+ }
872
+ if (!content) return void 0;
873
+ const match = content.match(/export \* from '\.\/(compiled-types\/[^']+)'/);
874
+ if (!match) return content;
875
+ const realPath = path2.join(outDir, "@mf-types", `${match[1]}.d.ts`);
876
+ return this.readFileFromCompilerOrNodeFs(compiler, realPath);
877
+ }
878
+ readFileFromCompilerOrNodeFs(compiler, absPath) {
879
+ const ofs = compiler?.outputFileSystem;
880
+ const readFileSync2 = ofs?.readFileSync;
881
+ if (typeof readFileSync2 === "function") {
882
+ try {
883
+ const resolved = readFileSync2.call(ofs, absPath);
884
+ return Buffer.isBuffer(resolved) ? resolved.toString("utf-8") : String(resolved);
885
+ } catch {
886
+ }
887
+ }
888
+ try {
889
+ if (fs4.existsSync(absPath)) return fs4.readFileSync(absPath, "utf-8");
890
+ } catch {
891
+ }
892
+ return void 0;
893
+ }
894
+ async readJsonFromCompilerOrNodeFs(compiler, absPath) {
895
+ const ofs = compiler?.outputFileSystem;
896
+ const readFile = ofs?.readFile;
897
+ if (typeof readFile === "function") {
898
+ const data = await new Promise((resolve, reject) => {
899
+ readFile.call(ofs, absPath, (err, buf) => {
900
+ if (err) return reject(err);
901
+ const text2 = Buffer.isBuffer(buf) ? buf.toString("utf-8") : String(buf);
902
+ resolve(text2);
903
+ });
904
+ });
905
+ return JSON.parse(data);
906
+ }
907
+ const text = await promises.readFile(absPath, "utf-8");
908
+ return JSON.parse(text);
909
+ }
910
+ async generateCatalogAndMcpAssetsAfterTypes(compiler) {
911
+ const outDir = this.getOutDir(compiler, void 0);
912
+ const statsAssetName = this.options.statsAssetName ?? "mf-stats.json";
913
+ const statsPath = path2.join(outDir, statsAssetName);
914
+ let stats;
915
+ try {
916
+ stats = await this.readJsonFromCompilerOrNodeFs(compiler, statsPath);
917
+ } catch (err) {
918
+ console.error(
919
+ `[aui-mcp-server] Failed to read ${statsAssetName} from ${statsPath}:`,
920
+ err instanceof Error ? err.message : err
921
+ );
922
+ return void 0;
923
+ }
924
+ const componentNames = (stats.exposes ?? []).filter((e) => e && typeof e === "object" && typeof e.path === "string" && !e.path.endsWith(".data")).map((e) => String(e.name)).filter(Boolean);
925
+ const maxAttempts = 20;
926
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
927
+ const missing = componentNames.filter((name) => !this.readCompiledTypes(compiler, outDir, name));
928
+ if (missing.length === 0) break;
929
+ if (attempt === maxAttempts) break;
930
+ await new Promise((r) => setTimeout(r, Math.min(200 * attempt, 1e3)));
931
+ }
932
+ const { catalog, issues, missingJsdocComponents } = buildCatalogFromStats({
933
+ stats,
934
+ readCompiledTypes: (componentName) => this.readCompiledTypes(compiler, outDir, componentName)
935
+ });
936
+ if (issues.length > 0) {
937
+ const head = issues.slice(0, 10).map((i) => `${i.componentName ?? "unknown"}: ${i.message}`).join("; ");
938
+ const tail = issues.length > 10 ? ` ... (+${issues.length - 10})` : "";
939
+ console.warn(`[aui-mcp-server] catalog issues: ${head}${tail}`);
940
+ }
941
+ if (missingJsdocComponents.length > 0) {
942
+ const names = missingJsdocComponents.slice(0, 10).join(", ");
943
+ const tail = missingJsdocComponents.length > 10 ? ` ... (+${missingJsdocComponents.length - 10})` : "";
944
+ console.warn(
945
+ `[aui-mcp-server] missing @aui-component JSDoc: ${names}${tail}. Please run skills/aui-jsdoc-gen to write back to source files and retry.`
946
+ );
947
+ }
948
+ if (!isRecord(catalog) || Object.keys(catalog).length === 0) return void 0;
949
+ const catalogAssetName = this.options.catalogAssetName ?? "aui-x-catalog.json";
950
+ const catalogPath = path2.resolve(outDir, catalogAssetName);
951
+ await promises.mkdir(path2.dirname(catalogPath), { recursive: true });
952
+ await promises.writeFile(catalogPath, JSON.stringify(catalog, null, 2) + "\n", "utf-8");
953
+ await generateAuiMcpAssets({
954
+ catalogPath,
955
+ outDir,
956
+ mcpDirname: this.options.mcpDirname ?? "mcp",
957
+ emitServer: this.options.emitServer !== false
958
+ });
959
+ return catalogPath;
960
+ }
961
+ injectAfterGenerateTypesHook(compiler, mfOptions) {
962
+ const pluginName = "aui-mcp-server";
963
+ const rawDts = mfOptions.dts ?? {};
964
+ mfOptions.dts = isRecord(rawDts) ? rawDts : {};
965
+ const dts = mfOptions.dts;
966
+ const rawGenerateTypes = dts.generateTypes ?? {};
967
+ dts.generateTypes = isRecord(rawGenerateTypes) ? rawGenerateTypes : {};
968
+ const generateTypes = dts.generateTypes;
969
+ if (generateTypes.__auiMcpInjectedAfterGenerate) return;
970
+ generateTypes.__auiMcpInjectedAfterGenerate = true;
971
+ const userAfter = generateTypes.afterGenerate;
972
+ generateTypes.afterGenerate = async (...args) => {
973
+ try {
974
+ await Promise.resolve(userAfter?.(...args));
975
+ } catch (err) {
976
+ console.error(`[${pluginName}] user afterGenerate failed:`, err);
977
+ }
978
+ let catalogPath;
979
+ try {
980
+ catalogPath = await this.generateCatalogAndMcpAssetsAfterTypes(compiler);
981
+ } catch (err) {
982
+ console.error(`[${pluginName}] afterGenerate (aui) failed:`, err);
983
+ return;
984
+ }
985
+ if (!catalogPath) return;
986
+ if (!this.isDev(compiler)) return;
987
+ if (this.devServerStarted) return;
988
+ this.devServerStarted = true;
989
+ try {
990
+ await serveAuiMcpServer({
991
+ catalogPath,
992
+ watch: true,
993
+ // Dev defaults to SSE so the sample can proxy to /api/tools.
994
+ transport: this.options.transport ?? "sse",
995
+ port: this.options.port ?? 8001
996
+ });
997
+ } catch (err) {
998
+ console.error(`[${pluginName}] start dev server failed:`, err);
999
+ }
1000
+ };
1001
+ }
1002
+ };
1003
+ function auiMcpRsbuildPlugin(options = {}) {
1004
+ return {
1005
+ // Rslib `format: 'mf'` expects this plugin to exist in config.plugins.
1006
+ name: "rsbuild:module-federation-enhanced",
1007
+ setup(api) {
1008
+ const mfOptions = options.mfOptions;
1009
+ if (!mfOptions || typeof mfOptions !== "object") {
1010
+ throw new Error("[aui-mcp-server] Missing required option: mfOptions");
1011
+ }
1012
+ try {
1013
+ const require2 = createRequire(import.meta.url);
1014
+ const { pluginModuleFederation } = require2("@module-federation/rsbuild-plugin");
1015
+ pluginModuleFederation(mfOptions, { target: "dual" }).setup?.(api);
1016
+ } catch (err) {
1017
+ throw new Error(
1018
+ `[aui-mcp-server] Failed to load @module-federation/rsbuild-plugin. Please install it. ${err instanceof Error ? err.message : String(err)}`
1019
+ );
1020
+ }
1021
+ api.modifyRspackConfig?.((config, utils) => {
1022
+ utils.appendPlugins(
1023
+ new AuiMcpRspackPlugin({
1024
+ ...options,
1025
+ mfOptions,
1026
+ // MF plugin is handled by @module-federation/rsbuild-plugin above.
1027
+ applyModuleFederationPlugin: false
1028
+ })
1029
+ );
1030
+ return config;
1031
+ });
1032
+ }
1033
+ };
1034
+ }
1035
+
1036
+ export { auiMcpRsbuildPlugin };
1037
+ //# sourceMappingURL=rsbuild.js.map
1038
+ //# sourceMappingURL=rsbuild.js.map