@alfe.ai/mcp-bundler 0.0.1 → 0.1.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/dist/index.d.ts CHANGED
@@ -235,7 +235,203 @@ declare function buildNamespacedToolName(server: string, tool: string): string;
235
235
  */
236
236
  declare function disambiguateAgainst(candidate: string, taken: ReadonlySet<string>): string;
237
237
  //# sourceMappingURL=tool-naming.d.ts.map
238
+ //#endregion
239
+ //#region src/store.d.ts
240
+ /**
241
+ * Where a server entry came from. Used by `removeServersByOwner` so an
242
+ * integration uninstall can drop only its own entries without touching
243
+ * `cli`-owned (e.g. `alfe-platform`) or `manual`-owned (user-added) ones.
244
+ */
245
+ type ServerOwner = 'cli' | `integration:${string}` | 'manual';
246
+ interface StoredServerCommon {
247
+ /** Where the entry came from — controls bulk-removal semantics. */
248
+ owner: ServerOwner;
249
+ /** ISO timestamp of first registration; preserved across updates. */
250
+ addedAt: string;
251
+ /** Optional semver of the providing package (e.g. `@alfe.ai/mcp-server` for `alfe-platform`). Used for drift detection on CLI upgrade. */
252
+ version?: string;
253
+ }
254
+ type StoredServerEntry = (StoredServerCommon & {
255
+ transport: 'stdio';
256
+ } & StdioServerConfig) | (StoredServerCommon & {
257
+ transport: 'sse' | 'streamable-http';
258
+ } & RemoteServerConfig);
259
+ interface StoreSchema {
260
+ servers: Record<string, StoredServerEntry>;
261
+ config: {
262
+ sessionIdleTtlMs?: number;
263
+ };
264
+ /**
265
+ * Server names this manager has written into `openclaw.json#mcp.servers.*`.
266
+ * Used to compute the mirror-write diff without re-reading openclaw.json
267
+ * (which would be a second source of truth). Foreign keys not listed here
268
+ * are preserved across mirror writes.
269
+ */
270
+ _ownedOpenclawKeys: string[];
271
+ }
272
+ interface StoreOptions {
273
+ /** Absolute path to the store file. Defaults to `~/.alfe/mcp/servers.json`. */
274
+ path?: string;
275
+ logger?: Logger;
276
+ }
277
+ /**
278
+ * On-disk source of truth for the bundler's configured servers.
279
+ *
280
+ * Mutations go through `update()` (read-modify-write with atomic
281
+ * temp+rename) so a concurrent writer (e.g. two `alfe mcp add` invocations
282
+ * racing) can't lose data — the second writer reads the first's state.
283
+ *
284
+ * Schema is owner-tagged so `removeServersByOwner` can implement
285
+ * integration uninstall without touching CLI-owned or manual entries.
286
+ */
287
+ declare class Store {
288
+ private readonly storePath;
289
+ private readonly logger?;
290
+ private watcher?;
291
+ private watcherListeners;
292
+ private rewatchTimer?;
293
+ constructor(opts?: StoreOptions);
294
+ get path(): string;
295
+ read(): StoreSchema;
296
+ /**
297
+ * Read-modify-write with atomic temp+rename, guarded by an
298
+ * inter-process lock file. Caller passes a pure function that
299
+ * produces the next state; this serialises the mutation to disk in
300
+ * one rename, which is atomic on POSIX and on Windows when the
301
+ * target path is on the same volume.
302
+ *
303
+ * The lock guards the read-then-rename window so two processes
304
+ * (e.g. two `alfe mcp add` shells, or the CLI racing the daemon)
305
+ * can't drop each other's writes. The lock file is at
306
+ * `<storePath>.lock`; stale locks (older than `LOCK_STALE_MS`) are
307
+ * stolen so a crashed writer doesn't wedge the store.
308
+ *
309
+ * Pure-function shape (instead of a `read()` then `write(next)`
310
+ * pair) intentionally — it keeps the read-modify-write contract
311
+ * local to each caller so two updates back-to-back never see each
312
+ * other's partial state.
313
+ */
314
+ update(fn: (cur: StoreSchema) => StoreSchema): StoreSchema;
315
+ /**
316
+ * Acquire an inter-process file lock by atomically creating a
317
+ * sentinel via `openSync(lockPath, 'wx')`. Spins with bounded
318
+ * backoff up to `LOCK_WAIT_MS`. If the lock file is older than
319
+ * `LOCK_STALE_MS` it's assumed orphaned (writer crashed mid-update)
320
+ * and stolen — the write window is sub-second in practice, so
321
+ * holding the lock for >5s means something went wrong.
322
+ *
323
+ * Returns the release function. Single-process callers are
324
+ * unaffected — re-entering the same process spins briefly while
325
+ * the prior call's `finally` runs.
326
+ */
327
+ private acquireLock;
328
+ private lockIsStale;
329
+ /**
330
+ * Watch the store file for external changes (e.g. another `alfe mcp add`
331
+ * shelling out from a separate process). Returns an unsubscribe fn.
332
+ *
333
+ * Coalesces bursts via a 50 ms debounce — editors and atomic-rename
334
+ * writers commonly fire multiple events per logical save.
335
+ */
336
+ watch(cb: () => void): () => void;
337
+ dispose(): void;
338
+ private ensureWatcher;
339
+ private disposeWatcher;
340
+ }
341
+ declare function defaultStorePath(): string;
342
+ /** Pull the runtime config (transport + transport-specific fields) out of a stored entry. */
343
+ declare function toServerConfig(entry: StoredServerEntry): McpServerConfig;
344
+ /** Build a stored entry from a runtime config + ownership metadata. */
345
+ declare function toStoredEntry(config: McpServerConfig, meta: {
346
+ owner: ServerOwner;
347
+ transport?: McpTransportKind;
348
+ version?: string;
349
+ addedAt?: string;
350
+ }): StoredServerEntry;
351
+ //#endregion
352
+ //#region src/manager.d.ts
353
+ interface ManagerOptions {
354
+ /** Pre-constructed store. If omitted, one is built with default options. */
355
+ store?: Store;
356
+ logger?: Logger;
357
+ }
358
+ interface AddServerOptions {
359
+ /** Required — flat-namespace key under the bundler store. */
360
+ id: string;
361
+ /** Marks ownership for bulk removal. Defaults to `manual`. */
362
+ owner?: ServerOwner;
363
+ /** Semver of the providing package; used for CLI version-drift detection. */
364
+ version?: string;
365
+ /** Explicit transport hint for remote configs. Defaults to inferring from `config`. */
366
+ transport?: McpTransportKind;
367
+ }
368
+ /**
369
+ * Bundler manager — owns the `~/.alfe/mcp/servers.json` store and surfaces a
370
+ * small CRUD API the CLI and integration applier both call into.
371
+ *
372
+ * Single source of truth: every consumer (daemon-hosted bundler, CLI `alfe mcp
373
+ * list`, integration uninstall) reads from this store. Openclaw.json is no
374
+ * longer kept in sync — the daemon hosts the bundler children and the
375
+ * openclaw plugin reaches them via IPC, so the openclaw.json mirror became
376
+ * dead weight and an active source of duplicate spawning on claude-cli /
377
+ * codex-cli backends.
378
+ *
379
+ * Call `loadIntoBundler(bundler)` once at daemon startup to wire the store
380
+ * into a live `McpBundler` — subsequent store mutations (including those
381
+ * landed by other processes via the file watcher) re-reconcile automatically.
382
+ */
383
+ declare class Manager {
384
+ private readonly store;
385
+ private readonly logger?;
386
+ private bundler?;
387
+ private changeListeners;
388
+ private storeUnsubscribe?;
389
+ constructor(opts?: ManagerOptions);
390
+ /** Direct accessor — handy for tests and the CLI's `alfe mcp list`. */
391
+ getStore(): Store;
392
+ /**
393
+ * Register or overwrite a server entry. Mutation lands in the store
394
+ * synchronously; if a bundler has been attached via `loadIntoBundler`,
395
+ * it gets re-reconciled in the background (errors logged, never
396
+ * thrown — the store is the source of truth, the bundler is derived).
397
+ */
398
+ addServer(config: McpServerConfig, opts: AddServerOptions): Promise<void>;
399
+ /**
400
+ * Remove a single server entry. No-op if the id isn't in the store.
401
+ * Refuses to remove an entry whose owner doesn't match `expectedOwner`
402
+ * when supplied — the CLI uses this to guard `alfe mcp remove` from
403
+ * accidentally clobbering integration- or cli-owned entries.
404
+ */
405
+ removeServer(id: string, opts?: {
406
+ expectedOwner?: ServerOwner;
407
+ }): Promise<boolean>;
408
+ /** Drop every entry whose owner matches — used by integration uninstall. */
409
+ removeServersByOwner(owner: ServerOwner): Promise<string[]>;
410
+ /** Read-only snapshot for `alfe mcp list` and similar UIs. */
411
+ listServers(): {
412
+ id: string;
413
+ entry: StoredServerEntry;
414
+ }[];
415
+ /**
416
+ * Push the current store contents into a bundler instance (which owns
417
+ * connections / tools). Wires up a store watcher so external mutations
418
+ * (e.g. another shell running `alfe mcp add`) re-reconcile.
419
+ */
420
+ loadIntoBundler(bundler: McpBundler): Promise<void>;
421
+ /** Subscribe to store mutations. Returns an unsubscribe fn. */
422
+ onChange(cb: () => void): () => void;
423
+ /**
424
+ * Detach from the bundler and stop watching the store. Safe to call
425
+ * multiple times. Does not dispose the underlying `Store` so the
426
+ * shared instance survives multi-manager environments (rare).
427
+ */
428
+ dispose(): Promise<void>;
429
+ private scheduleBundlerReconcile;
430
+ private reconcileBundler;
431
+ private fireChange;
432
+ }
433
+ //# sourceMappingURL=manager.d.ts.map
238
434
 
