@caplets/core 0.12.0 → 0.12.2

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.
@@ -1,7 +1,7 @@
1
1
  import { generatedToolInputDescriptions, operations } from "./generated-tool-input-schema.js";
2
2
  import { createRequire } from "node:module";
3
- import { accessSync, chmodSync, constants, existsSync, mkdirSync, readFileSync, readdirSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
4
- import minpath, { basename, delimiter, dirname, extname, isAbsolute, join, posix, relative, resolve, win32 } from "node:path";
3
+ import { accessSync, chmodSync, constants, existsSync, mkdirSync, readFileSync, readdirSync, renameSync, rmSync, statSync, watch, writeFileSync } from "node:fs";
4
+ import minpath, { basename, delimiter, dirname, extname, isAbsolute, join, parse, posix, relative, resolve, win32 } from "node:path";
5
5
  import minproc, { default as process$1 } from "node:process";
6
6
  import { fileURLToPath as urlToPath } from "node:url";
7
7
  import { homedir } from "node:os";
@@ -57393,4 +57393,294 @@ function backendFor(server, downstream, openapi, graphql, http, cli) {
57393
57393
  };
57394
57394
  }
57395
57395
  //#endregion
57396
- export { LoggingLevelSchema as $, assertToolsCallTaskCapability as A, CreateTaskResultSchema as B, discoverCapletFiles as C, toSafeError as D, errorResult as E, CallToolRequestSchema as F, InitializeRequestSchema as G, EmptyResultSchema as H, CallToolResultSchema as I, ListPromptsRequestSchema as J, InitializedNotificationSchema as K, CompleteRequestSchema as L, Protocol as M, mergeCapabilities as N, SERVER_ID_PATTERN as O, toJsonSchemaCompat as P, ListToolsRequestSchema as Q, CreateMessageResultSchema as R, resolveProjectConfigPath as S, CapletsError as T, ErrorCode as U, ElicitResultSchema as V, GetPromptRequestSchema as W, ListResourcesRequestSchema as X, ListResourceTemplatesRequestSchema as Y, ListRootsResultSchema as Z, parseConfig as _, __commonJSMin as _t, OpenApiManager as a, assertCompleteRequestResourceTemplate as at, resolveConfigPath as b, DownstreamManager as c, getObjectShape as ct, deleteTokenBundle as d, isSchemaOptional as dt, McpError as et, isTokenBundleExpired as f, isZ4Schema as ft, loadConfigWithSources as g, safeParseAsync$1 as gt, loadConfig as h, safeParse$1 as ht, capabilityDescription as i, assertCompleteRequestPrompt as it, AjvJsonSchemaValidator as j, assertClientRequestTaskCapability as k, runGenericOAuthFlow as l, getParseErrorMessage as lt, CliToolsManager as m, objectFromShape as mt, handleServerTool as n, SUPPORTED_PROTOCOL_VERSIONS as nt, HttpActionManager as o, ZodOptional as ot, readTokenBundle as p, normalizeObjectSchema as pt, LATEST_PROTOCOL_VERSION as q, ServerRegistry as r, SetLevelRequestSchema as rt, GraphQLManager as s, getLiteralValue as st, generatedToolInputSchema as t, ReadResourceRequestSchema as tt, runOAuthFlow as u, getSchemaDescription as ut, DEFAULT_AUTH_DIR as v, __require as vt, validateCapletFile as w, resolveProjectCapletsRoot as x, resolveCapletsRoot as y, __toESM as yt, CreateMessageResultWithToolsSchema as z };
57396
+ //#region src/engine.ts
57397
+ var CapletsEngine = class {
57398
+ registry;
57399
+ downstream;
57400
+ openapi;
57401
+ graphql;
57402
+ http;
57403
+ cli;
57404
+ paths;
57405
+ watchDebounceMs;
57406
+ watchEnabled;
57407
+ writeErr;
57408
+ reloadListeners = /* @__PURE__ */ new Set();
57409
+ watchers = [];
57410
+ reloadTimer;
57411
+ watcherRefreshTimer;
57412
+ reloading;
57413
+ pendingReload = false;
57414
+ closed = false;
57415
+ constructor(options = {}) {
57416
+ this.paths = {
57417
+ configPath: resolveConfigPath(options.configPath),
57418
+ projectConfigPath: options.projectConfigPath ?? resolveProjectConfigPath()
57419
+ };
57420
+ const config = loadConfig(this.paths.configPath, this.paths.projectConfigPath);
57421
+ this.registry = new ServerRegistry(config);
57422
+ this.downstream = new DownstreamManager(this.registry, selectAuthOptions(options.authDir));
57423
+ this.openapi = new OpenApiManager(this.registry, selectAuthOptions(options.authDir));
57424
+ this.graphql = new GraphQLManager(this.registry, selectAuthOptions(options.authDir));
57425
+ this.http = new HttpActionManager(this.registry, selectAuthOptions(options.authDir));
57426
+ this.cli = new CliToolsManager(this.registry);
57427
+ this.watchDebounceMs = options.watchDebounceMs ?? 250;
57428
+ this.watchEnabled = options.watch ?? true;
57429
+ this.writeErr = options.writeErr ?? ((value) => process.stderr.write(value));
57430
+ if (this.watchEnabled) this.resetWatchers();
57431
+ }
57432
+ currentConfig() {
57433
+ return this.registry.config;
57434
+ }
57435
+ enabledServers() {
57436
+ return nextEnabledServers(this.registry.config);
57437
+ }
57438
+ watchedPaths() {
57439
+ return [...new Set(watchedPaths(this.paths).map((entry) => entry.path))].sort();
57440
+ }
57441
+ onReload(listener) {
57442
+ this.reloadListeners.add(listener);
57443
+ return () => {
57444
+ this.reloadListeners.delete(listener);
57445
+ };
57446
+ }
57447
+ scheduleReload() {
57448
+ if (this.closed) return;
57449
+ if (this.reloadTimer) clearTimeout(this.reloadTimer);
57450
+ this.reloadTimer = setTimeout(() => {
57451
+ this.reloadTimer = void 0;
57452
+ this.reload();
57453
+ }, this.watchDebounceMs);
57454
+ }
57455
+ async reload() {
57456
+ if (this.closed) return false;
57457
+ if (this.reloading) {
57458
+ this.pendingReload = true;
57459
+ return await this.reloading;
57460
+ }
57461
+ this.reloading = this.reloadUntilSettled().finally(() => {
57462
+ this.reloading = void 0;
57463
+ });
57464
+ return await this.reloading;
57465
+ }
57466
+ async execute(serverId, request) {
57467
+ try {
57468
+ return await handleServerTool(this.registry.require(serverId), request, this.registry, this.downstream, this.openapi, this.graphql, this.http, this.cli);
57469
+ } catch (error) {
57470
+ return errorResult(error);
57471
+ }
57472
+ }
57473
+ async close() {
57474
+ this.closed = true;
57475
+ try {
57476
+ if (this.reloadTimer) {
57477
+ clearTimeout(this.reloadTimer);
57478
+ this.reloadTimer = void 0;
57479
+ }
57480
+ if (this.watcherRefreshTimer) {
57481
+ clearTimeout(this.watcherRefreshTimer);
57482
+ this.watcherRefreshTimer = void 0;
57483
+ }
57484
+ if (this.reloading) await this.reloading;
57485
+ } finally {
57486
+ this.closeWatchers();
57487
+ await this.downstream.close();
57488
+ this.reloadListeners.clear();
57489
+ }
57490
+ }
57491
+ async reloadOnce() {
57492
+ if (this.closed) return false;
57493
+ let nextConfig;
57494
+ try {
57495
+ nextConfig = loadConfig(this.paths.configPath, this.paths.projectConfigPath);
57496
+ } catch (error) {
57497
+ this.writeErr(`Caplets config reload failed; keeping last known-good config.\n`);
57498
+ this.writeErr(`${JSON.stringify(toSafeError(error, "CONFIG_INVALID"), null, 2)}\n`);
57499
+ return false;
57500
+ }
57501
+ if (this.closed) return false;
57502
+ const previousConfig = this.registry.config;
57503
+ const nextRegistry = new ServerRegistry(nextConfig);
57504
+ this.registry = nextRegistry;
57505
+ this.downstream.updateRegistry(nextRegistry);
57506
+ this.openapi.updateRegistry(nextRegistry);
57507
+ this.graphql.updateRegistry(nextRegistry);
57508
+ this.http.updateRegistry(nextRegistry);
57509
+ this.cli.updateRegistry(nextRegistry);
57510
+ let invalidated = true;
57511
+ try {
57512
+ await this.invalidateChangedBackends(previousConfig, nextConfig);
57513
+ } catch (error) {
57514
+ invalidated = false;
57515
+ this.writeErr(`Caplets backend invalidation failed; continuing reload.\n`);
57516
+ this.writeErr(`${JSON.stringify(toSafeError(error, "INTERNAL_ERROR"), null, 2)}\n`);
57517
+ }
57518
+ if (this.closed) return false;
57519
+ if (this.watchEnabled) this.resetWatchers();
57520
+ this.emitReload({
57521
+ previous: previousConfig,
57522
+ next: nextConfig,
57523
+ invalidated
57524
+ });
57525
+ return invalidated;
57526
+ }
57527
+ async reloadUntilSettled() {
57528
+ let succeeded = true;
57529
+ do {
57530
+ this.pendingReload = false;
57531
+ try {
57532
+ succeeded = await this.reloadOnce() && succeeded;
57533
+ } catch (err) {
57534
+ this.writeErr(`Caplets reload failed.\n`);
57535
+ this.writeErr(`${JSON.stringify(toSafeError(err, "INTERNAL_ERROR"), null, 2)}\n`);
57536
+ succeeded = false;
57537
+ }
57538
+ } while (this.pendingReload && !this.closed);
57539
+ return succeeded && !this.closed;
57540
+ }
57541
+ emitReload(event) {
57542
+ for (const listener of this.reloadListeners) try {
57543
+ listener(event);
57544
+ } catch (error) {
57545
+ this.writeErr(`Caplets reload listener failed.\n`);
57546
+ this.writeErr(`${JSON.stringify(toSafeError(error, "INTERNAL_ERROR"), null, 2)}\n`);
57547
+ }
57548
+ }
57549
+ async invalidateChangedBackends(previous, next) {
57550
+ const previousCaplets = new Map(allCaplets(previous).map((server) => [server.server, server]));
57551
+ const nextCaplets = new Map(allCaplets(next).map((server) => [server.server, server]));
57552
+ const changedIds = new Set([...previousCaplets.keys(), ...nextCaplets.keys()]);
57553
+ for (const serverId of changedIds) {
57554
+ const before = previousCaplets.get(serverId);
57555
+ const after = nextCaplets.get(serverId);
57556
+ if (!(serializeCaplet(before) !== serializeCaplet(after))) continue;
57557
+ if (before?.backend === "mcp") await this.downstream.closeServer(serverId);
57558
+ if (before?.backend === "openapi" || after?.backend === "openapi" || !after) this.openapi.invalidate(serverId);
57559
+ if (before?.backend === "graphql" || after?.backend === "graphql" || !after) this.graphql.invalidate(serverId);
57560
+ if (before?.backend === "http" || after?.backend === "http" || !after) this.http.invalidate(serverId);
57561
+ if (before?.backend === "cli" || after?.backend === "cli" || !after) this.cli.invalidate(serverId);
57562
+ }
57563
+ }
57564
+ resetWatchers() {
57565
+ this.closeWatchers();
57566
+ const watched = /* @__PURE__ */ new Set();
57567
+ for (const entry of watchedPaths(this.paths)) {
57568
+ const watchPath = existsSync(entry.path) ? entry.path : nearestExistingParent(entry.path);
57569
+ const watchKey = `${entry.reason}:${watchPath}`;
57570
+ if (!watchPath || watched.has(watchKey)) continue;
57571
+ watched.add(watchKey);
57572
+ try {
57573
+ this.watchers.push(...this.watchEntry(entry, watchPath));
57574
+ } catch (error) {
57575
+ this.writeErr(`Caplets could not watch ${entry.reason} path ${entry.path}.\n`);
57576
+ this.writeErr(`${JSON.stringify(toSafeError(error, "SERVER_UNAVAILABLE"), null, 2)}\n`);
57577
+ }
57578
+ }
57579
+ }
57580
+ closeWatchers() {
57581
+ for (const watcher of this.watchers) watcher.close();
57582
+ this.watchers = [];
57583
+ }
57584
+ watchEntry(entry, watchPath) {
57585
+ if (entry.reason === "caplets" && existsSync(entry.path) && isDirectory(watchPath)) return this.watchDirectoryTree(watchPath);
57586
+ return [watch(watchPath, { persistent: true }, (eventType) => {
57587
+ this.scheduleReload();
57588
+ if (eventType === "rename" && entry.reason === "caplets" && existsSync(entry.path)) this.scheduleWatcherRefresh();
57589
+ })];
57590
+ }
57591
+ watchDirectoryTree(root) {
57592
+ const watchers = [];
57593
+ const directories = discoverDirectories(root);
57594
+ for (const directory of directories) try {
57595
+ watchers.push(watch(directory, { persistent: true }, (eventType) => {
57596
+ this.scheduleReload();
57597
+ if (eventType === "rename") this.scheduleWatcherRefresh();
57598
+ }));
57599
+ } catch (error) {
57600
+ for (const watcher of watchers) watcher.close();
57601
+ throw error;
57602
+ }
57603
+ return watchers;
57604
+ }
57605
+ scheduleWatcherRefresh() {
57606
+ if (this.closed) return;
57607
+ if (this.watcherRefreshTimer) clearTimeout(this.watcherRefreshTimer);
57608
+ this.watcherRefreshTimer = setTimeout(() => {
57609
+ this.watcherRefreshTimer = void 0;
57610
+ if (!this.closed) this.resetWatchers();
57611
+ }, this.watchDebounceMs);
57612
+ }
57613
+ };
57614
+ function selectAuthOptions(authDir) {
57615
+ return authDir ? { authDir } : {};
57616
+ }
57617
+ function watchedPaths(paths) {
57618
+ return uniqueWatchedPaths([
57619
+ {
57620
+ path: dirname(paths.configPath),
57621
+ reason: "config"
57622
+ },
57623
+ {
57624
+ path: dirname(paths.projectConfigPath),
57625
+ reason: "config"
57626
+ },
57627
+ {
57628
+ path: resolveCapletsRoot(paths.configPath),
57629
+ reason: "caplets"
57630
+ },
57631
+ {
57632
+ path: dirname(paths.projectConfigPath),
57633
+ reason: "caplets"
57634
+ }
57635
+ ]);
57636
+ }
57637
+ function uniqueWatchedPaths(entries) {
57638
+ const seen = /* @__PURE__ */ new Set();
57639
+ const unique = [];
57640
+ for (const entry of entries) {
57641
+ const key = `${entry.reason}:${entry.path}`;
57642
+ if (seen.has(key)) continue;
57643
+ seen.add(key);
57644
+ unique.push(entry);
57645
+ }
57646
+ return unique;
57647
+ }
57648
+ function allCaplets(config) {
57649
+ return [
57650
+ ...Object.values(config.mcpServers),
57651
+ ...Object.values(config.openapiEndpoints),
57652
+ ...Object.values(config.graphqlEndpoints),
57653
+ ...Object.values(config.httpApis),
57654
+ ...Object.values(config.cliTools)
57655
+ ];
57656
+ }
57657
+ function nextEnabledServers(config) {
57658
+ return allCaplets(config).filter((server) => !server.disabled);
57659
+ }
57660
+ function serializeCaplet(caplet) {
57661
+ return JSON.stringify(caplet ?? null);
57662
+ }
57663
+ function nearestExistingParent(path) {
57664
+ let candidate = dirname(path);
57665
+ const root = parse(candidate).root;
57666
+ while (candidate && candidate !== root) {
57667
+ if (existsSync(candidate)) return candidate;
57668
+ candidate = dirname(candidate);
57669
+ }
57670
+ return existsSync(root) ? root : void 0;
57671
+ }
57672
+ function discoverDirectories(root) {
57673
+ if (!isDirectory(root)) return [];
57674
+ const directories = [root];
57675
+ for (const entry of readdirSync(root, { withFileTypes: true })) if (entry.isDirectory()) directories.push(...discoverDirectories(join(root, entry.name)));
57676
+ return directories;
57677
+ }
57678
+ function isDirectory(path) {
57679
+ try {
57680
+ return statSync(path).isDirectory();
57681
+ } catch {
57682
+ return false;
57683
+ }
57684
+ }
57685
+ //#endregion
57686
+ export { assertCompleteRequestPrompt as $, CallToolRequestSchema as A, InitializeRequestSchema as B, SERVER_ID_PATTERN as C, Protocol as D, AjvJsonSchemaValidator as E, CreateTaskResultSchema as F, ListResourcesRequestSchema as G, LATEST_PROTOCOL_VERSION as H, ElicitResultSchema as I, LoggingLevelSchema as J, ListRootsResultSchema as K, EmptyResultSchema as L, CompleteRequestSchema as M, CreateMessageResultSchema as N, mergeCapabilities as O, CreateMessageResultWithToolsSchema as P, SetLevelRequestSchema as Q, ErrorCode as R, toSafeError as S, assertToolsCallTaskCapability as T, ListPromptsRequestSchema as U, InitializedNotificationSchema as V, ListResourceTemplatesRequestSchema as W, ReadResourceRequestSchema as X, McpError as Y, SUPPORTED_PROTOCOL_VERSIONS as Z, resolveProjectCapletsRoot as _, capabilityDescription as a, getSchemaDescription as at, validateCapletFile as b, deleteTokenBundle as c, normalizeObjectSchema as ct, loadConfig as d, safeParseAsync$1 as dt, assertCompleteRequestResourceTemplate as et, loadConfigWithSources as f, __commonJSMin as ft, resolveConfigPath as g, resolveCapletsRoot as h, ServerRegistry as i, getParseErrorMessage as it, CallToolResultSchema as j, toJsonSchemaCompat as k, isTokenBundleExpired as l, objectFromShape as lt, DEFAULT_AUTH_DIR as m, __toESM as mt, generatedToolInputSchema as n, getLiteralValue as nt, runGenericOAuthFlow as o, isSchemaOptional as ot, parseConfig as p, __require as pt, ListToolsRequestSchema as q, handleServerTool as r, getObjectShape as rt, runOAuthFlow as s, isZ4Schema as st, CapletsEngine as t, ZodOptional as tt, readTokenBundle as u, safeParse$1 as ut, resolveProjectConfigPath as v, assertClientRequestTaskCapability as w, CapletsError as x, discoverCapletFiles as y, GetPromptRequestSchema as z };
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
- import { $ as LoggingLevelSchema, A as assertToolsCallTaskCapability, B as CreateTaskResultSchema, C as discoverCapletFiles, D as toSafeError, E as errorResult, F as CallToolRequestSchema, G as InitializeRequestSchema, H as EmptyResultSchema, I as CallToolResultSchema, J as ListPromptsRequestSchema, K as InitializedNotificationSchema, L as CompleteRequestSchema, M as Protocol, N as mergeCapabilities, O as SERVER_ID_PATTERN, P as toJsonSchemaCompat, Q as ListToolsRequestSchema, R as CreateMessageResultSchema, S as resolveProjectConfigPath, T as CapletsError, U as ErrorCode, V as ElicitResultSchema, W as GetPromptRequestSchema, X as ListResourcesRequestSchema, Y as ListResourceTemplatesRequestSchema, Z as ListRootsResultSchema, _ as parseConfig, _t as __commonJSMin, a as OpenApiManager, at as assertCompleteRequestResourceTemplate, b as resolveConfigPath, c as DownstreamManager, ct as getObjectShape, d as deleteTokenBundle, dt as isSchemaOptional, et as McpError, f as isTokenBundleExpired, ft as isZ4Schema, g as loadConfigWithSources, gt as safeParseAsync, h as loadConfig, ht as safeParse, i as capabilityDescription, it as assertCompleteRequestPrompt, j as AjvJsonSchemaValidator, k as assertClientRequestTaskCapability, l as runGenericOAuthFlow, lt as getParseErrorMessage, m as CliToolsManager, mt as objectFromShape, n as handleServerTool, nt as SUPPORTED_PROTOCOL_VERSIONS, o as HttpActionManager, ot as ZodOptional, p as readTokenBundle, pt as normalizeObjectSchema, q as LATEST_PROTOCOL_VERSION, r as ServerRegistry, rt as SetLevelRequestSchema, s as GraphQLManager, st as getLiteralValue, t as generatedToolInputSchema, tt as ReadResourceRequestSchema, u as runOAuthFlow, ut as getSchemaDescription, v as DEFAULT_AUTH_DIR, vt as __require, w as validateCapletFile, x as resolveProjectCapletsRoot, y as resolveCapletsRoot, yt as __toESM, z as CreateMessageResultWithToolsSchema } from "./tools-BlFF20vE.js";
2
- import { accessSync, chmodSync, closeSync, constants, cpSync, existsSync, lstatSync, mkdirSync, mkdtempSync, openSync, readFileSync, readdirSync, rmSync, statSync, watch, writeFileSync, writeSync } from "node:fs";
1
+ import { $ as assertCompleteRequestPrompt, A as CallToolRequestSchema, B as InitializeRequestSchema, C as SERVER_ID_PATTERN, D as Protocol, E as AjvJsonSchemaValidator, F as CreateTaskResultSchema, G as ListResourcesRequestSchema, H as LATEST_PROTOCOL_VERSION, I as ElicitResultSchema, J as LoggingLevelSchema, K as ListRootsResultSchema, L as EmptyResultSchema, M as CompleteRequestSchema, N as CreateMessageResultSchema, O as mergeCapabilities, P as CreateMessageResultWithToolsSchema, Q as SetLevelRequestSchema, R as ErrorCode, S as toSafeError, T as assertToolsCallTaskCapability, U as ListPromptsRequestSchema, V as InitializedNotificationSchema, W as ListResourceTemplatesRequestSchema, X as ReadResourceRequestSchema, Y as McpError, Z as SUPPORTED_PROTOCOL_VERSIONS, _ as resolveProjectCapletsRoot, a as capabilityDescription, at as getSchemaDescription, b as validateCapletFile, c as deleteTokenBundle, ct as normalizeObjectSchema, d as loadConfig, dt as safeParseAsync, et as assertCompleteRequestResourceTemplate, f as loadConfigWithSources, ft as __commonJSMin, g as resolveConfigPath, h as resolveCapletsRoot, i as ServerRegistry, it as getParseErrorMessage, j as CallToolResultSchema, k as toJsonSchemaCompat, l as isTokenBundleExpired, lt as objectFromShape, m as DEFAULT_AUTH_DIR, mt as __toESM, n as generatedToolInputSchema, nt as getLiteralValue, o as runGenericOAuthFlow, ot as isSchemaOptional, p as parseConfig, pt as __require, q as ListToolsRequestSchema, r as handleServerTool, rt as getObjectShape, s as runOAuthFlow, st as isZ4Schema, t as CapletsEngine, tt as ZodOptional, u as readTokenBundle, ut as safeParse, v as resolveProjectConfigPath, w as assertClientRequestTaskCapability, x as CapletsError, y as discoverCapletFiles, z as GetPromptRequestSchema } from "./engine-BzekyZ5r.js";
2
+ import { accessSync, chmodSync, closeSync, constants, cpSync, existsSync, lstatSync, mkdirSync, mkdtempSync, openSync, readFileSync, rmSync, statSync, writeFileSync, writeSync } from "node:fs";
3
3
  import { basename, dirname, join, parse, relative, resolve } from "node:path";
