@leanmcp/core 0.3.8 → 0.3.10
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/dist/index.d.mts +13 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +173 -15
- package/dist/index.mjs +173 -15
- package/package.json +2 -1
package/dist/index.d.mts
CHANGED
|
@@ -411,6 +411,7 @@ declare class MCPServer {
|
|
|
411
411
|
private options;
|
|
412
412
|
private initPromise;
|
|
413
413
|
private autoDiscovered;
|
|
414
|
+
private manifestWatcher;
|
|
414
415
|
constructor(options: MCPServerConstructorOptions);
|
|
415
416
|
/**
|
|
416
417
|
* Internal initialization - runs automatically in constructor
|
|
@@ -462,6 +463,14 @@ declare class MCPServer {
|
|
|
462
463
|
* Register a service instance with decorated methods
|
|
463
464
|
*/
|
|
464
465
|
registerService(instance: any): void;
|
|
466
|
+
/**
|
|
467
|
+
* Watch UI manifest for changes and reload resources dynamically
|
|
468
|
+
*/
|
|
469
|
+
private watchUIManifest;
|
|
470
|
+
/**
|
|
471
|
+
* Reload UI manifest and update resource registrations
|
|
472
|
+
*/
|
|
473
|
+
private reloadUIManifest;
|
|
465
474
|
/**
|
|
466
475
|
* Load UI manifest and auto-register resources for pre-built @UIApp components.
|
|
467
476
|
* The manifest is generated by `leanmcp dev` or `leanmcp start` commands.
|
|
@@ -511,6 +520,10 @@ declare class MCPServer {
|
|
|
511
520
|
} | undefined;
|
|
512
521
|
} | undefined;
|
|
513
522
|
}>;
|
|
523
|
+
/**
|
|
524
|
+
* Cleanup resources (call on server shutdown)
|
|
525
|
+
*/
|
|
526
|
+
cleanup(): Promise<void>;
|
|
514
527
|
}
|
|
515
528
|
declare class MCPServerRuntime {
|
|
516
529
|
private server;
|
package/dist/index.d.ts
CHANGED
|
@@ -411,6 +411,7 @@ declare class MCPServer {
|
|
|
411
411
|
private options;
|
|
412
412
|
private initPromise;
|
|
413
413
|
private autoDiscovered;
|
|
414
|
+
private manifestWatcher;
|
|
414
415
|
constructor(options: MCPServerConstructorOptions);
|
|
415
416
|
/**
|
|
416
417
|
* Internal initialization - runs automatically in constructor
|
|
@@ -462,6 +463,14 @@ declare class MCPServer {
|
|
|
462
463
|
* Register a service instance with decorated methods
|
|
463
464
|
*/
|
|
464
465
|
registerService(instance: any): void;
|
|
466
|
+
/**
|
|
467
|
+
* Watch UI manifest for changes and reload resources dynamically
|
|
468
|
+
*/
|
|
469
|
+
private watchUIManifest;
|
|
470
|
+
/**
|
|
471
|
+
* Reload UI manifest and update resource registrations
|
|
472
|
+
*/
|
|
473
|
+
private reloadUIManifest;
|
|
465
474
|
/**
|
|
466
475
|
* Load UI manifest and auto-register resources for pre-built @UIApp components.
|
|
467
476
|
* The manifest is generated by `leanmcp dev` or `leanmcp start` commands.
|
|
@@ -511,6 +520,10 @@ declare class MCPServer {
|
|
|
511
520
|
} | undefined;
|
|
512
521
|
} | undefined;
|
|
513
522
|
}>;
|
|
523
|
+
/**
|
|
524
|
+
* Cleanup resources (call on server shutdown)
|
|
525
|
+
*/
|
|
526
|
+
cleanup(): Promise<void>;
|
|
514
527
|
}
|
|
515
528
|
declare class MCPServerRuntime {
|
|
516
529
|
private server;
|
package/dist/index.js
CHANGED
|
@@ -889,20 +889,36 @@ async function createHTTPServer(serverInput, options) {
|
|
|
889
889
|
logger.error(`Server error: ${error.message}`);
|
|
890
890
|
reject(error);
|
|
891
891
|
});
|
|
892
|
-
|
|
892
|
+
let isShuttingDown = false;
|
|
893
|
+
const cleanup = /* @__PURE__ */ __name(async () => {
|
|
894
|
+
if (isShuttingDown) return;
|
|
895
|
+
isShuttingDown = true;
|
|
893
896
|
logger.info("\nShutting down server...");
|
|
894
|
-
Object.values(transports)
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
897
|
+
for (const transport of Object.values(transports)) {
|
|
898
|
+
try {
|
|
899
|
+
transport.close?.();
|
|
900
|
+
} catch (e) {
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
if (activeListener) {
|
|
904
|
+
await new Promise((resolveClose) => {
|
|
905
|
+
activeListener.close((err) => {
|
|
906
|
+
if (err) {
|
|
907
|
+
logger.warn(`Error closing server: ${err.message}`);
|
|
908
|
+
} else {
|
|
909
|
+
logger.info("Server closed");
|
|
910
|
+
}
|
|
911
|
+
resolveClose();
|
|
912
|
+
});
|
|
913
|
+
});
|
|
914
|
+
}
|
|
903
915
|
}, "cleanup");
|
|
904
|
-
|
|
905
|
-
|
|
916
|
+
const handleShutdown = /* @__PURE__ */ __name(() => {
|
|
917
|
+
cleanup().finally(() => {
|
|
918
|
+
});
|
|
919
|
+
}, "handleShutdown");
|
|
920
|
+
process.once("SIGINT", handleShutdown);
|
|
921
|
+
process.once("SIGTERM", handleShutdown);
|
|
906
922
|
} catch (error) {
|
|
907
923
|
reject(error);
|
|
908
924
|
}
|
|
@@ -990,6 +1006,7 @@ var init_index = __esm({
|
|
|
990
1006
|
options;
|
|
991
1007
|
initPromise;
|
|
992
1008
|
autoDiscovered = false;
|
|
1009
|
+
manifestWatcher = null;
|
|
993
1010
|
constructor(options) {
|
|
994
1011
|
this.options = options;
|
|
995
1012
|
this.logging = options.logging || false;
|
|
@@ -1023,6 +1040,7 @@ var init_index = __esm({
|
|
|
1023
1040
|
await this.autoDiscoverServices(options.mcpDir, options.serviceFactories);
|
|
1024
1041
|
}
|
|
1025
1042
|
await this.loadUIManifest();
|
|
1043
|
+
this.watchUIManifest();
|
|
1026
1044
|
}
|
|
1027
1045
|
/**
|
|
1028
1046
|
* Wait for initialization to complete
|
|
@@ -1420,6 +1438,125 @@ var init_index = __esm({
|
|
|
1420
1438
|
}
|
|
1421
1439
|
}
|
|
1422
1440
|
/**
|
|
1441
|
+
* Watch UI manifest for changes and reload resources dynamically
|
|
1442
|
+
*/
|
|
1443
|
+
watchUIManifest() {
|
|
1444
|
+
try {
|
|
1445
|
+
const manifestPath = import_path.default.join(process.cwd(), "dist", "ui-manifest.json");
|
|
1446
|
+
if (!import_fs.default.existsSync(manifestPath)) {
|
|
1447
|
+
return;
|
|
1448
|
+
}
|
|
1449
|
+
if (this.logging) {
|
|
1450
|
+
this.logger.debug(`Watching UI manifest: ${manifestPath}`);
|
|
1451
|
+
}
|
|
1452
|
+
import("chokidar").then(({ default: chokidar }) => {
|
|
1453
|
+
this.manifestWatcher = chokidar.watch(manifestPath, {
|
|
1454
|
+
ignoreInitial: true,
|
|
1455
|
+
persistent: true,
|
|
1456
|
+
awaitWriteFinish: {
|
|
1457
|
+
stabilityThreshold: 100,
|
|
1458
|
+
pollInterval: 50
|
|
1459
|
+
}
|
|
1460
|
+
});
|
|
1461
|
+
this.manifestWatcher.on("change", async () => {
|
|
1462
|
+
if (this.logging) {
|
|
1463
|
+
this.logger.debug("UI manifest changed, reloading resources");
|
|
1464
|
+
}
|
|
1465
|
+
await this.reloadUIManifest();
|
|
1466
|
+
});
|
|
1467
|
+
this.manifestWatcher.on("error", (error) => {
|
|
1468
|
+
if (this.logging) {
|
|
1469
|
+
this.logger.warn(`Manifest watcher error: ${error.message}`);
|
|
1470
|
+
}
|
|
1471
|
+
});
|
|
1472
|
+
}).catch((error) => {
|
|
1473
|
+
if (this.logging) {
|
|
1474
|
+
this.logger.warn(`Failed to initialize manifest watcher: ${error.message}`);
|
|
1475
|
+
}
|
|
1476
|
+
});
|
|
1477
|
+
} catch (error) {
|
|
1478
|
+
if (this.logging) {
|
|
1479
|
+
this.logger.warn(`Failed to setup manifest watcher: ${error.message}`);
|
|
1480
|
+
}
|
|
1481
|
+
}
|
|
1482
|
+
}
|
|
1483
|
+
/**
|
|
1484
|
+
* Reload UI manifest and update resource registrations
|
|
1485
|
+
*/
|
|
1486
|
+
async reloadUIManifest() {
|
|
1487
|
+
try {
|
|
1488
|
+
const manifestPath = import_path.default.join(process.cwd(), "dist", "ui-manifest.json");
|
|
1489
|
+
if (!import_fs.default.existsSync(manifestPath)) {
|
|
1490
|
+
const uiResourceUris = Array.from(this.resources.keys()).filter((uri) => uri.startsWith("ui://"));
|
|
1491
|
+
for (const uri of uiResourceUris) {
|
|
1492
|
+
this.resources.delete(uri);
|
|
1493
|
+
if (this.logging) {
|
|
1494
|
+
this.logger.debug(`Removed UI resource: ${uri}`);
|
|
1495
|
+
}
|
|
1496
|
+
}
|
|
1497
|
+
return;
|
|
1498
|
+
}
|
|
1499
|
+
const manifest = JSON.parse(import_fs.default.readFileSync(manifestPath, "utf-8"));
|
|
1500
|
+
const currentUIUris = new Set(Object.keys(manifest));
|
|
1501
|
+
const registeredUIUris = Array.from(this.resources.keys()).filter((uri) => uri.startsWith("ui://"));
|
|
1502
|
+
for (const uri of registeredUIUris) {
|
|
1503
|
+
if (!currentUIUris.has(uri)) {
|
|
1504
|
+
this.resources.delete(uri);
|
|
1505
|
+
if (this.logging) {
|
|
1506
|
+
this.logger.debug(`Removed UI resource: ${uri}`);
|
|
1507
|
+
}
|
|
1508
|
+
}
|
|
1509
|
+
}
|
|
1510
|
+
for (const [uri, entry] of Object.entries(manifest)) {
|
|
1511
|
+
const isString = typeof entry === "string";
|
|
1512
|
+
const htmlPath = isString ? entry : entry.htmlPath;
|
|
1513
|
+
const isGPTApp = !isString && entry.isGPTApp;
|
|
1514
|
+
const gptMeta = !isString ? entry.gptMeta : void 0;
|
|
1515
|
+
if (!import_fs.default.existsSync(htmlPath)) {
|
|
1516
|
+
if (this.logging) {
|
|
1517
|
+
this.logger.warn(`UI HTML file not found: ${htmlPath}`);
|
|
1518
|
+
}
|
|
1519
|
+
continue;
|
|
1520
|
+
}
|
|
1521
|
+
const wasRegistered = this.resources.has(uri);
|
|
1522
|
+
const mimeType = isGPTApp ? "text/html+skybridge" : "text/html;profile=mcp-app";
|
|
1523
|
+
const _meta = {};
|
|
1524
|
+
if (isGPTApp) {
|
|
1525
|
+
_meta["openai/outputTemplate"] = uri;
|
|
1526
|
+
if (gptMeta) Object.assign(_meta, gptMeta);
|
|
1527
|
+
if (_meta["openai/widgetPrefersBorder"] === void 0) _meta["openai/widgetPrefersBorder"] = true;
|
|
1528
|
+
}
|
|
1529
|
+
this.resources.set(uri, {
|
|
1530
|
+
uri,
|
|
1531
|
+
name: uri.replace("ui://", "").replace(/\//g, "-"),
|
|
1532
|
+
description: `Auto-generated UI resource from pre-built HTML`,
|
|
1533
|
+
mimeType,
|
|
1534
|
+
inputSchema: void 0,
|
|
1535
|
+
method: /* @__PURE__ */ __name(async () => {
|
|
1536
|
+
if (import_fs.default.existsSync(htmlPath)) {
|
|
1537
|
+
const html = import_fs.default.readFileSync(htmlPath, "utf-8");
|
|
1538
|
+
return {
|
|
1539
|
+
text: html,
|
|
1540
|
+
_meta: Object.keys(_meta).length > 0 ? _meta : void 0
|
|
1541
|
+
};
|
|
1542
|
+
}
|
|
1543
|
+
throw new Error(`UI HTML file not found: ${htmlPath}`);
|
|
1544
|
+
}, "method"),
|
|
1545
|
+
instance: null,
|
|
1546
|
+
propertyKey: "getUI"
|
|
1547
|
+
});
|
|
1548
|
+
if (this.logging) {
|
|
1549
|
+
const action = wasRegistered ? "Updated" : "Registered";
|
|
1550
|
+
this.logger.debug(`${action} UI resource: ${uri}`);
|
|
1551
|
+
}
|
|
1552
|
+
}
|
|
1553
|
+
} catch (error) {
|
|
1554
|
+
if (this.logging) {
|
|
1555
|
+
this.logger.warn(`Failed to reload UI manifest: ${error.message}`);
|
|
1556
|
+
}
|
|
1557
|
+
}
|
|
1558
|
+
}
|
|
1559
|
+
/**
|
|
1423
1560
|
* Load UI manifest and auto-register resources for pre-built @UIApp components.
|
|
1424
1561
|
* The manifest is generated by `leanmcp dev` or `leanmcp start` commands.
|
|
1425
1562
|
*/
|
|
@@ -1430,7 +1567,11 @@ var init_index = __esm({
|
|
|
1430
1567
|
return;
|
|
1431
1568
|
}
|
|
1432
1569
|
const manifest = JSON.parse(import_fs.default.readFileSync(manifestPath, "utf-8"));
|
|
1433
|
-
for (const [uri,
|
|
1570
|
+
for (const [uri, entry] of Object.entries(manifest)) {
|
|
1571
|
+
const isString = typeof entry === "string";
|
|
1572
|
+
const htmlPath = isString ? entry : entry.htmlPath;
|
|
1573
|
+
const isGPTApp = !isString && entry.isGPTApp;
|
|
1574
|
+
const gptMeta = !isString ? entry.gptMeta : void 0;
|
|
1434
1575
|
if (this.resources.has(uri)) {
|
|
1435
1576
|
if (this.logging) {
|
|
1436
1577
|
this.logger.debug(`Skipping UI resource ${uri} - already registered`);
|
|
@@ -1444,14 +1585,22 @@ var init_index = __esm({
|
|
|
1444
1585
|
continue;
|
|
1445
1586
|
}
|
|
1446
1587
|
const html = import_fs.default.readFileSync(htmlPath, "utf-8");
|
|
1588
|
+
const mimeType = isGPTApp ? "text/html+skybridge" : "text/html;profile=mcp-app";
|
|
1589
|
+
const _meta = {};
|
|
1590
|
+
if (isGPTApp) {
|
|
1591
|
+
_meta["openai/outputTemplate"] = uri;
|
|
1592
|
+
if (gptMeta) Object.assign(_meta, gptMeta);
|
|
1593
|
+
if (_meta["openai/widgetPrefersBorder"] === void 0) _meta["openai/widgetPrefersBorder"] = true;
|
|
1594
|
+
}
|
|
1447
1595
|
this.resources.set(uri, {
|
|
1448
1596
|
uri,
|
|
1449
1597
|
name: uri.replace("ui://", "").replace(/\//g, "-"),
|
|
1450
1598
|
description: `Auto-generated UI resource from pre-built HTML`,
|
|
1451
|
-
mimeType
|
|
1599
|
+
mimeType,
|
|
1452
1600
|
inputSchema: void 0,
|
|
1453
1601
|
method: /* @__PURE__ */ __name(async () => ({
|
|
1454
|
-
text: html
|
|
1602
|
+
text: html,
|
|
1603
|
+
_meta: Object.keys(_meta).length > 0 ? _meta : void 0
|
|
1455
1604
|
}), "method"),
|
|
1456
1605
|
instance: null,
|
|
1457
1606
|
propertyKey: "getUI"
|
|
@@ -1474,6 +1623,15 @@ var init_index = __esm({
|
|
|
1474
1623
|
this.server.waitForInit = () => this.waitForInit();
|
|
1475
1624
|
return this.server;
|
|
1476
1625
|
}
|
|
1626
|
+
/**
|
|
1627
|
+
* Cleanup resources (call on server shutdown)
|
|
1628
|
+
*/
|
|
1629
|
+
async cleanup() {
|
|
1630
|
+
if (this.manifestWatcher) {
|
|
1631
|
+
await this.manifestWatcher.close();
|
|
1632
|
+
this.manifestWatcher = null;
|
|
1633
|
+
}
|
|
1634
|
+
}
|
|
1477
1635
|
};
|
|
1478
1636
|
MCPServerRuntime = class {
|
|
1479
1637
|
static {
|
package/dist/index.mjs
CHANGED
|
@@ -851,20 +851,36 @@ async function createHTTPServer(serverInput, options) {
|
|
|
851
851
|
logger.error(`Server error: ${error.message}`);
|
|
852
852
|
reject(error);
|
|
853
853
|
});
|
|
854
|
-
|
|
854
|
+
let isShuttingDown = false;
|
|
855
|
+
const cleanup = /* @__PURE__ */ __name(async () => {
|
|
856
|
+
if (isShuttingDown) return;
|
|
857
|
+
isShuttingDown = true;
|
|
855
858
|
logger.info("\nShutting down server...");
|
|
856
|
-
Object.values(transports)
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
859
|
+
for (const transport of Object.values(transports)) {
|
|
860
|
+
try {
|
|
861
|
+
transport.close?.();
|
|
862
|
+
} catch (e) {
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
if (activeListener) {
|
|
866
|
+
await new Promise((resolveClose) => {
|
|
867
|
+
activeListener.close((err) => {
|
|
868
|
+
if (err) {
|
|
869
|
+
logger.warn(`Error closing server: ${err.message}`);
|
|
870
|
+
} else {
|
|
871
|
+
logger.info("Server closed");
|
|
872
|
+
}
|
|
873
|
+
resolveClose();
|
|
874
|
+
});
|
|
875
|
+
});
|
|
876
|
+
}
|
|
865
877
|
}, "cleanup");
|
|
866
|
-
|
|
867
|
-
|
|
878
|
+
const handleShutdown = /* @__PURE__ */ __name(() => {
|
|
879
|
+
cleanup().finally(() => {
|
|
880
|
+
});
|
|
881
|
+
}, "handleShutdown");
|
|
882
|
+
process.once("SIGINT", handleShutdown);
|
|
883
|
+
process.once("SIGTERM", handleShutdown);
|
|
868
884
|
} catch (error) {
|
|
869
885
|
reject(error);
|
|
870
886
|
}
|
|
@@ -887,6 +903,7 @@ var MCPServer = class {
|
|
|
887
903
|
options;
|
|
888
904
|
initPromise;
|
|
889
905
|
autoDiscovered = false;
|
|
906
|
+
manifestWatcher = null;
|
|
890
907
|
constructor(options) {
|
|
891
908
|
this.options = options;
|
|
892
909
|
this.logging = options.logging || false;
|
|
@@ -920,6 +937,7 @@ var MCPServer = class {
|
|
|
920
937
|
await this.autoDiscoverServices(options.mcpDir, options.serviceFactories);
|
|
921
938
|
}
|
|
922
939
|
await this.loadUIManifest();
|
|
940
|
+
this.watchUIManifest();
|
|
923
941
|
}
|
|
924
942
|
/**
|
|
925
943
|
* Wait for initialization to complete
|
|
@@ -1317,6 +1335,125 @@ var MCPServer = class {
|
|
|
1317
1335
|
}
|
|
1318
1336
|
}
|
|
1319
1337
|
/**
|
|
1338
|
+
* Watch UI manifest for changes and reload resources dynamically
|
|
1339
|
+
*/
|
|
1340
|
+
watchUIManifest() {
|
|
1341
|
+
try {
|
|
1342
|
+
const manifestPath = path.join(process.cwd(), "dist", "ui-manifest.json");
|
|
1343
|
+
if (!fs.existsSync(manifestPath)) {
|
|
1344
|
+
return;
|
|
1345
|
+
}
|
|
1346
|
+
if (this.logging) {
|
|
1347
|
+
this.logger.debug(`Watching UI manifest: ${manifestPath}`);
|
|
1348
|
+
}
|
|
1349
|
+
import("chokidar").then(({ default: chokidar }) => {
|
|
1350
|
+
this.manifestWatcher = chokidar.watch(manifestPath, {
|
|
1351
|
+
ignoreInitial: true,
|
|
1352
|
+
persistent: true,
|
|
1353
|
+
awaitWriteFinish: {
|
|
1354
|
+
stabilityThreshold: 100,
|
|
1355
|
+
pollInterval: 50
|
|
1356
|
+
}
|
|
1357
|
+
});
|
|
1358
|
+
this.manifestWatcher.on("change", async () => {
|
|
1359
|
+
if (this.logging) {
|
|
1360
|
+
this.logger.debug("UI manifest changed, reloading resources");
|
|
1361
|
+
}
|
|
1362
|
+
await this.reloadUIManifest();
|
|
1363
|
+
});
|
|
1364
|
+
this.manifestWatcher.on("error", (error) => {
|
|
1365
|
+
if (this.logging) {
|
|
1366
|
+
this.logger.warn(`Manifest watcher error: ${error.message}`);
|
|
1367
|
+
}
|
|
1368
|
+
});
|
|
1369
|
+
}).catch((error) => {
|
|
1370
|
+
if (this.logging) {
|
|
1371
|
+
this.logger.warn(`Failed to initialize manifest watcher: ${error.message}`);
|
|
1372
|
+
}
|
|
1373
|
+
});
|
|
1374
|
+
} catch (error) {
|
|
1375
|
+
if (this.logging) {
|
|
1376
|
+
this.logger.warn(`Failed to setup manifest watcher: ${error.message}`);
|
|
1377
|
+
}
|
|
1378
|
+
}
|
|
1379
|
+
}
|
|
1380
|
+
/**
|
|
1381
|
+
* Reload UI manifest and update resource registrations
|
|
1382
|
+
*/
|
|
1383
|
+
async reloadUIManifest() {
|
|
1384
|
+
try {
|
|
1385
|
+
const manifestPath = path.join(process.cwd(), "dist", "ui-manifest.json");
|
|
1386
|
+
if (!fs.existsSync(manifestPath)) {
|
|
1387
|
+
const uiResourceUris = Array.from(this.resources.keys()).filter((uri) => uri.startsWith("ui://"));
|
|
1388
|
+
for (const uri of uiResourceUris) {
|
|
1389
|
+
this.resources.delete(uri);
|
|
1390
|
+
if (this.logging) {
|
|
1391
|
+
this.logger.debug(`Removed UI resource: ${uri}`);
|
|
1392
|
+
}
|
|
1393
|
+
}
|
|
1394
|
+
return;
|
|
1395
|
+
}
|
|
1396
|
+
const manifest = JSON.parse(fs.readFileSync(manifestPath, "utf-8"));
|
|
1397
|
+
const currentUIUris = new Set(Object.keys(manifest));
|
|
1398
|
+
const registeredUIUris = Array.from(this.resources.keys()).filter((uri) => uri.startsWith("ui://"));
|
|
1399
|
+
for (const uri of registeredUIUris) {
|
|
1400
|
+
if (!currentUIUris.has(uri)) {
|
|
1401
|
+
this.resources.delete(uri);
|
|
1402
|
+
if (this.logging) {
|
|
1403
|
+
this.logger.debug(`Removed UI resource: ${uri}`);
|
|
1404
|
+
}
|
|
1405
|
+
}
|
|
1406
|
+
}
|
|
1407
|
+
for (const [uri, entry] of Object.entries(manifest)) {
|
|
1408
|
+
const isString = typeof entry === "string";
|
|
1409
|
+
const htmlPath = isString ? entry : entry.htmlPath;
|
|
1410
|
+
const isGPTApp = !isString && entry.isGPTApp;
|
|
1411
|
+
const gptMeta = !isString ? entry.gptMeta : void 0;
|
|
1412
|
+
if (!fs.existsSync(htmlPath)) {
|
|
1413
|
+
if (this.logging) {
|
|
1414
|
+
this.logger.warn(`UI HTML file not found: ${htmlPath}`);
|
|
1415
|
+
}
|
|
1416
|
+
continue;
|
|
1417
|
+
}
|
|
1418
|
+
const wasRegistered = this.resources.has(uri);
|
|
1419
|
+
const mimeType = isGPTApp ? "text/html+skybridge" : "text/html;profile=mcp-app";
|
|
1420
|
+
const _meta = {};
|
|
1421
|
+
if (isGPTApp) {
|
|
1422
|
+
_meta["openai/outputTemplate"] = uri;
|
|
1423
|
+
if (gptMeta) Object.assign(_meta, gptMeta);
|
|
1424
|
+
if (_meta["openai/widgetPrefersBorder"] === void 0) _meta["openai/widgetPrefersBorder"] = true;
|
|
1425
|
+
}
|
|
1426
|
+
this.resources.set(uri, {
|
|
1427
|
+
uri,
|
|
1428
|
+
name: uri.replace("ui://", "").replace(/\//g, "-"),
|
|
1429
|
+
description: `Auto-generated UI resource from pre-built HTML`,
|
|
1430
|
+
mimeType,
|
|
1431
|
+
inputSchema: void 0,
|
|
1432
|
+
method: /* @__PURE__ */ __name(async () => {
|
|
1433
|
+
if (fs.existsSync(htmlPath)) {
|
|
1434
|
+
const html = fs.readFileSync(htmlPath, "utf-8");
|
|
1435
|
+
return {
|
|
1436
|
+
text: html,
|
|
1437
|
+
_meta: Object.keys(_meta).length > 0 ? _meta : void 0
|
|
1438
|
+
};
|
|
1439
|
+
}
|
|
1440
|
+
throw new Error(`UI HTML file not found: ${htmlPath}`);
|
|
1441
|
+
}, "method"),
|
|
1442
|
+
instance: null,
|
|
1443
|
+
propertyKey: "getUI"
|
|
1444
|
+
});
|
|
1445
|
+
if (this.logging) {
|
|
1446
|
+
const action = wasRegistered ? "Updated" : "Registered";
|
|
1447
|
+
this.logger.debug(`${action} UI resource: ${uri}`);
|
|
1448
|
+
}
|
|
1449
|
+
}
|
|
1450
|
+
} catch (error) {
|
|
1451
|
+
if (this.logging) {
|
|
1452
|
+
this.logger.warn(`Failed to reload UI manifest: ${error.message}`);
|
|
1453
|
+
}
|
|
1454
|
+
}
|
|
1455
|
+
}
|
|
1456
|
+
/**
|
|
1320
1457
|
* Load UI manifest and auto-register resources for pre-built @UIApp components.
|
|
1321
1458
|
* The manifest is generated by `leanmcp dev` or `leanmcp start` commands.
|
|
1322
1459
|
*/
|
|
@@ -1327,7 +1464,11 @@ var MCPServer = class {
|
|
|
1327
1464
|
return;
|
|
1328
1465
|
}
|
|
1329
1466
|
const manifest = JSON.parse(fs.readFileSync(manifestPath, "utf-8"));
|
|
1330
|
-
for (const [uri,
|
|
1467
|
+
for (const [uri, entry] of Object.entries(manifest)) {
|
|
1468
|
+
const isString = typeof entry === "string";
|
|
1469
|
+
const htmlPath = isString ? entry : entry.htmlPath;
|
|
1470
|
+
const isGPTApp = !isString && entry.isGPTApp;
|
|
1471
|
+
const gptMeta = !isString ? entry.gptMeta : void 0;
|
|
1331
1472
|
if (this.resources.has(uri)) {
|
|
1332
1473
|
if (this.logging) {
|
|
1333
1474
|
this.logger.debug(`Skipping UI resource ${uri} - already registered`);
|
|
@@ -1341,14 +1482,22 @@ var MCPServer = class {
|
|
|
1341
1482
|
continue;
|
|
1342
1483
|
}
|
|
1343
1484
|
const html = fs.readFileSync(htmlPath, "utf-8");
|
|
1485
|
+
const mimeType = isGPTApp ? "text/html+skybridge" : "text/html;profile=mcp-app";
|
|
1486
|
+
const _meta = {};
|
|
1487
|
+
if (isGPTApp) {
|
|
1488
|
+
_meta["openai/outputTemplate"] = uri;
|
|
1489
|
+
if (gptMeta) Object.assign(_meta, gptMeta);
|
|
1490
|
+
if (_meta["openai/widgetPrefersBorder"] === void 0) _meta["openai/widgetPrefersBorder"] = true;
|
|
1491
|
+
}
|
|
1344
1492
|
this.resources.set(uri, {
|
|
1345
1493
|
uri,
|
|
1346
1494
|
name: uri.replace("ui://", "").replace(/\//g, "-"),
|
|
1347
1495
|
description: `Auto-generated UI resource from pre-built HTML`,
|
|
1348
|
-
mimeType
|
|
1496
|
+
mimeType,
|
|
1349
1497
|
inputSchema: void 0,
|
|
1350
1498
|
method: /* @__PURE__ */ __name(async () => ({
|
|
1351
|
-
text: html
|
|
1499
|
+
text: html,
|
|
1500
|
+
_meta: Object.keys(_meta).length > 0 ? _meta : void 0
|
|
1352
1501
|
}), "method"),
|
|
1353
1502
|
instance: null,
|
|
1354
1503
|
propertyKey: "getUI"
|
|
@@ -1371,6 +1520,15 @@ var MCPServer = class {
|
|
|
1371
1520
|
this.server.waitForInit = () => this.waitForInit();
|
|
1372
1521
|
return this.server;
|
|
1373
1522
|
}
|
|
1523
|
+
/**
|
|
1524
|
+
* Cleanup resources (call on server shutdown)
|
|
1525
|
+
*/
|
|
1526
|
+
async cleanup() {
|
|
1527
|
+
if (this.manifestWatcher) {
|
|
1528
|
+
await this.manifestWatcher.close();
|
|
1529
|
+
this.manifestWatcher = null;
|
|
1530
|
+
}
|
|
1531
|
+
}
|
|
1374
1532
|
};
|
|
1375
1533
|
var MCPServerRuntime = class {
|
|
1376
1534
|
static {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@leanmcp/core",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.10",
|
|
4
4
|
"description": "Core library implementing decorators, reflection, and MCP runtime server",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.mjs",
|
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
"dependencies": {
|
|
27
27
|
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
28
28
|
"ajv": "^8.12.0",
|
|
29
|
+
"chokidar": "^4.0.0",
|
|
29
30
|
"dotenv": "^16.3.1",
|
|
30
31
|
"reflect-metadata": "^0.2.1"
|
|
31
32
|
},
|