239
435
  //#endregion
240
- export { type BundlerOptions, Connection, type ConnectionDeps, type Logger, McpBundler, type McpClientHandle, type McpServerConfig, type McpToolCallResult, type McpToolDescriptor, type McpTransportKind, type ReconcileDiff, type RemoteServerConfig, STDIO_ENV_DENYLIST, type StdioServerConfig, buildNamespacedToolName, defaultConnect, disambiguateAgainst, sanitizeNameSegment, sanitizeStdioEnv };
436
+ export { type AddServerOptions, type BundlerOptions, Connection, type ConnectionDeps, type Logger, Manager, type ManagerOptions, McpBundler, type McpClientHandle, type McpServerConfig, type McpToolCallResult, type McpToolDescriptor, type McpTransportKind, type ReconcileDiff, type RemoteServerConfig, STDIO_ENV_DENYLIST, type ServerOwner, type StdioServerConfig, Store, type StoreOptions, type StoreSchema, type StoredServerEntry, buildNamespacedToolName, defaultConnect, defaultStorePath, disambiguateAgainst, sanitizeNameSegment, sanitizeStdioEnv, toServerConfig, toStoredEntry };
241
437
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","names":[],"sources":["../src/types.ts","../src/connection.ts","../src/bundler.ts","../src/tool-naming.ts"],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../src/types.ts","../src/connection.ts","../src/bundler.ts","../src/tool-naming.ts","../src/store.ts","../src/manager.ts"],"mappings":"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"}
package/dist/index.js CHANGED
@@ -1,3 +1,6 @@
1
+ import { closeSync, existsSync, mkdirSync, openSync, readFileSync, renameSync, statSync, unlinkSync, watch, writeFileSync } from "node:fs";
2
+ import { dirname, join } from "node:path";
3
+ import { homedir } from "node:os";
1
4
  //#region src/tool-naming.ts