4
4
  import { stdin, stdout } from "node:process";
5
5
  import { tmpdir } from "node:os";
@@ -1314,142 +1314,48 @@ const EMPTY_COMPLETION_RESULT = { completion: {
1314
1314
  } };
1315
1315
  //#endregion
1316
1316
  //#region package.json
1317
- var version = "0.12.0";
1317
+ var version = "0.12.2";
1318
1318
  //#endregion
1319
1319
  //#region src/runtime.ts
1320
1320
  var CapletsRuntime = class {
1321
1321
  server;
1322
- registry;
1323
- downstream;
1324
- openapi;
1325
- graphql;
1326
- http;
1327
- cli;
1322
+ engine;
1328
1323
  tools = /* @__PURE__ */ new Map();
1329
- paths;
1330
- watchDebounceMs;
1331
- writeErr;
1332
- watchers = [];
1333
- reloadTimer;
1334
- watcherRefreshTimer;
1335
- reloading;
1336
- pendingReload = false;
1337
- closed = false;
1324
+ unsubscribeReload;
1338
1325
  constructor(options = {}) {
1339
- this.paths = {
1340
- configPath: resolveConfigPath(options.configPath),
1341
- projectConfigPath: options.projectConfigPath ?? resolveProjectConfigPath()
1342
- };
1343
- const config = loadConfig(this.paths.configPath, this.paths.projectConfigPath);
1344
- this.registry = new ServerRegistry(config);
1345
- this.downstream = new DownstreamManager(this.registry, selectAuthOptions(options.authDir));
1346
- this.openapi = new OpenApiManager(this.registry, selectAuthOptions(options.authDir));
1347
- this.graphql = new GraphQLManager(this.registry, selectAuthOptions(options.authDir));
1348
- this.http = new HttpActionManager(this.registry, selectAuthOptions(options.authDir));
1349
- this.cli = new CliToolsManager(this.registry);
1326
+ this.engine = new CapletsEngine(engineOptions(options));
1350
1327
  this.server = options.server ?? new McpServer({
1351
1328
  name: "caplets",
1352
1329
  version
1353
1330
  });
1354
- this.watchDebounceMs = options.watchDebounceMs ?? 250;
1355
- this.writeErr = options.writeErr ?? ((value) => process.stderr.write(value));
1356
- this.reconcileTools(void 0, config);
1357
- this.resetWatchers();
1331
+ this.unsubscribeReload = this.engine.onReload(({ previous, next }) => this.reconcileTools(previous, next));
1332
+ this.reconcileTools(void 0, this.engine.currentConfig());
1358
1333
  }
1359
1334
  async connect(transport) {
1360
1335
  await this.server.connect(transport);
1361
1336
  }
1362
1337
  scheduleReload() {
1363
- if (this.closed) return;
1364
- if (this.reloadTimer) clearTimeout(this.reloadTimer);
1365
- this.reloadTimer = setTimeout(() => {
1366
- this.reloadTimer = void 0;
1367
- this.reload();
1368
- }, this.watchDebounceMs);
1338
+ this.engine.scheduleReload();
1369
1339
  }
1370
1340
  async reload() {
1371
- if (this.closed) return false;
1372
- if (this.reloading) {
1373
- this.pendingReload = true;
1374
- return await this.reloading;
1375
- }
1376
- this.reloading = this.reloadUntilSettled().finally(() => {
1377
- this.reloading = void 0;
1378
- });
1379
- return await this.reloading;
1341
+ return await this.engine.reload();
1380
1342
  }
1381
1343
  async close() {
1382
- this.closed = true;
1344
+ this.unsubscribeReload();
1383
1345
  try {
1384
- if (this.reloadTimer) {
1385
- clearTimeout(this.reloadTimer);
1386
- this.reloadTimer = void 0;
1387
- }
1388
- if (this.watcherRefreshTimer) {
1389
- clearTimeout(this.watcherRefreshTimer);
1390
- this.watcherRefreshTimer = void 0;
1391
- }
1392
- if (this.reloading) await this.reloading;
1346
+ await this.engine.close();
1393
1347
  } finally {
1394
- this.closeWatchers();
1395
- await this.downstream.close();
1396
1348
  await this.server.close();
1397
1349
  }
1398
1350
  }
1399
1351
  currentConfig() {
1400
- return this.registry.config;
1352
+ return this.engine.currentConfig();
1401
1353
  }
1402
1354
  registeredToolIds() {
1403
1355
  return [...this.tools.keys()].sort();
1404
1356
  }
1405
1357
  watchedPaths() {
1406
- return [...new Set(watchedPaths(this.paths).map((entry) => entry.path))].sort();
1407
- }
1408
- async reloadOnce() {
1409
- if (this.closed) return false;
1410
- let nextConfig;
1411
- try {
1412
- nextConfig = loadConfig(this.paths.configPath, this.paths.projectConfigPath);
1413
- } catch (error) {
1414
- this.writeErr(`Caplets config reload failed; keeping last known-good config.\n`);
1415
- this.writeErr(`${JSON.stringify(toSafeError(error, "CONFIG_INVALID"), null, 2)}\n`);
1416
- return false;
1417
- }
1418
- if (this.closed) return false;
1419
- const previousConfig = this.registry.config;
1420
- const nextRegistry = new ServerRegistry(nextConfig);
1421
- this.registry = nextRegistry;
1422
- this.downstream.updateRegistry(nextRegistry);
1423
- this.openapi.updateRegistry(nextRegistry);
1424
- this.graphql.updateRegistry(nextRegistry);
1425
- this.http.updateRegistry(nextRegistry);
1426
- this.cli.updateRegistry(nextRegistry);
1427
- let invalidated = true;
1428
- try {
1429
- await this.invalidateChangedBackends(previousConfig, nextConfig);
1430
- } catch (error) {
1431
- invalidated = false;
1432
- this.writeErr(`Caplets backend invalidation failed; continuing reload.\n`);
1433
- this.writeErr(`${JSON.stringify(toSafeError(error, "INTERNAL_ERROR"), null, 2)}\n`);
1434
- }
1435
- if (this.closed) return false;
1436
- this.reconcileTools(previousConfig, nextConfig);
1437
- this.resetWatchers();
1438
- return invalidated;
1439
- }
1440
- async reloadUntilSettled() {
1441
- let succeeded = true;
1442
- do {
1443
- this.pendingReload = false;
1444
- try {
1445
- succeeded = await this.reloadOnce() && succeeded;
1446
- } catch (err) {
1447
- this.writeErr(`Caplets reload failed.\n`);
1448
- this.writeErr(`${JSON.stringify(toSafeError(err, "INTERNAL_ERROR"), null, 2)}\n`);
1449
- succeeded = false;
1450
- }
1451
- } while (this.pendingReload && !this.closed);
1452
- return succeeded && !this.closed;
1358
+ return this.engine.watchedPaths();
1453
1359
  }
1454
1360
  reconcileTools(previous, next) {
1455
1361
  const enabled = new Map(nextEnabledServers(next).map((server) => [server.server, server]));
@@ -1481,122 +1387,17 @@ var CapletsRuntime = class {
1481
1387
  }, async (request) => this.handleTool(caplet.server, request));
1482
1388
  }
