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
package/src/server.ts ADDED
@@ -0,0 +1,391 @@
1
+ import http from 'node:http';
2
+ import { promises as fs } from 'node:fs';
3
+
4
+ import chokidar from 'chokidar';
5
+
6
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
7
+ import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js';
8
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
9
+ import {
10
+ CallToolRequestSchema,
11
+ ListToolsRequestSchema,
12
+ type Tool,
13
+ } from '@modelcontextprotocol/sdk/types.js';
14
+
15
+ import { init, loadRemote } from '@module-federation/runtime';
16
+
17
+ // In Node.js there is no `self` global by default, but many MF remoteEntry
18
+ // bundles still reference it (especially browser-first builds). Polyfill it so
19
+ // MF runtime can eval remoteEntry safely.
20
+ const __mfGlobal = globalThis as any;
21
+ if (typeof __mfGlobal.self === 'undefined') __mfGlobal.self = __mfGlobal;
22
+ if (typeof __mfGlobal.window === 'undefined') __mfGlobal.window = __mfGlobal;
23
+
24
+ import type { AuiMcpManifest, AuiXCatalog, AuiXToolDefinition } from './types';
25
+ import { readJsonFile, toToolName } from './utils';
26
+
27
+ export type TransportType = 'stdio' | 'sse';
28
+
29
+ export interface ServeAuiMcpServerOptions {
30
+ /** If provided, server will load tools from manifest.json (recommended for prod) */
31
+ manifestPath?: string;
32
+ /** If provided, server will derive tools from aui-x-catalog.json (recommended for dev) */
33
+ catalogPath?: string;
34
+ /** Enable hot reload for manifest/catalog */
35
+ watch?: boolean;
36
+ /** Transport type */
37
+ transport?: TransportType;
38
+ /** SSE port (only for transport=sse) */
39
+ port?: number;
40
+ }
41
+
42
+ const EMPTY_INPUT_SCHEMA: Tool['inputSchema'] = {
43
+ type: 'object',
44
+ properties: {},
45
+ required: [],
46
+ };
47
+
48
+ async function loadManifestFromFile(manifestPath: string): Promise<AuiMcpManifest> {
49
+ const manifest = await readJsonFile<AuiMcpManifest>(manifestPath);
50
+ if (!manifest || typeof manifest !== 'object' || (manifest as any).schemaVersion !== 1) {
51
+ throw new Error(`Invalid manifest: ${manifestPath}`);
52
+ }
53
+ return manifest;
54
+ }
55
+
56
+ async function loadManifestFromCatalog(catalogPath: string): Promise<AuiMcpManifest> {
57
+ const catalog = await readJsonFile<AuiXCatalog>(catalogPath);
58
+ if (!catalog || typeof catalog !== 'object' || Array.isArray(catalog)) {
59
+ throw new Error(`Invalid aui-x-catalog.json: ${catalogPath}`);
60
+ }
61
+
62
+ return {
63
+ schemaVersion: 1,
64
+ tools: Object.entries(catalog).map(([componentName, entry]) => ({
65
+ name: toToolName(componentName),
66
+ componentName,
67
+ description: entry.description,
68
+ inputSchema: EMPTY_INPUT_SCHEMA,
69
+ entry,
70
+ })),
71
+ };
72
+ }
73
+
74
+ function buildToolMap(manifest: AuiMcpManifest): Map<string, (typeof manifest.tools)[number]> {
75
+ const map = new Map<string, (typeof manifest.tools)[number]>();
76
+ for (const tool of manifest.tools ?? []) {
77
+ map.set(tool.name, tool);
78
+ }
79
+ return map;
80
+ }
81
+
82
+ async function executeCatalogTool(
83
+ manifest: AuiMcpManifest,
84
+ toolName: string,
85
+ args: unknown
86
+ ): Promise<{ ok: true; value: unknown } | { ok: false; message: string }> {
87
+ const tools = buildToolMap(manifest);
88
+ const tool = tools.get(toolName);
89
+
90
+ if (!tool) {
91
+ return { ok: false, message: `Tool not found: ${toolName}` };
92
+ }
93
+
94
+ const entry = tool.entry;
95
+ const xLoader = entry['x-loader'];
96
+ const xTools = (entry['x-tools'] ?? []) as AuiXToolDefinition[];
97
+
98
+ if (!xLoader) {
99
+ return { ok: false, message: `No x-loader config for tool: ${toolName}` };
100
+ }
101
+
102
+ if (!xTools.length) {
103
+ return { ok: false, message: `No x-tools defined for component: ${tool.componentName}` };
104
+ }
105
+
106
+ const selectedTool = xTools.find((t) => t.name === toolName) ?? xTools[0];
107
+
108
+ if (!selectedTool) {
109
+ return { ok: false, message: `No matching x-tools entry for tool: ${toolName}` };
110
+ }
111
+
112
+ // Prefer MF remote loader when loader path is present; otherwise fall back to local logic file.
113
+ const loaderPath = selectedTool.loader.slice(2); // Remove "./" prefix
114
+ const logicFilePath = selectedTool.logicFilePath;
115
+
116
+ try {
117
+ const inputArgs = (args && typeof args === 'object') ? (args as Record<string, unknown>) : {};
118
+
119
+ // MF 2.0 remote loader path (e.g. "./ArtistEvents.data")
120
+ if (loaderPath) {
121
+ const scope = xLoader.scope;
122
+ const remoteEntryUrl = xLoader.url;
123
+
124
+ // Initialize MF runtime for this remote; repeated init calls are tolerated.
125
+ try {
126
+ init({
127
+ name: 'aui-mcp-mf-host',
128
+ remotes: [
129
+ {
130
+ name: scope,
131
+ entry: remoteEntryUrl,
132
+ },
133
+ ],
134
+ });
135
+ } catch {
136
+ // Ignore init errors (e.g. duplicate init); runtime will reuse existing instance.
137
+ }
138
+
139
+ const remoteKey = `${scope}/${loaderPath}`;
140
+ const mod: any = await loadRemote(remoteKey);
141
+ const loaderFn = mod?.loader ?? mod?.default;
142
+
143
+ if (typeof loaderFn !== 'function') {
144
+ return {
145
+ ok: false,
146
+ message: `Loaded MF module '${remoteKey}' does not export a callable loader`,
147
+ };
148
+ }
149
+
150
+ const result = await loaderFn(inputArgs);
151
+ return { ok: true, value: result };
152
+ }
153
+
154
+ // Fallback: execute local logic file directly (dev/non-MF mode)
155
+ if (logicFilePath) {
156
+ const moduleUrl = new URL(`file://${logicFilePath}`);
157
+ const mod: any = await import(moduleUrl.href);
158
+ const loaderFn = mod?.loader ?? mod?.default;
159
+
160
+ if (typeof loaderFn !== 'function') {
161
+ return {
162
+ ok: false,
163
+ message: `Local logic file '${logicFilePath}' does not export a callable loader`,
164
+ };
165
+ }
166
+
167
+ const result = await loaderFn(inputArgs);
168
+ return { ok: true, value: result };
169
+ }
170
+
171
+ return { ok: false, message: `No executable loader found for tool: ${toolName}` };
172
+ } catch (error) {
173
+ // eslint-disable-next-line no-console
174
+ console.error('[aui-mcp] tool execution failed:', error);
175
+ return {
176
+ ok: false,
177
+ message: error instanceof Error ? error.message : String(error),
178
+ };
179
+ }
180
+ }
181
+
182
+ async function createServer(getManifest: () => AuiMcpManifest): Promise<Server> {
183
+ const server = new Server(
184
+ { name: 'aui-x-catalog', version: '0.1.0' },
185
+ { capabilities: { tools: {} } }
186
+ );
187
+
188
+ server.setRequestHandler(ListToolsRequestSchema, async () => {
189
+ const manifest = getManifest();
190
+ return {
191
+ tools: (manifest.tools ?? []).map((t) => ({
192
+ name: t.name,
193
+ description: t.description,
194
+ inputSchema: t.inputSchema ?? EMPTY_INPUT_SCHEMA,
195
+ })),
196
+ };
197
+ });
198
+
199
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
200
+ const manifest = getManifest();
201
+ const exec = await executeCatalogTool(manifest, request.params.name, request.params.arguments);
202
+
203
+ if (!exec.ok) {
204
+ return {
205
+ content: [{ type: 'text' as const, text: exec.message }],
206
+ isError: true,
207
+ };
208
+ }
209
+
210
+ return {
211
+ // Return raw loader result as JSON for the Agent to consume.
212
+ content: [{ type: 'text' as const, text: JSON.stringify(exec.value) }],
213
+ };
214
+ });
215
+
216
+ return server;
217
+ }
218
+
219
+ async function startWatch(
220
+ filePath: string,
221
+ onReload: () => Promise<void>,
222
+ onReloadError: (error: unknown) => void
223
+ ): Promise<chokidar.FSWatcher> {
224
+ await fs.access(filePath);
225
+
226
+ const watcher = chokidar.watch(filePath, { ignoreInitial: true });
227
+ watcher.on('change', async () => {
228
+ try {
229
+ await onReload();
230
+ } catch (err) {
231
+ onReloadError(err);
232
+ }
233
+ });
234
+
235
+ return watcher;
236
+ }
237
+
238
+ export async function serveAuiMcpServer(options: ServeAuiMcpServerOptions = {}): Promise<void> {
239
+ const transport: TransportType = options.transport ?? 'stdio';
240
+ const port = options.port ?? 8001;
241
+
242
+ const manifestPath = options.manifestPath;
243
+ const catalogPath = options.catalogPath;
244
+
245
+ if (!manifestPath && !catalogPath) {
246
+ throw new Error('Either manifestPath or catalogPath is required');
247
+ }
248
+
249
+ const resolvedCatalogPath = catalogPath ?? '';
250
+ let currentManifest: AuiMcpManifest = manifestPath
251
+ ? await loadManifestFromFile(manifestPath)
252
+ : await loadManifestFromCatalog(resolvedCatalogPath);
253
+
254
+ const getRawCatalog = (): AuiXCatalog => {
255
+ const catalog: AuiXCatalog = {};
256
+ for (const tool of currentManifest.tools ?? []) {
257
+ if (tool.entry) {
258
+ catalog[tool.componentName] = tool.entry;
259
+ }
260
+ }
261
+ return catalog;
262
+ };
263
+
264
+ const server = await createServer(() => currentManifest);
265
+
266
+ if (options.watch) {
267
+ const watchPath = manifestPath ?? resolvedCatalogPath;
268
+
269
+ await startWatch(
270
+ watchPath,
271
+ async () => {
272
+ currentManifest = manifestPath
273
+ ? await loadManifestFromFile(manifestPath)
274
+ : await loadManifestFromCatalog(resolvedCatalogPath);
275
+
276
+ await server.sendToolListChanged();
277
+ // eslint-disable-next-line no-console
278
+ console.error(
279
+ `[aui-mcp] reloaded: ${(currentManifest.tools ?? []).length} tools from ${watchPath}`
280
+ );
281
+ },
282
+ (error) => {
283
+ // eslint-disable-next-line no-console
284
+ console.error(`[aui-mcp] reload failed:`, error);
285
+ }
286
+ );
287
+ }
288
+
289
+ if (transport === 'stdio') {
290
+ await server.connect(new StdioServerTransport());
291
+ return;
292
+ }
293
+
294
+ const transports = new Map<string, SSEServerTransport>();
295
+
296
+ const httpServer = http.createServer(async (req, res) => {
297
+ try {
298
+ const url = new URL(req.url ?? '/', `http://${req.headers.host ?? 'localhost'}`);
299
+
300
+ if (req.method === 'GET' && url.pathname === '/catalog') {
301
+ res.writeHead(200, { 'Content-Type': 'application/json' });
302
+ res.end(JSON.stringify(getRawCatalog(), null, 2));
303
+ return;
304
+ }
305
+
306
+ if (req.method === 'POST' && url.pathname === '/call-tool') {
307
+ const chunks: Buffer[] = [];
308
+ for await (const chunk of req) {
309
+ chunks.push(chunk as Buffer);
310
+ }
311
+
312
+ let payload: any = {};
313
+ try {
314
+ const raw = Buffer.concat(chunks).toString('utf-8');
315
+ payload = raw ? JSON.parse(raw) : {};
316
+ } catch (error) {
317
+ // eslint-disable-next-line no-console
318
+ console.error('[aui-mcp] invalid /call-tool payload:', error);
319
+ res.writeHead(400, { 'Content-Type': 'application/json' });
320
+ res.end(JSON.stringify({ error: 'Invalid JSON payload' }));
321
+ return;
322
+ }
323
+
324
+ const name = payload.name as string | undefined;
325
+ const args = payload.arguments ?? payload.args ?? {};
326
+
327
+ if (!name || typeof name !== 'string') {
328
+ res.writeHead(400, { 'Content-Type': 'application/json' });
329
+ res.end(JSON.stringify({ error: 'Missing tool name' }));
330
+ return;
331
+ }
332
+
333
+ const exec = await executeCatalogTool(currentManifest, name, args);
334
+
335
+ if (!exec.ok) {
336
+ res.writeHead(400, { 'Content-Type': 'application/json' });
337
+ res.end(JSON.stringify({ error: exec.message }));
338
+ return;
339
+ }
340
+
341
+ res.writeHead(200, { 'Content-Type': 'application/json' });
342
+ res.end(JSON.stringify(exec.value));
343
+ return;
344
+ }
345
+
346
+ if (req.method === 'GET' && url.pathname === '/sse') {
347
+ const transportInstance = new SSEServerTransport('/message', res);
348
+ transports.set(transportInstance.sessionId, transportInstance);
349
+ req.on('close', () => {
350
+ transports.delete(transportInstance.sessionId);
351
+ });
352
+ await server.connect(transportInstance);
353
+ return;
354
+ }
355
+
356
+ if (req.method === 'POST' && url.pathname === '/message') {
357
+ const sessionId = url.searchParams.get('sessionId');
358
+ if (!sessionId) {
359
+ res.writeHead(400).end('Missing sessionId');
360
+ return;
361
+ }
362
+
363
+ const transportInstance = transports.get(sessionId);
364
+ if (!transportInstance) {
365
+ res.writeHead(404).end('Unknown sessionId');
366
+ return;
367
+ }
368
+
369
+ await transportInstance.handlePostMessage(req, res);
370
+ return;
371
+ }
372
+
373
+ res.writeHead(404).end('Not found');
374
+ } catch (error) {
375
+ // eslint-disable-next-line no-console
376
+ console.error('[aui-mcp] request handler error:', error);
377
+ res.writeHead(500).end('Internal server error');
378
+ }
379
+ });
380
+
381
+ httpServer.on('error', (error) => {
382
+ // Keep dev workflow resilient: don't crash the whole build process.
383
+ // eslint-disable-next-line no-console
384
+ console.error('[aui-mcp] http server error:', error);
385
+ });
386
+
387
+ httpServer.listen(port, () => {
388
+ // eslint-disable-next-line no-console
389
+ console.error(`[aui-mcp] SSE listening on http://localhost:${port}/sse`);
390
+ });
391
+ }
@@ -0,0 +1,51 @@
1
+ export function buildServerSource(): string {
2
+ // NOTE: This file is meant to be bundled by esbuild in the generator.
3
+ // It reads manifest.json next to itself.
4
+ return `import { readFileSync } from 'node:fs';
5
+ import { dirname, join } from 'node:path';
6
+ import { fileURLToPath } from 'node:url';
7
+
8
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
9
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
10
+ import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';
11
+
12
+ const __dirname = dirname(fileURLToPath(import.meta.url));
13
+ const manifestPath = process.env.AUI_MCP_MANIFEST ?? join(__dirname, 'manifest.json');
14
+ const manifest = JSON.parse(readFileSync(manifestPath, 'utf-8'));
15
+
16
+ const server = new Server(
17
+ { name: 'aui-x-catalog', version: '0.1.0' },
18
+ { capabilities: { tools: {} } }
19
+ );
20
+
21
+ server.setRequestHandler(ListToolsRequestSchema, async () => {
22
+ return {
23
+ tools: (manifest.tools ?? []).map((tool) => ({
24
+ name: tool.name,
25
+ description: tool.description,
26
+ inputSchema: tool.inputSchema ?? { type: 'object', properties: {}, required: [] },
27
+ })),
28
+ };
29
+ });
30
+
31
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
32
+ const toolName = request.params?.name;
33
+ const tool = (manifest.tools ?? []).find((t) => t.name === toolName);
34
+ if (!tool) {
35
+ throw new Error('Tool not found: ' + toolName);
36
+ }
37
+
38
+ return {
39
+ content: [
40
+ {
41
+ type: 'text',
42
+ text: JSON.stringify(tool.entry, null, 2),
43
+ },
44
+ ],
45
+ };
46
+ });
47
+
48
+ const transport = new StdioServerTransport();
49
+ await server.connect(transport);
50
+ `;
51
+ }
package/src/types.ts ADDED
@@ -0,0 +1,195 @@
1
+ /**
2
+ * Shared types for AUI-X catalog & MCP artifacts.
3
+ */
4
+
5
+ // ─── MF Stats JSON (dist/mf-stats.json) ─────────────────────────────────────
6
+
7
+ export interface MfStatsExposes {
8
+ path: string; // e.g. "./ArtistProfile"
9
+ id: string; // e.g. "artist_profile_remote:ArtistProfile"
10
+ name: string; // e.g. "ArtistProfile"
11
+ file: string; // e.g. "src/ArtistProfile"
12
+ loader?: string; // MF 2.0 data loader path
13
+ }
14
+
15
+ export interface MfStats {
16
+ id: string; // MF scope name, e.g. "artist_profile_remote"
17
+ name: string;
18
+ metaData: {
19
+ remoteEntry: { name: string; path: string; type: string };
20
+ globalName: string;
21
+ publicPath: string;
22
+ };
23
+ exposes: MfStatsExposes[];
24
+ }
25
+
26
+ // ─── JSDoc parsing result (from compiled d.ts) ──────────────────────────────
27
+
28
+ export interface AuiActionDefinition {
29
+ /**
30
+ * Action event name sent to the server (e.g. "wish_more_shows").
31
+ */
32
+ name: string;
33
+ /**
34
+ * Human-readable description for the LLM to understand when to trigger this action.
35
+ */
36
+ description?: string;
37
+ /**
38
+ * Optional list of context key names sent in the action payload.
39
+ */
40
+ contextKeys?: string[];
41
+ /**
42
+ * If set, the Agent should reply with this component name when receiving this action.
43
+ */
44
+ replyWith?: string;
45
+ }
46
+
47
+ export interface AuiJsDocTags {
48
+ isAuiComponent: boolean;
49
+ description?: string;
50
+ authRequired?: boolean;
51
+ fallback?: string;
52
+ example?: unknown;
53
+ tools?: { name: string; logicFile: string }[];
54
+ /** Parsed @action tags – formally declares server-event actions this component can dispatch */
55
+ actions?: AuiActionDefinition[];
56
+ }
57
+
58
+ export interface PropDefinition {
59
+ name: string;
60
+ type: string;
61
+ optional: boolean;
62
+ description?: string;
63
+ }
64
+
65
+ export interface RemoteContext {
66
+ stats: MfStats;
67
+ expose: MfStatsExposes;
68
+ jsdoc: AuiJsDocTags;
69
+ props: PropDefinition[];
70
+ componentName: string;
71
+ remoteEntryUrl: string;
72
+ loaderExpose?: MfStatsExposes;
73
+ }
74
+
75
+ // ─── aui-x-catalog.json ─────────────────────────────────────────────────────
76
+
77
+ export interface AuiXLoaderConfig {
78
+ type: 'module-federation';
79
+ /** remoteEntry.js full url */
80
+ url: string;
81
+ /** MF scope name */
82
+ scope: string;
83
+ /** MF exposed module path, e.g. "./ArtistProfile" */
84
+ module: string;
85
+ }
86
+
87
+ export interface AuiXToolDefinition {
88
+ /**
89
+ * Logical tool name exposed to MCP clients.
90
+ * Typically follows the `get_<component>_...` convention.
91
+ */
92
+ name: string;
93
+
94
+ /** Human-readable description for the LLM. */
95
+ description: string;
96
+
97
+ /**
98
+ * MF data loader exposed module path, e.g. "./ArtistEvents.data".
99
+ *
100
+ * This is consumed at runtime by the MCP server together with the
101
+ * component-level `x-loader` config to dynamically load and execute
102
+ * the corresponding MF 2.0 loader module.
103
+ */
104
+ loader: string;
105
+
106
+ /** JSON Schema-style parameter definition for MCP tool input. */
107
+ parameters: Record<string, unknown>;
108
+
109
+ /** Optional: local logic file path for dev-server or non-MF execution. */
110
+ logicFilePath?: string;
111
+ }
112
+
113
+ export interface AuiXCatalogEntry {
114
+ type: 'object';
115
+ description: string;
116
+
117
+ /** JSON Schema properties */
118
+ properties: Record<
119
+ string,
120
+ | {
121
+ type: 'object';
122
+ description?: string;
123
+ properties: {
124
+ literalString?: { type: 'string' };
125
+ path?: { type: 'string' };
126
+ };
127
+ }
128
+ | {
129
+ type: 'object';
130
+ description?: string;
131
+ properties: {
132
+ name: { type: 'string' };
133
+ context?: {
134
+ type: 'array';
135
+ items: {
136
+ type: 'object';
137
+ properties: {
138
+ key: { type: 'string' };
139
+ value: { type: 'object' };
140
+ };
141
+ required: ['key', 'value'];
142
+ };
143
+ };
144
+ };
145
+ required: ['name'];
146
+ }
147
+ >;
148
+
149
+ required?: string[];
150
+
151
+ 'x-loader': AuiXLoaderConfig;
152
+ 'x-example'?: unknown;
153
+ 'x-auth-required'?: boolean;
154
+ 'x-fallback'?: string;
155
+ 'x-tools'?: AuiXToolDefinition[];
156
+ }
157
+
158
+ /** Full aui-x-catalog.json structure */
159
+ export type AuiXCatalog = Record<string, AuiXCatalogEntry>;
160
+
161
+ // ─── MCP manifest (dist/mcp/manifest.json) ──────────────────────────────────
162
+
163
+ export interface AuiMcpTool {
164
+ /** MCP tool name */
165
+ name: string;
166
+ /** Component name (key in aui-x-catalog.json) */
167
+ componentName: string;
168
+ /** Display description for LLM */
169
+ description?: string;
170
+ /** MCP input schema (JSON Schema) */
171
+ inputSchema: Record<string, unknown>;
172
+ /** Full catalog entry (includes x-loader, x-example, ...) */
173
+ entry: AuiXCatalogEntry;
174
+ }
175
+
176
+ export interface AuiMcpManifest {
177
+ schemaVersion: 1;
178
+ tools: AuiMcpTool[];
179
+ }
180
+
181
+ export interface GenerateAuiMcpAssetsOptions {
182
+ /** Path to aui-x-catalog.json. Defaults to "aui-x-catalog.json" in cwd */
183
+ catalogPath?: string;
184
+ /** dist directory of remote build output, defaults to dist */
185
+ outDir?: string;
186
+ /** sub dir inside outDir, defaults to mcp */
187
+ mcpDirname?: string;
188
+ /** Whether to emit an executable MCP server bundle */
189
+ emitServer?: boolean;
190
+ }
191
+
192
+ export interface GenerateAuiMcpAssetsResult {
193
+ manifestPath: string;
194
+ serverPath?: string;
195
+ }
package/src/utils.ts ADDED
@@ -0,0 +1,29 @@
1
+ import { promises as fs } from 'node:fs';
2
+
3
+ export async function fileExists(filePath: string): Promise<boolean> {
4
+ try {
5
+ await fs.access(filePath);
6
+ return true;
7
+ } catch {
8
+ return false;
9
+ }
10
+ }
11
+
12
+ export async function readJsonFile<T>(filePath: string): Promise<T> {
13
+ const raw = await fs.readFile(filePath, 'utf-8');
14
+ return JSON.parse(raw) as T;
15
+ }
16
+
17
+ export function toSnakeCase(input: string): string {
18
+ return input
19
+ .trim()
20
+ .replace(/[\s-]+/g, '_')
21
+ .replace(/([a-z0-9])([A-Z])/g, '$1_$2')
22
+ .replace(/([A-Z]+)([A-Z][a-z])/g, '$1_$2')
23
+ .replace(/_+/g, '_')
24
+ .toLowerCase();
25
+ }
26
+
27
+ export function toToolName(componentName: string): string {
28
+ return `get_${toSnakeCase(componentName)}`;
29
+ }
package/test.js ADDED
@@ -0,0 +1,16 @@
1
+ import { createInstance, } from '@module-federation/runtime';
2
+ const host = createInstance({
3
+ name:'host',
4
+ remotes:[
5
+ {
6
+ name:'music_remotes',
7
+ entry:'http://localhost:5174/mf-manifest.json'
8
+ }
9
+ ]
10
+ });
11
+
12
+ host.loadRemote('music_remotes/ArtistEvents.data').then(({loader})=>{
13
+ loader({artist:'xxxx',region:'cn'}).then((data)=>{
14
+ console.log('loader data',data)
15
+ })
16
+ });
package/tsconfig.json ADDED
@@ -0,0 +1,19 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "lib": ["ES2022", "DOM"],
5
+ "module": "ESNext",
6
+ "moduleResolution": "bundler",
7
+ "jsx": "react-jsx",
8
+ "strict": true,
9
+ "noUncheckedIndexedAccess": true,
10
+ "noEmit": true,
11
+ "esModuleInterop": true,
12
+ "skipLibCheck": true,
13
+ "forceConsistentCasingInFileNames": true,
14
+ "resolveJsonModule": true,
15
+ "isolatedModules": true
16
+ },
17
+ "include": ["src/**/*"],
18
+ "exclude": ["node_modules", "dist"]
19
+ }