2
5
  /**
3
6
  * Tool name sanitization and collision handling.
@@ -449,6 +452,445 @@ var McpBundler = class {
449
452
  }
450
453
  };
451
454
  //#endregion
452
- export { Connection, McpBundler, STDIO_ENV_DENYLIST, buildNamespacedToolName, defaultConnect, disambiguateAgainst, sanitizeNameSegment, sanitizeStdioEnv };
455
+ //#region src/store.ts
456
+ const DEFAULT_STORE_PATH = join(join(homedir(), ".alfe", "mcp"), "servers.json");
457
+ /** Inter-process lock tunings — exported as constants so tests can override. */
458
+ const LOCK_WAIT_MS = 5e3;
459
+ const LOCK_RETRY_INTERVAL_MS = 25;
460
+ const LOCK_STALE_MS = 1e4;
461
+ /**
462
+ * On-disk source of truth for the bundler's configured servers.
463
+ *
464
+ * Mutations go through `update()` (read-modify-write with atomic
465
+ * temp+rename) so a concurrent writer (e.g. two `alfe mcp add` invocations
466
+ * racing) can't lose data — the second writer reads the first's state.
467
+ *
468
+ * Schema is owner-tagged so `removeServersByOwner` can implement
469
+ * integration uninstall without touching CLI-owned or manual entries.
470
+ */
471
+ var Store = class {
472
+ storePath;
473
+ logger;
474
+ watcher;
475
+ watcherListeners = /* @__PURE__ */ new Set();
476
+ rewatchTimer;
477
+ constructor(opts = {}) {
478
+ this.storePath = opts.path ?? DEFAULT_STORE_PATH;
479
+ this.logger = opts.logger;
480
+ }
481
+ get path() {
482
+ return this.storePath;
483
+ }
484
+ read() {
485
+ if (!existsSync(this.storePath)) return cloneEmpty();
486
+ try {
487
+ const raw = readFileSync(this.storePath, "utf8");
488
+ return normalize(JSON.parse(raw));
489
+ } catch (err) {
490
+ this.logger?.warn("[mcp-bundler/store] failed to read store; returning empty", {
491
+ err: errMsg$1(err),
492
+ path: this.storePath
493
+ });
494
+ return cloneEmpty();
495
+ }
496
+ }
497
+ /**
498
+ * Read-modify-write with atomic temp+rename, guarded by an
499
+ * inter-process lock file. Caller passes a pure function that
500
+ * produces the next state; this serialises the mutation to disk in
501
+ * one rename, which is atomic on POSIX and on Windows when the
502
+ * target path is on the same volume.
503
+ *
504
+ * The lock guards the read-then-rename window so two processes
505
+ * (e.g. two `alfe mcp add` shells, or the CLI racing the daemon)
506
+ * can't drop each other's writes. The lock file is at
507
+ * `<storePath>.lock`; stale locks (older than `LOCK_STALE_MS`) are
508
+ * stolen so a crashed writer doesn't wedge the store.
509
+ *
510
+ * Pure-function shape (instead of a `read()` then `write(next)`
511
+ * pair) intentionally — it keeps the read-modify-write contract
512
+ * local to each caller so two updates back-to-back never see each
513
+ * other's partial state.
514
+ */
515
+ update(fn) {
516
+ mkdirSync(dirname(this.storePath), { recursive: true });
517
+ const release = this.acquireLock();
518
+ try {
519
+ const next = fn(this.read());
520
+ const tempPath = `${this.storePath}.${String(process.pid)}.${String(Date.now())}.tmp`;
521
+ writeFileSync(tempPath, JSON.stringify(next, null, 2), {
522
+ encoding: "utf8",
523
+ mode: 384
524
+ });
525
+ try {
526
+ renameSync(tempPath, this.storePath);
527
+ } catch (err) {
528
+ try {
529
+ unlinkSync(tempPath);
530
+ } catch {}
531
+ throw err;
532
+ }
533
+ return next;
534
+ } finally {
535
+ release();
536
+ }
537
+ }
538
+ /**
539
+ * Acquire an inter-process file lock by atomically creating a
540
+ * sentinel via `openSync(lockPath, 'wx')`. Spins with bounded
541
+ * backoff up to `LOCK_WAIT_MS`. If the lock file is older than
542
+ * `LOCK_STALE_MS` it's assumed orphaned (writer crashed mid-update)
543
+ * and stolen — the write window is sub-second in practice, so
544
+ * holding the lock for >5s means something went wrong.
545
+ *
546
+ * Returns the release function. Single-process callers are
547
+ * unaffected — re-entering the same process spins briefly while
548
+ * the prior call's `finally` runs.
549
+ */
550
+ acquireLock() {
551
+ const lockPath = `${this.storePath}.lock`;
552
+ const deadline = Date.now() + LOCK_WAIT_MS;
553
+ let fd = -1;
554
+ for (;;) try {
555
+ fd = openSync(lockPath, "wx", 384);
556
+ break;
557
+ } catch (err) {
558
+ if (err.code !== "EEXIST") throw err;
559
+ if (this.lockIsStale(lockPath)) {
560
+ try {
561
+ unlinkSync(lockPath);
562
+ } catch {}
563
+ continue;
564
+ }
565
+ if (Date.now() >= deadline) throw new Error(`Store.update: timed out waiting for ${lockPath} (held by another writer or stale lock)`);
566
+ const sleepUntil = Date.now() + LOCK_RETRY_INTERVAL_MS;
567
+ while (Date.now() < sleepUntil);
568
+ }
569
+ const held = fd;
570
+ return () => {
571
+ try {
572
+ closeSync(held);
573
+ } catch {}
574
+ try {
575
+ unlinkSync(lockPath);
576
+ } catch {}
577
+ };
578
+ }
579
+ lockIsStale(lockPath) {
580
+ try {
581
+ const st = statSync(lockPath);
582
+ return Date.now() - st.mtimeMs > LOCK_STALE_MS;
583
+ } catch {
584
+ return false;
585
+ }
586
+ }
587
+ /**
588
+ * Watch the store file for external changes (e.g. another `alfe mcp add`
589
+ * shelling out from a separate process). Returns an unsubscribe fn.
590
+ *
591
+ * Coalesces bursts via a 50 ms debounce — editors and atomic-rename
592
+ * writers commonly fire multiple events per logical save.
593
+ */
594
+ watch(cb) {
595
+ this.watcherListeners.add(cb);
596
+ this.ensureWatcher();
597
+ return () => {
598
+ this.watcherListeners.delete(cb);
599
+ if (this.watcherListeners.size === 0) this.disposeWatcher();
600
+ };
601
+ }
602
+ dispose() {
603
+ this.watcherListeners.clear();
604
+ this.disposeWatcher();
605
+ }
606
+ ensureWatcher() {
607
+ if (this.watcher) return;
608
+ mkdirSync(dirname(this.storePath), { recursive: true });
609
+ const dir = dirname(this.storePath);
610
+ const basename = this.storePath.slice(dir.length + 1);
611
+ let pending;
612
+ const fire = () => {
613
+ pending = void 0;
614
+ for (const cb of this.watcherListeners) try {
615
+ cb();
616
+ } catch (err) {
617
+ this.logger?.warn("[mcp-bundler/store] watcher listener threw", { err: errMsg$1(err) });
618
+ }
619
+ };
620
+ try {
621
+ this.watcher = watch(dir, (_event, fn) => {
622
+ if (fn !== basename) return;
623
+ if (pending) clearTimeout(pending);
624
+ pending = setTimeout(fire, 50);
625
+ });
626
+ this.watcher.on("error", (err) => {
627
+ this.logger?.warn("[mcp-bundler/store] watcher error; retrying in 1s", { err: errMsg$1(err) });
628
+ this.disposeWatcher();
629
+ if (!this.rewatchTimer && this.watcherListeners.size > 0) {
630
+ this.rewatchTimer = setTimeout(() => {
631
+ this.rewatchTimer = void 0;
632
+ this.ensureWatcher();
633
+ }, 1e3);
634
+ this.rewatchTimer.unref();
635
+ }
636
+ });
637
+ } catch (err) {
638
+ this.logger?.warn("[mcp-bundler/store] failed to start watcher", { err: errMsg$1(err) });
639
+ }
640
+ }
641
+ disposeWatcher() {
642
+ if (this.watcher) {
643
+ try {
644
+ this.watcher.close();
645
+ } catch {}
646
+ this.watcher = void 0;
647
+ }
648
+ if (this.rewatchTimer) {
649
+ clearTimeout(this.rewatchTimer);
650
+ this.rewatchTimer = void 0;
651
+ }
652
+ }
653
+ };
654
+ function defaultStorePath() {
655
+ return DEFAULT_STORE_PATH;
656
+ }
657
+ /** Pull the runtime config (transport + transport-specific fields) out of a stored entry. */
658
+ function toServerConfig(entry) {
659
+ if (entry.transport === "stdio") {
660
+ const { command, args, env, cwd } = entry;
661
+ const cfg = { command };
662
+ if (args) cfg.args = args;
663
+ if (env) cfg.env = env;
664
+ if (cwd) cfg.cwd = cwd;
665
+ return cfg;
666
+ }
667
+ const { url, transport, headers, connectionTimeoutMs } = entry;
668
+ const cfg = {
669
+ url,
670
+ transport
671
+ };
672
+ if (headers) cfg.headers = headers;
673
+ if (connectionTimeoutMs !== void 0) cfg.connectionTimeoutMs = connectionTimeoutMs;
674
+ return cfg;
675
+ }
676
+ /** Build a stored entry from a runtime config + ownership metadata. */
677
+ function toStoredEntry(config, meta) {
678
+ const addedAt = meta.addedAt ?? (/* @__PURE__ */ new Date()).toISOString();
679
+ if ("command" in config) return {
680
+ transport: "stdio",
681
+ owner: meta.owner,
682
+ addedAt,
683
+ ...meta.version !== void 0 ? { version: meta.version } : {},
684
+ ...config
685
+ };
686
+ const transport = meta.transport ?? config.transport ?? "sse";
687
+ if (transport === "stdio") throw new Error("toStoredEntry: transport=stdio specified but config is remote-shaped");
688
+ return {
689
+ transport,
690
+ owner: meta.owner,
691
+ addedAt,
692
+ ...meta.version !== void 0 ? { version: meta.version } : {},
693
+ ...config
694
+ };
695
+ }
696
+ function cloneEmpty() {
697
+ return {
698
+ servers: {},
699
+ config: {},
700
+ _ownedOpenclawKeys: []
701
+ };
702
+ }
703
+ function normalize(raw) {
704
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) return cloneEmpty();
705
+ const r = raw;
706
+ return {
707
+ servers: r.servers && typeof r.servers === "object" ? r.servers : {},
708
+ config: r.config && typeof r.config === "object" ? r.config : {},
709
+ _ownedOpenclawKeys: Array.isArray(r._ownedOpenclawKeys) ? r._ownedOpenclawKeys.slice() : []
710
+ };
711
+ }
712
+ function errMsg$1(err) {
713
+ return err instanceof Error ? err.message : String(err);
714
+ }
715
+ //#endregion
716
+ //#region src/manager.ts
717
+ /**
718
+ * Bundler manager — owns the `~/.alfe/mcp/servers.json` store and surfaces a
719
+ * small CRUD API the CLI and integration applier both call into.
720
+ *
721
+ * Single source of truth: every consumer (daemon-hosted bundler, CLI `alfe mcp
722
+ * list`, integration uninstall) reads from this store. Openclaw.json is no
723
+ * longer kept in sync — the daemon hosts the bundler children and the
724
+ * openclaw plugin reaches them via IPC, so the openclaw.json mirror became
725
+ * dead weight and an active source of duplicate spawning on claude-cli /
726
+ * codex-cli backends.
727
+ *
728
+ * Call `loadIntoBundler(bundler)` once at daemon startup to wire the store
729
+ * into a live `McpBundler` — subsequent store mutations (including those
730
+ * landed by other processes via the file watcher) re-reconcile automatically.
731
+ */
732
+ var Manager = class {
733
+ store;
734
+ logger;
735
+ bundler;
736
+ changeListeners = /* @__PURE__ */ new Set();
737
+ storeUnsubscribe;
738
+ constructor(opts = {}) {
739
+ this.store = opts.store ?? new Store({ logger: opts.logger });
740
+ this.logger = opts.logger;
741
+ }
742
+ /** Direct accessor — handy for tests and the CLI's `alfe mcp list`. */
743
+ getStore() {
744
+ return this.store;
745
+ }
746
+ /**
747
+ * Register or overwrite a server entry. Mutation lands in the store
748
+ * synchronously; if a bundler has been attached via `loadIntoBundler`,
749
+ * it gets re-reconciled in the background (errors logged, never
750
+ * thrown — the store is the source of truth, the bundler is derived).
751
+ */
752
+ async addServer(config, opts) {
753
+ if (!opts.id) throw new Error("Manager.addServer: id is required");
754
+ const owner = opts.owner ?? "manual";
755
+ this.store.update((cur) => {
756
+ const previousAddedAt = lookupAddedAt(cur.servers, opts.id);
757
+ const entry = toStoredEntry(config, {
758
+ owner,
759
+ transport: opts.transport,
760
+ version: opts.version,
761
+ addedAt: previousAddedAt
762
+ });
763
+ return {
764
+ ...cur,
765
+ servers: {
766
+ ...cur.servers,
767
+ [opts.id]: entry
768
+ }
769
+ };
770
+ });
771
+ this.scheduleBundlerReconcile();
772
+ this.fireChange();
773
+ return Promise.resolve();
774
+ }
775
+ /**
776
+ * Remove a single server entry. No-op if the id isn't in the store.
777
+ * Refuses to remove an entry whose owner doesn't match `expectedOwner`
778
+ * when supplied — the CLI uses this to guard `alfe mcp remove` from
779
+ * accidentally clobbering integration- or cli-owned entries.
780
+ */
781
+ removeServer(id, opts = {}) {
782
+ const existing = lookupEntry(this.store.read().servers, id);
783
+ if (!existing) return Promise.resolve(false);
784
+ if (opts.expectedOwner && existing.owner !== opts.expectedOwner) return Promise.reject(/* @__PURE__ */ new Error(`Manager.removeServer: server "${id}" is owned by "${existing.owner}", not "${opts.expectedOwner}"`));
785
+ this.store.update((cur) => ({
786
+ ...cur,
787
+ servers: Object.fromEntries(Object.entries(cur.servers).filter(([k]) => k !== id))
788
+ }));
789
+ this.scheduleBundlerReconcile();
790
+ this.fireChange();
791
+ return Promise.resolve(true);
792
+ }
793
+ /** Drop every entry whose owner matches — used by integration uninstall. */
794
+ async removeServersByOwner(owner) {
795
+ const removed = [];
796
+ this.store.update((cur) => {
797
+ const next = {};
798
+ for (const [id, entry] of Object.entries(cur.servers)) if (entry.owner === owner) removed.push(id);
799
+ else next[id] = entry;
800
+ if (removed.length === 0) return cur;
801
+ return {
802
+ ...cur,
803
+ servers: next
804
+ };
805
+ });
806
+ if (removed.length > 0) {
807
+ this.scheduleBundlerReconcile();
808
+ this.fireChange();
809
+ }
810
+ return Promise.resolve(removed);
811
+ }
812
+ /** Read-only snapshot for `alfe mcp list` and similar UIs. */
813
+ listServers() {
814
+ const snap = this.store.read();
815
+ return Object.entries(snap.servers).map(([id, entry]) => ({
816
+ id,
817
+ entry
818
+ }));
819
+ }
820
+ /**
821
+ * Push the current store contents into a bundler instance (which owns
822
+ * connections / tools). Wires up a store watcher so external mutations
823
+ * (e.g. another shell running `alfe mcp add`) re-reconcile.
824
+ */
825
+ async loadIntoBundler(bundler) {
826
+ this.bundler = bundler;
827
+ await this.reconcileBundler();
828
+ this.storeUnsubscribe ??= this.store.watch(() => {
829
+ this.reconcileBundler().catch((err) => {
830
+ this.logger?.warn("[mcp-bundler/manager] watcher reconcile failed", { err: errMsg(err) });
831
+ });
832
+ });
833
+ }
834
+ /** Subscribe to store mutations. Returns an unsubscribe fn. */
835
+ onChange(cb) {
836
+ this.changeListeners.add(cb);
837
+ return () => {
838
+ this.changeListeners.delete(cb);
839
+ };
840
+ }
841
+ /**
842
+ * Detach from the bundler and stop watching the store. Safe to call
843
+ * multiple times. Does not dispose the underlying `Store` so the
844
+ * shared instance survives multi-manager environments (rare).
845
+ */
846
+ async dispose() {
847
+ if (this.storeUnsubscribe) {
848
+ this.storeUnsubscribe();
849
+ this.storeUnsubscribe = void 0;
850
+ }
851
+ this.store.dispose();
852
+ this.changeListeners.clear();
853
+ this.bundler = void 0;
854
+ return Promise.resolve();
855
+ }
856
+ scheduleBundlerReconcile() {
857
+ if (!this.bundler) return;
858
+ this.reconcileBundler().catch((err) => {
859
+ this.logger?.warn("[mcp-bundler/manager] bundler reconcile failed", { err: errMsg(err) });
860
+ });
861
+ }
862
+ async reconcileBundler() {
863
+ if (!this.bundler) return;
864
+ const snap = this.store.read();
865
+ const servers = {};
866
+ for (const [id, entry] of Object.entries(snap.servers)) servers[id] = toServerConfig(entry);
867
+ await this.bundler.reconcile(servers);
868
+ }
869
+ fireChange() {
870
+ for (const cb of this.changeListeners) try {
871
+ cb();
872
+ } catch (err) {
873
+ this.logger?.warn("[mcp-bundler/manager] onChange listener threw", { err: errMsg(err) });
874
+ }
875
+ }
876
+ };
877
+ function errMsg(err) {
878
+ return err instanceof Error ? err.message : String(err);
879
+ }
880
+ /**
881
+ * Indexed access on `Record<string, T>` returns `T` (not `T | undefined`)
882
+ * unless `noUncheckedIndexedAccess` is set in tsconfig. These helpers
883
+ * make the optional-ness explicit so the lint rules that hate
884
+ * always-truthy conditionals stop firing on real lookups.
885
+ */
886
+ function lookupEntry(servers, id) {
887
+ return Object.hasOwn(servers, id) ? servers[id] : void 0;
888
+ }
889
+ function lookupAddedAt(servers, id) {
890
+ const entry = lookupEntry(servers, id);
891
+ return entry ? entry.addedAt : void 0;
892
+ }
893
+ //#endregion
894
+ export { Connection, Manager, McpBundler, STDIO_ENV_DENYLIST, Store, buildNamespacedToolName, defaultConnect, defaultStorePath, disambiguateAgainst, sanitizeNameSegment, sanitizeStdioEnv, toServerConfig, toStoredEntry };
453
895
 
454
896
  //# sourceMappingURL=index.js.map