1483
1389
  async handleTool(serverId, request) {
1484
- try {
1485
- return await handleServerTool(this.registry.require(serverId), request, this.registry, this.downstream, this.openapi, this.graphql, this.http, this.cli);
1486
- } catch (error) {
1487
- return errorResult(error);
1488
- }
1489
- }
1490
- async invalidateChangedBackends(previous, next) {
1491
- const previousCaplets = new Map(allCaplets$1(previous).map((server) => [server.server, server]));
1492
- const nextCaplets = new Map(allCaplets$1(next).map((server) => [server.server, server]));
1493
- const changedIds = new Set([...previousCaplets.keys(), ...nextCaplets.keys()]);
1494
- for (const serverId of changedIds) {
1495
- const before = previousCaplets.get(serverId);
1496
- const after = nextCaplets.get(serverId);
1497
- if (!(serializeCaplet(before) !== serializeCaplet(after))) continue;
1498
- if (before?.backend === "mcp") await this.downstream.closeServer(serverId);
1499
- if (before?.backend === "openapi" || after?.backend === "openapi" || !after) this.openapi.invalidate(serverId);
1500
- if (before?.backend === "graphql" || after?.backend === "graphql" || !after) this.graphql.invalidate(serverId);
1501
- if (before?.backend === "http" || after?.backend === "http" || !after) this.http.invalidate(serverId);
1502
- if (before?.backend === "cli" || after?.backend === "cli" || !after) this.cli.invalidate(serverId);
1503
- }
1504
- }
1505
- resetWatchers() {
1506
- this.closeWatchers();
1507
- const watched = /* @__PURE__ */ new Set();
1508
- for (const entry of watchedPaths(this.paths)) {
1509
- const watchPath = existsSync(entry.path) ? entry.path : nearestExistingParent$1(entry.path);
1510
- const watchKey = `${entry.reason}:${watchPath}`;
1511
- if (!watchPath || watched.has(watchKey)) continue;
1512
- watched.add(watchKey);
1513
- try {
1514
- this.watchers.push(...this.watchEntry(entry, watchPath));
1515
- } catch (error) {
1516
- this.writeErr(`Caplets could not watch ${entry.reason} path ${entry.path}.\n`);
1517
- this.writeErr(`${JSON.stringify(toSafeError(error, "SERVER_UNAVAILABLE"), null, 2)}\n`);
1518
- }
1519
- }
1520
- }
1521
- closeWatchers() {
1522
- for (const watcher of this.watchers) watcher.close();
1523
- this.watchers = [];
1524
- }
1525
- watchEntry(entry, watchPath) {
1526
- if (entry.reason === "caplets" && existsSync(entry.path) && isDirectory(watchPath)) return this.watchDirectoryTree(watchPath);
1527
- return [watch(watchPath, { persistent: true }, (eventType) => {
1528
- this.scheduleReload();
1529
- if (eventType === "rename" && entry.reason === "caplets" && existsSync(entry.path)) this.scheduleWatcherRefresh();
1530
- })];
1531
- }
1532
- watchDirectoryTree(root) {
1533
- const watchers = [];
1534
- const directories = discoverDirectories(root);
1535
- for (const directory of directories) try {
1536
- watchers.push(watch(directory, { persistent: true }, (eventType) => {
1537
- this.scheduleReload();
1538
- if (eventType === "rename") this.scheduleWatcherRefresh();
1539
- }));
1540
- } catch (error) {
1541
- for (const watcher of watchers) watcher.close();
1542
- throw error;
1543
- }
1544
- return watchers;
1545
- }
1546
- scheduleWatcherRefresh() {
1547
- if (this.closed) return;
1548
- if (this.watcherRefreshTimer) clearTimeout(this.watcherRefreshTimer);
1549
- this.watcherRefreshTimer = setTimeout(() => {
1550
- this.watcherRefreshTimer = void 0;
1551
- if (!this.closed) this.resetWatchers();
1552
- }, this.watchDebounceMs);
1390
+ return await this.engine.execute(serverId, request);
1553
1391
  }
