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