1554
1392
  };
1555
- function selectAuthOptions(authDir) {
1556
- return authDir ? { authDir } : {};
1557
- }
1558
- function watchedPaths(paths) {
1559
- return uniqueWatchedPaths([
1560
- {
1561
- path: dirname(paths.configPath),
1562
- reason: "config"
1563
- },
1564
- {
1565
- path: dirname(paths.projectConfigPath),
1566
- reason: "config"
1567
- },
1568
- {
1569
- path: resolveCapletsRoot(paths.configPath),
1570
- reason: "caplets"
1571
- },
1572
- {
1573
- path: dirname(paths.projectConfigPath),
1574
- reason: "caplets"
1575
- }
1576
- ]);
1577
- }
1578
- function uniqueWatchedPaths(entries) {
1579
- const seen = /* @__PURE__ */ new Set();
1580
- const unique = [];
1581
- for (const entry of entries) {
1582
- const key = `${entry.reason}:${entry.path}`;
1583
- if (seen.has(key)) continue;
1584
- seen.add(key);
1585
- unique.push(entry);
1586
- }
1587
- return unique;
1588
- }
1589
- function allCaplets$1(config) {
1393
+ function nextEnabledServers(config) {
1590
1394
  return [
1591
1395
  ...Object.values(config.mcpServers),
1592
1396
  ...Object.values(config.openapiEndpoints),
1593
1397
  ...Object.values(config.graphqlEndpoints),
1594
1398
  ...Object.values(config.httpApis),
1595
1399
  ...Object.values(config.cliTools)
1596
- ];
1597
- }
1598
- function nextEnabledServers(config) {
1599
- return allCaplets$1(config).filter((server) => !server.disabled);
1400
+ ].filter((server) => !server.disabled);
1600
1401
  }
1601
1402
  function capletById(config, serverId) {
1602
1403
  return config.mcpServers[serverId] ?? config.openapiEndpoints[serverId] ?? config.graphqlEndpoints[serverId] ?? config.httpApis[serverId] ?? config.cliTools[serverId];
@@ -1604,27 +1405,14 @@ function capletById(config, serverId) {
1604
1405
  function serializeCaplet(caplet) {
1605
1406
  return JSON.stringify(caplet ?? null);
1606
1407
  }
1607
- function nearestExistingParent$1(path) {
1608
- let candidate = dirname(path);
1609
- const root = parse(candidate).root;
1610
- while (candidate && candidate !== root) {
1611
- if (existsSync(candidate)) return candidate;
1612
- candidate = dirname(candidate);
1613
- }
1614
- return existsSync(root) ? root : void 0;
1615
- }
1616
- function discoverDirectories(root) {
1617
- if (!isDirectory(root)) return [];
1618
- const directories = [root];
1619
- for (const entry of readdirSync(root, { withFileTypes: true })) if (entry.isDirectory()) directories.push(...discoverDirectories(`${root}/${entry.name}`));
1620
- return directories;
1621
- }
1622
- function isDirectory(path) {
1623
- try {
1624
- return statSync(path).isDirectory();
1625
- } catch {
1626
- return false;
1627
- }
1408
+ function engineOptions(options) {
1409
+ const engineOptions = {};
1410
+ if (options.configPath !== void 0) engineOptions.configPath = options.configPath;
1411
+ if (options.projectConfigPath !== void 0) engineOptions.projectConfigPath = options.projectConfigPath;
1412
+ if (options.authDir !== void 0) engineOptions.authDir = options.authDir;
1413
+ if (options.watchDebounceMs !== void 0) engineOptions.watchDebounceMs = options.watchDebounceMs;
1414
+ if (options.writeErr !== void 0) engineOptions.writeErr = options.writeErr;
1415
+ return engineOptions;
1628
1416
  }
1629
1417
  //#endregion
1630
1418
  //#region ../../node_modules/.pnpm/commander@14.0.3/node_modules/commander/lib/error.js
@@ -5535,7 +5323,7 @@ function createProgram(io = {}) {
5535
5323
  const writeOut = io.writeOut ?? ((value) => process.stdout.write(value));
5536
5324
  const writeErr = io.writeErr ?? ((value) => process.stderr.write(value));
5537
5325
  const program = new Command();
5538
- program.name("caplets").description("Progressive-disclosure gateway for MCP servers.").version(version).exitOverride().configureOutput({
5326
+ program.name("caplets").description("Progressive-disclosure gateway for MCP servers.").version(io.version ?? version).exitOverride().configureOutput({
5539
5327
  writeOut,
5540
5328
  writeErr,
5541
5329
  outputError: (value, write) => write(value)
package/dist/native.js CHANGED
@@ -1,4 +1,4 @@
1
- import { E as errorResult, S as resolveProjectConfigPath, a as OpenApiManager, b as resolveConfigPath, c as DownstreamManager, h as loadConfig, i as capabilityDescription, m as CliToolsManager, n as handleServerTool, o as HttpActionManager, r as ServerRegistry, s as GraphQLManager, t as generatedToolInputSchema } from "./tools-BlFF20vE.js";
1
+ import { a as capabilityDescription, n as generatedToolInputSchema, t as CapletsEngine } from "./engine-BzekyZ5r.js";
2
2
  import { generatedToolInputJsonSchema } from "./generated-tool-input-schema.js";
3
3
  //#region src/native/tools.ts
4
4
  function nativeCapletToolName(capletId) {
@@ -46,27 +46,12 @@ function createNativeCapletsService(options = {}) {
46
46
  return new DefaultNativeCapletsService(options);
47
47
  }
48
48
  var DefaultNativeCapletsService = class {
49
- config;
50
- registry;
51
- downstream;
52
- openapi;
53
- graphql;
54
- http;
55
- cli;
49
+ engine;
56
50
  constructor(options) {
57
- const configPath = resolveConfigPath(options.configPath);
58
- const projectConfigPath = options.projectConfigPath ?? resolveProjectConfigPath();
59
- this.config = loadConfig(configPath, projectConfigPath);
60
- this.registry = new ServerRegistry(this.config);
61
- const authOptions = options.authDir ? { authDir: options.authDir } : void 0;
62
- this.downstream = new DownstreamManager(this.registry, authOptions);
63
- this.openapi = new OpenApiManager(this.registry, authOptions);
64
- this.graphql = new GraphQLManager(this.registry, authOptions);
65
- this.http = new HttpActionManager(this.registry, authOptions);
66
- this.cli = new CliToolsManager(this.registry);
51
+ this.engine = new CapletsEngine(options);
67
52
  }
68
53
  listTools() {
69
- return this.registry.enabledServers().map((caplet) => {
54
+ return this.engine.enabledServers().map((caplet) => {
70
55
  const toolName = nativeCapletToolName(caplet.server);
71
56
  return {
72
57
  caplet: caplet.server,
@@ -78,14 +63,16 @@ var DefaultNativeCapletsService = class {
78
63
  });
79
64
  }
80
65
  async execute(capletId, request) {
81
- try {
82
- return await handleServerTool(this.registry.require(capletId), request, this.registry, this.downstream, this.openapi, this.graphql, this.http, this.cli);
83
- } catch (error) {
84
- return errorResult(error);
85
- }
66
+ return await this.engine.execute(capletId, request);
67
+ }
68
+ async reload() {
69
+ return await this.engine.reload();
70
+ }
71
+ onToolsChanged(listener) {
72
+ return this.engine.onReload(() => listener(this.listTools()));
86
73
  }
87
74
  async close() {
88
- await this.downstream.close();
75
+ await this.engine.close();
89
76
  }
90
77
  };
91
78
  //#endregion
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@caplets/core",
3
- "version": "0.12.0",
3
+ "version": "0.12.2",
4
4
  "description": "Core runtime library for Caplets progressive disclosure gateways.",
5
5
  "keywords": [
6
6
  "caplets",