@darkiceinteractive/mcp-conductor 1.1.0 → 3.0.0-beta.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 (293) hide show
  1. package/README.md +35 -5
  2. package/dist/bin/cli.d.ts +20 -0
  3. package/dist/bin/cli.d.ts.map +1 -0
  4. package/dist/bin/cli.js +260 -0
  5. package/dist/bin/cli.js.map +1 -0
  6. package/dist/bridge/http-server.d.ts +65 -1
  7. package/dist/bridge/http-server.d.ts.map +1 -1
  8. package/dist/bridge/http-server.js +192 -7
  9. package/dist/bridge/http-server.js.map +1 -1
  10. package/dist/bridge/index.d.ts +1 -0
  11. package/dist/bridge/index.d.ts.map +1 -1
  12. package/dist/bridge/index.js +1 -0
  13. package/dist/bridge/index.js.map +1 -1
  14. package/dist/bridge/pool.d.ts +95 -0
  15. package/dist/bridge/pool.d.ts.map +1 -0
  16. package/dist/bridge/pool.js +384 -0
  17. package/dist/bridge/pool.js.map +1 -0
  18. package/dist/bridge/session-registry.d.ts +64 -0
  19. package/dist/bridge/session-registry.d.ts.map +1 -0
  20. package/dist/bridge/session-registry.js +124 -0
  21. package/dist/bridge/session-registry.js.map +1 -0
  22. package/dist/cache/cache.d.ts +43 -0
  23. package/dist/cache/cache.d.ts.map +1 -0
  24. package/dist/cache/cache.js +167 -0
  25. package/dist/cache/cache.js.map +1 -0
  26. package/dist/cache/delta.d.ts +32 -0
  27. package/dist/cache/delta.d.ts.map +1 -0
  28. package/dist/cache/delta.js +131 -0
  29. package/dist/cache/delta.js.map +1 -0
  30. package/dist/cache/disk.d.ts +65 -0
  31. package/dist/cache/disk.d.ts.map +1 -0
  32. package/dist/cache/disk.js +238 -0
  33. package/dist/cache/disk.js.map +1 -0
  34. package/dist/cache/index.d.ts +53 -0
  35. package/dist/cache/index.d.ts.map +1 -0
  36. package/dist/cache/index.js +12 -0
  37. package/dist/cache/index.js.map +1 -0
  38. package/dist/cache/key.d.ts +44 -0
  39. package/dist/cache/key.d.ts.map +1 -0
  40. package/dist/cache/key.js +83 -0
  41. package/dist/cache/key.js.map +1 -0
  42. package/dist/cache/lru.d.ts +57 -0
  43. package/dist/cache/lru.d.ts.map +1 -0
  44. package/dist/cache/lru.js +112 -0
  45. package/dist/cache/lru.js.map +1 -0
  46. package/dist/cache/policy.d.ts +34 -0
  47. package/dist/cache/policy.d.ts.map +1 -0
  48. package/dist/cache/policy.js +95 -0
  49. package/dist/cache/policy.js.map +1 -0
  50. package/dist/cli/commands/doctor.d.ts +33 -0
  51. package/dist/cli/commands/doctor.d.ts.map +1 -0
  52. package/dist/cli/commands/doctor.js +135 -0
  53. package/dist/cli/commands/doctor.js.map +1 -0
  54. package/dist/cli/commands/export-servers.d.ts +22 -0
  55. package/dist/cli/commands/export-servers.d.ts.map +1 -0
  56. package/dist/cli/commands/export-servers.js +45 -0
  57. package/dist/cli/commands/export-servers.js.map +1 -0
  58. package/dist/cli/commands/import-servers.d.ts +57 -0
  59. package/dist/cli/commands/import-servers.d.ts.map +1 -0
  60. package/dist/cli/commands/import-servers.js +137 -0
  61. package/dist/cli/commands/import-servers.js.map +1 -0
  62. package/dist/cli/commands/routing.d.ts +34 -0
  63. package/dist/cli/commands/routing.d.ts.map +1 -0
  64. package/dist/cli/commands/routing.js +60 -0
  65. package/dist/cli/commands/routing.js.map +1 -0
  66. package/dist/cli/commands/test-server.d.ts +34 -0
  67. package/dist/cli/commands/test-server.d.ts.map +1 -0
  68. package/dist/cli/commands/test-server.js +86 -0
  69. package/dist/cli/commands/test-server.js.map +1 -0
  70. package/dist/cli/daemon.d.ts +60 -0
  71. package/dist/cli/daemon.d.ts.map +1 -0
  72. package/dist/cli/daemon.js +244 -0
  73. package/dist/cli/daemon.js.map +1 -0
  74. package/dist/cli/replay.d.ts +16 -0
  75. package/dist/cli/replay.d.ts.map +1 -0
  76. package/dist/cli/replay.js +89 -0
  77. package/dist/cli/replay.js.map +1 -0
  78. package/dist/cli/wizard/setup.d.ts +12 -0
  79. package/dist/cli/wizard/setup.d.ts.map +1 -0
  80. package/dist/cli/wizard/setup.js +71 -0
  81. package/dist/cli/wizard/setup.js.map +1 -0
  82. package/dist/config/defaults.d.ts +10 -0
  83. package/dist/config/defaults.d.ts.map +1 -1
  84. package/dist/config/defaults.js +14 -1
  85. package/dist/config/defaults.js.map +1 -1
  86. package/dist/config/schema.d.ts +34 -0
  87. package/dist/config/schema.d.ts.map +1 -1
  88. package/dist/daemon/client.d.ts +97 -0
  89. package/dist/daemon/client.d.ts.map +1 -0
  90. package/dist/daemon/client.js +279 -0
  91. package/dist/daemon/client.js.map +1 -0
  92. package/dist/daemon/discovery.d.ts +50 -0
  93. package/dist/daemon/discovery.d.ts.map +1 -0
  94. package/dist/daemon/discovery.js +104 -0
  95. package/dist/daemon/discovery.js.map +1 -0
  96. package/dist/daemon/index.d.ts +16 -0
  97. package/dist/daemon/index.d.ts.map +1 -0
  98. package/dist/daemon/index.js +11 -0
  99. package/dist/daemon/index.js.map +1 -0
  100. package/dist/daemon/sandbox-api.d.ts +45 -0
  101. package/dist/daemon/sandbox-api.d.ts.map +1 -0
  102. package/dist/daemon/sandbox-api.js +74 -0
  103. package/dist/daemon/sandbox-api.js.map +1 -0
  104. package/dist/daemon/server.d.ts +65 -0
  105. package/dist/daemon/server.d.ts.map +1 -0
  106. package/dist/daemon/server.js +351 -0
  107. package/dist/daemon/server.js.map +1 -0
  108. package/dist/daemon/shared-kv.d.ts +81 -0
  109. package/dist/daemon/shared-kv.d.ts.map +1 -0
  110. package/dist/daemon/shared-kv.js +215 -0
  111. package/dist/daemon/shared-kv.js.map +1 -0
  112. package/dist/daemon/shared-lock.d.ts +71 -0
  113. package/dist/daemon/shared-lock.d.ts.map +1 -0
  114. package/dist/daemon/shared-lock.js +119 -0
  115. package/dist/daemon/shared-lock.js.map +1 -0
  116. package/dist/hub/mcp-hub.d.ts +23 -0
  117. package/dist/hub/mcp-hub.d.ts.map +1 -1
  118. package/dist/hub/mcp-hub.js +34 -1
  119. package/dist/hub/mcp-hub.js.map +1 -1
  120. package/dist/index.js +19 -0
  121. package/dist/index.js.map +1 -1
  122. package/dist/observability/anomaly.d.ts +67 -0
  123. package/dist/observability/anomaly.d.ts.map +1 -0
  124. package/dist/observability/anomaly.js +141 -0
  125. package/dist/observability/anomaly.js.map +1 -0
  126. package/dist/observability/cost-predictor.d.ts +49 -0
  127. package/dist/observability/cost-predictor.d.ts.map +1 -0
  128. package/dist/observability/cost-predictor.js +145 -0
  129. package/dist/observability/cost-predictor.js.map +1 -0
  130. package/dist/observability/hot-path.d.ts +49 -0
  131. package/dist/observability/hot-path.d.ts.map +1 -0
  132. package/dist/observability/hot-path.js +125 -0
  133. package/dist/observability/hot-path.js.map +1 -0
  134. package/dist/observability/index.d.ts +10 -0
  135. package/dist/observability/index.d.ts.map +1 -0
  136. package/dist/observability/index.js +10 -0
  137. package/dist/observability/index.js.map +1 -0
  138. package/dist/observability/replay.d.ts +104 -0
  139. package/dist/observability/replay.d.ts.map +1 -0
  140. package/dist/observability/replay.js +239 -0
  141. package/dist/observability/replay.js.map +1 -0
  142. package/dist/registry/built-in-recommendations.d.ts +54 -0
  143. package/dist/registry/built-in-recommendations.d.ts.map +1 -0
  144. package/dist/registry/built-in-recommendations.js +65 -0
  145. package/dist/registry/built-in-recommendations.js.map +1 -0
  146. package/dist/registry/events.d.ts +26 -0
  147. package/dist/registry/events.d.ts.map +1 -0
  148. package/dist/registry/events.js +22 -0
  149. package/dist/registry/events.js.map +1 -0
  150. package/dist/registry/index.d.ts +159 -0
  151. package/dist/registry/index.d.ts.map +1 -0
  152. package/dist/registry/index.js +12 -0
  153. package/dist/registry/index.js.map +1 -0
  154. package/dist/registry/registry.d.ts +87 -0
  155. package/dist/registry/registry.d.ts.map +1 -0
  156. package/dist/registry/registry.js +294 -0
  157. package/dist/registry/registry.js.map +1 -0
  158. package/dist/registry/snapshot.d.ts +42 -0
  159. package/dist/registry/snapshot.d.ts.map +1 -0
  160. package/dist/registry/snapshot.js +71 -0
  161. package/dist/registry/snapshot.js.map +1 -0
  162. package/dist/registry/typegen.d.ts +48 -0
  163. package/dist/registry/typegen.d.ts.map +1 -0
  164. package/dist/registry/typegen.js +200 -0
  165. package/dist/registry/typegen.js.map +1 -0
  166. package/dist/registry/validator.d.ts +23 -0
  167. package/dist/registry/validator.d.ts.map +1 -0
  168. package/dist/registry/validator.js +50 -0
  169. package/dist/registry/validator.js.map +1 -0
  170. package/dist/reliability/breaker.d.ts +57 -0
  171. package/dist/reliability/breaker.d.ts.map +1 -0
  172. package/dist/reliability/breaker.js +130 -0
  173. package/dist/reliability/breaker.js.map +1 -0
  174. package/dist/reliability/errors.d.ts +78 -0
  175. package/dist/reliability/errors.d.ts.map +1 -0
  176. package/dist/reliability/errors.js +160 -0
  177. package/dist/reliability/errors.js.map +1 -0
  178. package/dist/reliability/gateway.d.ts +88 -0
  179. package/dist/reliability/gateway.d.ts.map +1 -0
  180. package/dist/reliability/gateway.js +180 -0
  181. package/dist/reliability/gateway.js.map +1 -0
  182. package/dist/reliability/index.d.ts +20 -0
  183. package/dist/reliability/index.d.ts.map +1 -0
  184. package/dist/reliability/index.js +16 -0
  185. package/dist/reliability/index.js.map +1 -0
  186. package/dist/reliability/profile.d.ts +49 -0
  187. package/dist/reliability/profile.d.ts.map +1 -0
  188. package/dist/reliability/profile.js +58 -0
  189. package/dist/reliability/profile.js.map +1 -0
  190. package/dist/reliability/retry.d.ts +39 -0
  191. package/dist/reliability/retry.d.ts.map +1 -0
  192. package/dist/reliability/retry.js +51 -0
  193. package/dist/reliability/retry.js.map +1 -0
  194. package/dist/reliability/timeout.d.ts +34 -0
  195. package/dist/reliability/timeout.d.ts.map +1 -0
  196. package/dist/reliability/timeout.js +53 -0
  197. package/dist/reliability/timeout.js.map +1 -0
  198. package/dist/runtime/executor.d.ts +12 -0
  199. package/dist/runtime/executor.d.ts.map +1 -1
  200. package/dist/runtime/executor.js +148 -16
  201. package/dist/runtime/executor.js.map +1 -1
  202. package/dist/runtime/findtool/embed.d.ts +28 -0
  203. package/dist/runtime/findtool/embed.d.ts.map +1 -0
  204. package/dist/runtime/findtool/embed.js +85 -0
  205. package/dist/runtime/findtool/embed.js.map +1 -0
  206. package/dist/runtime/findtool/index.d.ts +52 -0
  207. package/dist/runtime/findtool/index.d.ts.map +1 -0
  208. package/dist/runtime/findtool/index.js +78 -0
  209. package/dist/runtime/findtool/index.js.map +1 -0
  210. package/dist/runtime/findtool/vector-index.d.ts +53 -0
  211. package/dist/runtime/findtool/vector-index.d.ts.map +1 -0
  212. package/dist/runtime/findtool/vector-index.js +71 -0
  213. package/dist/runtime/findtool/vector-index.js.map +1 -0
  214. package/dist/runtime/helpers/budget.d.ts +27 -0
  215. package/dist/runtime/helpers/budget.d.ts.map +1 -0
  216. package/dist/runtime/helpers/budget.js +103 -0
  217. package/dist/runtime/helpers/budget.js.map +1 -0
  218. package/dist/runtime/helpers/compact.d.ts +32 -0
  219. package/dist/runtime/helpers/compact.d.ts.map +1 -0
  220. package/dist/runtime/helpers/compact.js +93 -0
  221. package/dist/runtime/helpers/compact.js.map +1 -0
  222. package/dist/runtime/helpers/delta.d.ts +45 -0
  223. package/dist/runtime/helpers/delta.d.ts.map +1 -0
  224. package/dist/runtime/helpers/delta.js +116 -0
  225. package/dist/runtime/helpers/delta.js.map +1 -0
  226. package/dist/runtime/helpers/index.d.ts +16 -0
  227. package/dist/runtime/helpers/index.d.ts.map +1 -0
  228. package/dist/runtime/helpers/index.js +13 -0
  229. package/dist/runtime/helpers/index.js.map +1 -0
  230. package/dist/runtime/helpers/summarize.d.ts +24 -0
  231. package/dist/runtime/helpers/summarize.d.ts.map +1 -0
  232. package/dist/runtime/helpers/summarize.js +124 -0
  233. package/dist/runtime/helpers/summarize.js.map +1 -0
  234. package/dist/runtime/helpers/worker-preload.d.ts +25 -0
  235. package/dist/runtime/helpers/worker-preload.d.ts.map +1 -0
  236. package/dist/runtime/helpers/worker-preload.js +223 -0
  237. package/dist/runtime/helpers/worker-preload.js.map +1 -0
  238. package/dist/runtime/index.d.ts +1 -0
  239. package/dist/runtime/index.d.ts.map +1 -1
  240. package/dist/runtime/index.js +1 -0
  241. package/dist/runtime/index.js.map +1 -1
  242. package/dist/runtime/pool/index.d.ts +11 -0
  243. package/dist/runtime/pool/index.d.ts.map +1 -0
  244. package/dist/runtime/pool/index.js +8 -0
  245. package/dist/runtime/pool/index.js.map +1 -0
  246. package/dist/runtime/pool/recycle.d.ts +44 -0
  247. package/dist/runtime/pool/recycle.d.ts.map +1 -0
  248. package/dist/runtime/pool/recycle.js +50 -0
  249. package/dist/runtime/pool/recycle.js.map +1 -0
  250. package/dist/runtime/pool/worker-pool.d.ts +77 -0
  251. package/dist/runtime/pool/worker-pool.d.ts.map +1 -0
  252. package/dist/runtime/pool/worker-pool.js +216 -0
  253. package/dist/runtime/pool/worker-pool.js.map +1 -0
  254. package/dist/runtime/pool/worker.d.ts +80 -0
  255. package/dist/runtime/pool/worker.d.ts.map +1 -0
  256. package/dist/runtime/pool/worker.js +324 -0
  257. package/dist/runtime/pool/worker.js.map +1 -0
  258. package/dist/server/mcp-server.d.ts +6 -0
  259. package/dist/server/mcp-server.d.ts.map +1 -1
  260. package/dist/server/mcp-server.js +610 -45
  261. package/dist/server/mcp-server.js.map +1 -1
  262. package/dist/server/passthrough-registrar.d.ts +73 -0
  263. package/dist/server/passthrough-registrar.d.ts.map +1 -0
  264. package/dist/server/passthrough-registrar.js +110 -0
  265. package/dist/server/passthrough-registrar.js.map +1 -0
  266. package/dist/skills/skills-engine.d.ts +9 -1
  267. package/dist/skills/skills-engine.d.ts.map +1 -1
  268. package/dist/skills/skills-engine.js +20 -3
  269. package/dist/skills/skills-engine.js.map +1 -1
  270. package/dist/utils/index.d.ts +3 -0
  271. package/dist/utils/index.d.ts.map +1 -1
  272. package/dist/utils/index.js +3 -0
  273. package/dist/utils/index.js.map +1 -1
  274. package/dist/utils/logger.d.ts.map +1 -1
  275. package/dist/utils/logger.js +5 -1
  276. package/dist/utils/logger.js.map +1 -1
  277. package/dist/utils/orphan-watch.d.ts +34 -0
  278. package/dist/utils/orphan-watch.d.ts.map +1 -0
  279. package/dist/utils/orphan-watch.js +54 -0
  280. package/dist/utils/orphan-watch.js.map +1 -0
  281. package/dist/utils/redact.d.ts +15 -0
  282. package/dist/utils/redact.d.ts.map +1 -0
  283. package/dist/utils/redact.js +48 -0
  284. package/dist/utils/redact.js.map +1 -0
  285. package/dist/utils/tokenize.d.ts +55 -0
  286. package/dist/utils/tokenize.d.ts.map +1 -0
  287. package/dist/utils/tokenize.js +205 -0
  288. package/dist/utils/tokenize.js.map +1 -0
  289. package/dist/version.d.ts +3 -3
  290. package/dist/version.d.ts.map +1 -1
  291. package/dist/version.js +3 -3
  292. package/dist/version.js.map +1 -1
  293. package/package.json +13 -3
@@ -0,0 +1,81 @@
1
+ /**
2
+ * Shared Key-Value Store for MCP Conductor Daemon.
3
+ *
4
+ * Provides in-memory storage with optional disk persistence and TTL support.
5
+ * Each KV entry can carry an expiry timestamp; a background sweep runs every
6
+ * 30 s to evict stale entries and sync the on-disk snapshot.
7
+ *
8
+ * @module daemon/shared-kv
9
+ */
10
+ /** Options accepted by {@link SharedKV.set}. */
11
+ export interface KVSetOptions {
12
+ /** Time-to-live in milliseconds. Entry is deleted after this duration. */
13
+ ttl?: number;
14
+ }
15
+ /** Options for constructing a {@link SharedKV} instance. */
16
+ export interface SharedKVOptions {
17
+ /** Directory for disk persistence. Defaults to `~/.mcp-conductor/kv/`. */
18
+ persistDir?: string;
19
+ /** Sweep interval for TTL expiry, in milliseconds. Defaults to 30 000. */
20
+ sweepIntervalMs?: number;
21
+ /** Namespace / shard name used for the snapshot file. Defaults to `default`. */
22
+ namespace?: string;
23
+ /** Whether to skip loading from disk on construction. Useful in tests. */
24
+ skipLoad?: boolean;
25
+ }
26
+ /**
27
+ * Shared key-value store with TTL expiry and disk persistence.
28
+ *
29
+ * Thread-safety note: Node.js is single-threaded; all mutations are
30
+ * synchronous operations on the in-memory Map, so no locking is required
31
+ * within a single process. Cross-process sharing is handled by the daemon
32
+ * server, which owns a single SharedKV instance.
33
+ */
34
+ export declare class SharedKV {
35
+ private readonly store;
36
+ private readonly persistDir;
37
+ private readonly snapshotPath;
38
+ private readonly sweepIntervalMs;
39
+ private sweepTimer;
40
+ private dirty;
41
+ constructor(options?: SharedKVOptions);
42
+ /**
43
+ * Retrieve a value by key.
44
+ * Returns `null` if the key does not exist or has expired.
45
+ */
46
+ get<T>(key: string): T | null;
47
+ /**
48
+ * Store a value, with an optional TTL.
49
+ */
50
+ set<T>(key: string, value: T, options?: KVSetOptions): void;
51
+ /**
52
+ * Delete a key. No-op if the key does not exist.
53
+ */
54
+ delete(key: string): void;
55
+ /**
56
+ * List all non-expired keys, optionally filtered by a string prefix.
57
+ */
58
+ list(prefix?: string): string[];
59
+ /**
60
+ * Remove all entries (optionally matching a prefix).
61
+ */
62
+ clear(prefix?: string): void;
63
+ /** Total number of (potentially expired) entries currently held in memory. */
64
+ get size(): number;
65
+ /**
66
+ * Flush the in-memory state to disk and stop the sweep timer.
67
+ * Call this before process exit or daemon shutdown.
68
+ */
69
+ shutdown(): Promise<void>;
70
+ private loadFromDisk;
71
+ /** Write the current (non-expired) state to disk. */
72
+ flushToDisk(): void;
73
+ private startSweep;
74
+ private evictExpired;
75
+ /**
76
+ * Delete all snapshot files under `persistDir`.
77
+ * Used in tests for clean-up between runs.
78
+ */
79
+ static clearAllSnapshots(persistDir: string): void;
80
+ }
81
+ //# sourceMappingURL=shared-kv.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shared-kv.d.ts","sourceRoot":"","sources":["../../src/daemon/shared-kv.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAOH,gDAAgD;AAChD,MAAM,WAAW,YAAY;IAC3B,0EAA0E;IAC1E,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAeD,4DAA4D;AAC5D,MAAM,WAAW,eAAe;IAC9B,0EAA0E;IAC1E,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,0EAA0E;IAC1E,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,gFAAgF;IAChF,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,0EAA0E;IAC1E,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED;;;;;;;GAOG;AACH,qBAAa,QAAQ;IACnB,OAAO,CAAC,QAAQ,CAAC,KAAK,CAA8B;IACpD,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAS;IACtC,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAS;IACzC,OAAO,CAAC,UAAU,CAA+C;IACjE,OAAO,CAAC,KAAK,CAAS;gBAEV,OAAO,GAAE,eAAoB;IAgBzC;;;OAGG;IACH,GAAG,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,GAAG,CAAC,GAAG,IAAI;IAa7B;;OAEG;IACH,GAAG,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,EAAE,OAAO,CAAC,EAAE,YAAY,GAAG,IAAI;IAS3D;;OAEG;IACH,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;IAMzB;;OAEG;IACH,IAAI,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE;IAY/B;;OAEG;IACH,KAAK,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI;IAW5B,8EAA8E;IAC9E,IAAI,IAAI,IAAI,MAAM,CAEjB;IAMD;;;OAGG;IACG,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IAa/B,OAAO,CAAC,YAAY;IA2BpB,qDAAqD;IACrD,WAAW,IAAI,IAAI;IAqBnB,OAAO,CAAC,UAAU;IAclB,OAAO,CAAC,YAAY;IAUpB;;;OAGG;IACH,MAAM,CAAC,iBAAiB,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI;CAQnD"}
@@ -0,0 +1,215 @@
1
+ /**
2
+ * Shared Key-Value Store for MCP Conductor Daemon.
3
+ *
4
+ * Provides in-memory storage with optional disk persistence and TTL support.
5
+ * Each KV entry can carry an expiry timestamp; a background sweep runs every
6
+ * 30 s to evict stale entries and sync the on-disk snapshot.
7
+ *
8
+ * @module daemon/shared-kv
9
+ */
10
+ import { existsSync, mkdirSync, readFileSync, writeFileSync, readdirSync, unlinkSync } from 'node:fs';
11
+ import { join } from 'node:path';
12
+ import { homedir } from 'node:os';
13
+ import { logger } from '../utils/logger.js';
14
+ /**
15
+ * Shared key-value store with TTL expiry and disk persistence.
16
+ *
17
+ * Thread-safety note: Node.js is single-threaded; all mutations are
18
+ * synchronous operations on the in-memory Map, so no locking is required
19
+ * within a single process. Cross-process sharing is handled by the daemon
20
+ * server, which owns a single SharedKV instance.
21
+ */
22
+ export class SharedKV {
23
+ store = new Map();
24
+ persistDir;
25
+ snapshotPath;
26
+ sweepIntervalMs;
27
+ sweepTimer = null;
28
+ dirty = false;
29
+ constructor(options = {}) {
30
+ this.persistDir = options.persistDir ?? join(homedir(), '.mcp-conductor', 'kv');
31
+ this.sweepIntervalMs = options.sweepIntervalMs ?? 30_000;
32
+ const ns = options.namespace ?? 'default';
33
+ this.snapshotPath = join(this.persistDir, `${ns}.json`);
34
+ if (!options.skipLoad) {
35
+ this.loadFromDisk();
36
+ }
37
+ this.startSweep();
38
+ }
39
+ // ---------------------------------------------------------------------------
40
+ // Public API
41
+ // ---------------------------------------------------------------------------
42
+ /**
43
+ * Retrieve a value by key.
44
+ * Returns `null` if the key does not exist or has expired.
45
+ */
46
+ get(key) {
47
+ const entry = this.store.get(key);
48
+ if (!entry)
49
+ return null;
50
+ if (entry.expiresAt !== undefined && Date.now() > entry.expiresAt) {
51
+ this.store.delete(key);
52
+ this.dirty = true;
53
+ return null;
54
+ }
55
+ return entry.value;
56
+ }
57
+ /**
58
+ * Store a value, with an optional TTL.
59
+ */
60
+ set(key, value, options) {
61
+ const entry = { value };
62
+ if (options?.ttl !== undefined && options.ttl > 0) {
63
+ entry.expiresAt = Date.now() + options.ttl;
64
+ }
65
+ this.store.set(key, entry);
66
+ this.dirty = true;
67
+ }
68
+ /**
69
+ * Delete a key. No-op if the key does not exist.
70
+ */
71
+ delete(key) {
72
+ if (this.store.delete(key)) {
73
+ this.dirty = true;
74
+ }
75
+ }
76
+ /**
77
+ * List all non-expired keys, optionally filtered by a string prefix.
78
+ */
79
+ list(prefix) {
80
+ const now = Date.now();
81
+ const keys = [];
82
+ for (const [k, entry] of this.store) {
83
+ if (entry.expiresAt !== undefined && now > entry.expiresAt)
84
+ continue;
85
+ if (prefix === undefined || k.startsWith(prefix)) {
86
+ keys.push(k);
87
+ }
88
+ }
89
+ return keys.sort();
90
+ }
91
+ /**
92
+ * Remove all entries (optionally matching a prefix).
93
+ */
94
+ clear(prefix) {
95
+ if (prefix === undefined) {
96
+ this.store.clear();
97
+ }
98
+ else {
99
+ for (const k of this.store.keys()) {
100
+ if (k.startsWith(prefix))
101
+ this.store.delete(k);
102
+ }
103
+ }
104
+ this.dirty = true;
105
+ }
106
+ /** Total number of (potentially expired) entries currently held in memory. */
107
+ get size() {
108
+ return this.store.size;
109
+ }
110
+ // ---------------------------------------------------------------------------
111
+ // Lifecycle
112
+ // ---------------------------------------------------------------------------
113
+ /**
114
+ * Flush the in-memory state to disk and stop the sweep timer.
115
+ * Call this before process exit or daemon shutdown.
116
+ */
117
+ async shutdown() {
118
+ if (this.sweepTimer !== null) {
119
+ clearInterval(this.sweepTimer);
120
+ this.sweepTimer = null;
121
+ }
122
+ this.evictExpired();
123
+ this.flushToDisk();
124
+ }
125
+ // ---------------------------------------------------------------------------
126
+ // Persistence
127
+ // ---------------------------------------------------------------------------
128
+ loadFromDisk() {
129
+ if (!existsSync(this.snapshotPath))
130
+ return;
131
+ try {
132
+ const raw = readFileSync(this.snapshotPath, 'utf-8');
133
+ const snapshot = JSON.parse(raw);
134
+ if (snapshot.version !== 1) {
135
+ logger.warn('SharedKV: unknown snapshot version, skipping load', { path: this.snapshotPath });
136
+ return;
137
+ }
138
+ const now = Date.now();
139
+ let loaded = 0;
140
+ let skipped = 0;
141
+ for (const [k, entry] of Object.entries(snapshot.entries)) {
142
+ if (entry.expiresAt !== undefined && now > entry.expiresAt) {
143
+ skipped++;
144
+ continue; // already expired — do not restore
145
+ }
146
+ this.store.set(k, entry);
147
+ loaded++;
148
+ }
149
+ logger.debug('SharedKV: loaded from disk', { loaded, skipped, path: this.snapshotPath });
150
+ }
151
+ catch (err) {
152
+ logger.warn('SharedKV: failed to load snapshot', { error: String(err), path: this.snapshotPath });
153
+ }
154
+ }
155
+ /** Write the current (non-expired) state to disk. */
156
+ flushToDisk() {
157
+ try {
158
+ mkdirSync(this.persistDir, { recursive: true });
159
+ const entries = {};
160
+ const now = Date.now();
161
+ for (const [k, entry] of this.store) {
162
+ if (entry.expiresAt !== undefined && now > entry.expiresAt)
163
+ continue;
164
+ entries[k] = entry;
165
+ }
166
+ const snapshot = { version: 1, entries };
167
+ writeFileSync(this.snapshotPath, JSON.stringify(snapshot, null, 2), 'utf-8');
168
+ this.dirty = false;
169
+ }
170
+ catch (err) {
171
+ logger.error('SharedKV: failed to flush to disk', { error: String(err), path: this.snapshotPath });
172
+ }
173
+ }
174
+ // ---------------------------------------------------------------------------
175
+ // TTL sweep
176
+ // ---------------------------------------------------------------------------
177
+ startSweep() {
178
+ this.sweepTimer = setInterval(() => {
179
+ this.evictExpired();
180
+ if (this.dirty) {
181
+ this.flushToDisk();
182
+ }
183
+ }, this.sweepIntervalMs);
184
+ // Allow the process to exit even if the timer is still running.
185
+ if (this.sweepTimer.unref) {
186
+ this.sweepTimer.unref();
187
+ }
188
+ }
189
+ evictExpired() {
190
+ const now = Date.now();
191
+ for (const [k, entry] of this.store) {
192
+ if (entry.expiresAt !== undefined && now > entry.expiresAt) {
193
+ this.store.delete(k);
194
+ this.dirty = true;
195
+ }
196
+ }
197
+ }
198
+ /**
199
+ * Delete all snapshot files under `persistDir`.
200
+ * Used in tests for clean-up between runs.
201
+ */
202
+ static clearAllSnapshots(persistDir) {
203
+ if (!existsSync(persistDir))
204
+ return;
205
+ for (const f of readdirSync(persistDir)) {
206
+ if (f.endsWith('.json')) {
207
+ try {
208
+ unlinkSync(join(persistDir, f));
209
+ }
210
+ catch { /* ignore */ }
211
+ }
212
+ }
213
+ }
214
+ }
215
+ //# sourceMappingURL=shared-kv.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shared-kv.js","sourceRoot":"","sources":["../../src/daemon/shared-kv.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Shared Lock Primitive for MCP Conductor Daemon.
3
+ *
4
+ * Implements an in-process mutex per key using a promise chain. Callers
5
+ * `acquire` a lock, receive a `release` function, and must call `release`
6
+ * when their critical section completes.
7
+ *
8
+ * Cross-daemon distributed locking is deferred to v3.1; this module handles
9
+ * the single-daemon (single-process) case which covers the v3.0 requirement
10
+ * of serialising concurrent writers within one daemon instance.
11
+ *
12
+ * @module daemon/shared-lock
13
+ */
14
+ /** Handle returned by {@link SharedLock.acquire}. */
15
+ export interface LockHandle {
16
+ /** Release the lock so the next waiter can proceed. */
17
+ release: () => Promise<void>;
18
+ /** Key this lock was acquired for. */
19
+ key: string;
20
+ }
21
+ /** Options for {@link SharedLock.acquire}. */
22
+ export interface LockAcquireOptions {
23
+ /**
24
+ * Maximum time to wait for the lock in milliseconds.
25
+ * If the lock is not acquired within this window, the call throws.
26
+ * Defaults to 30 000 ms.
27
+ */
28
+ timeoutMs?: number;
29
+ }
30
+ /**
31
+ * Error thrown when a lock acquisition times out.
32
+ */
33
+ export declare class LockTimeoutError extends Error {
34
+ constructor(key: string, timeoutMs: number);
35
+ }
36
+ /**
37
+ * In-process mutex registry keyed by arbitrary string keys.
38
+ *
39
+ * Each key gets its own independent promise chain. The chain starts as a
40
+ * resolved promise; each caller appends to it by storing a new promise that
41
+ * resolves only after the previous holder calls `release`.
42
+ */
43
+ export declare class SharedLock {
44
+ private readonly locks;
45
+ /**
46
+ * Acquire the lock for `key`.
47
+ *
48
+ * Returns a {@link LockHandle} with a `release` function. The caller MUST
49
+ * call `release()` — ideally in a `finally` block — to unblock the next
50
+ * waiter. Forgetting to release will deadlock all subsequent acquirers.
51
+ *
52
+ * @throws {LockTimeoutError} if the lock is not acquired within `timeoutMs`.
53
+ */
54
+ acquire(key: string, options?: LockAcquireOptions): Promise<LockHandle>;
55
+ /**
56
+ * Execute `fn` while holding the lock for `key`.
57
+ * The lock is always released after `fn` completes (even on throw).
58
+ */
59
+ withLock<T>(key: string, fn: () => Promise<T>, options?: LockAcquireOptions): Promise<T>;
60
+ /**
61
+ * Number of active waiters (including the current holder) for a given key.
62
+ * Returns 0 if the key has no lock contention.
63
+ */
64
+ waiters(key: string): number;
65
+ /**
66
+ * True if any key currently has at least one waiter.
67
+ */
68
+ get hasActiveLocks(): boolean;
69
+ private waitWithTimeout;
70
+ }
71
+ //# sourceMappingURL=shared-lock.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shared-lock.d.ts","sourceRoot":"","sources":["../../src/daemon/shared-lock.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAIH,qDAAqD;AACrD,MAAM,WAAW,UAAU;IACzB,uDAAuD;IACvD,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7B,sCAAsC;IACtC,GAAG,EAAE,MAAM,CAAC;CACb;AAED,8CAA8C;AAC9C,MAAM,WAAW,kBAAkB;IACjC;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;GAEG;AACH,qBAAa,gBAAiB,SAAQ,KAAK;gBAC7B,GAAG,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM;CAI3C;AAUD;;;;;;GAMG;AACH,qBAAa,UAAU;IACrB,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAiC;IAEvD;;;;;;;;OAQG;IACG,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,kBAAkB,GAAG,OAAO,CAAC,UAAU,CAAC;IA4C7E;;;OAGG;IACG,QAAQ,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,CAAC,EAAE,kBAAkB,GAAG,OAAO,CAAC,CAAC,CAAC;IAS9F;;;OAGG;IACH,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM;IAI5B;;OAEG;IACH,IAAI,cAAc,IAAI,OAAO,CAE5B;YAMa,eAAe;CAoB9B"}
@@ -0,0 +1,119 @@
1
+ /**
2
+ * Shared Lock Primitive for MCP Conductor Daemon.
3
+ *
4
+ * Implements an in-process mutex per key using a promise chain. Callers
5
+ * `acquire` a lock, receive a `release` function, and must call `release`
6
+ * when their critical section completes.
7
+ *
8
+ * Cross-daemon distributed locking is deferred to v3.1; this module handles
9
+ * the single-daemon (single-process) case which covers the v3.0 requirement
10
+ * of serialising concurrent writers within one daemon instance.
11
+ *
12
+ * @module daemon/shared-lock
13
+ */
14
+ import { logger } from '../utils/logger.js';
15
+ /**
16
+ * Error thrown when a lock acquisition times out.
17
+ */
18
+ export class LockTimeoutError extends Error {
19
+ constructor(key, timeoutMs) {
20
+ super(`Lock acquisition timed out for key "${key}" after ${timeoutMs}ms`);
21
+ this.name = 'LockTimeoutError';
22
+ }
23
+ }
24
+ /**
25
+ * In-process mutex registry keyed by arbitrary string keys.
26
+ *
27
+ * Each key gets its own independent promise chain. The chain starts as a
28
+ * resolved promise; each caller appends to it by storing a new promise that
29
+ * resolves only after the previous holder calls `release`.
30
+ */
31
+ export class SharedLock {
32
+ locks = new Map();
33
+ /**
34
+ * Acquire the lock for `key`.
35
+ *
36
+ * Returns a {@link LockHandle} with a `release` function. The caller MUST
37
+ * call `release()` — ideally in a `finally` block — to unblock the next
38
+ * waiter. Forgetting to release will deadlock all subsequent acquirers.
39
+ *
40
+ * @throws {LockTimeoutError} if the lock is not acquired within `timeoutMs`.
41
+ */
42
+ async acquire(key, options) {
43
+ const timeoutMs = options?.timeoutMs ?? 30_000;
44
+ // Get or create the per-key chain.
45
+ const current = this.locks.get(key) ?? { chain: Promise.resolve(), waiters: 0 };
46
+ current.waiters++;
47
+ this.locks.set(key, current);
48
+ // `releaseNext` is the function that the new holder will call to advance
49
+ // the chain for the subsequent waiter.
50
+ let releaseNext;
51
+ const next = new Promise((resolve) => { releaseNext = resolve; });
52
+ // Wait for the previous holder to release, with a timeout.
53
+ const previousChain = current.chain;
54
+ // Advance the chain pointer so the next caller waits on our `next`.
55
+ current.chain = next;
56
+ await this.waitWithTimeout(previousChain, timeoutMs, key);
57
+ logger.debug('SharedLock: acquired', { key, waiters: current.waiters });
58
+ const released = { done: false };
59
+ return {
60
+ key,
61
+ release: async () => {
62
+ if (released.done) {
63
+ logger.warn('SharedLock: release called more than once', { key });
64
+ return;
65
+ }
66
+ released.done = true;
67
+ current.waiters--;
68
+ logger.debug('SharedLock: released', { key, remainingWaiters: current.waiters });
69
+ // Clean up the key entry if no one else is waiting.
70
+ if (current.waiters === 0) {
71
+ this.locks.delete(key);
72
+ }
73
+ releaseNext();
74
+ },
75
+ };
76
+ }
77
+ /**
78
+ * Execute `fn` while holding the lock for `key`.
79
+ * The lock is always released after `fn` completes (even on throw).
80
+ */
81
+ async withLock(key, fn, options) {
82
+ const handle = await this.acquire(key, options);
83
+ try {
84
+ return await fn();
85
+ }
86
+ finally {
87
+ await handle.release();
88
+ }
89
+ }
90
+ /**
91
+ * Number of active waiters (including the current holder) for a given key.
92
+ * Returns 0 if the key has no lock contention.
93
+ */
94
+ waiters(key) {
95
+ return this.locks.get(key)?.waiters ?? 0;
96
+ }
97
+ /**
98
+ * True if any key currently has at least one waiter.
99
+ */
100
+ get hasActiveLocks() {
101
+ return this.locks.size > 0;
102
+ }
103
+ // ---------------------------------------------------------------------------
104
+ // Private helpers
105
+ // ---------------------------------------------------------------------------
106
+ async waitWithTimeout(promise, timeoutMs, key) {
107
+ let timer;
108
+ const timeout = new Promise((_, reject) => {
109
+ timer = setTimeout(() => reject(new LockTimeoutError(key, timeoutMs)), timeoutMs);
110
+ });
111
+ try {
112
+ await Promise.race([promise, timeout]);
113
+ }
114
+ finally {
115
+ clearTimeout(timer);
116
+ }
117
+ }
118
+ }
119
+ //# sourceMappingURL=shared-lock.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shared-lock.js","sourceRoot":"","sources":["../../src/daemon/shared-lock.ts"],"names":[],"mappings":"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"}
@@ -13,6 +13,7 @@ import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js'
13
13
  import type { Tool } from '@modelcontextprotocol/sdk/types.js';
14
14
  import { EventEmitter } from 'node:events';
15
15
  import { RateLimiter } from '../utils/index.js';
16
+ import { type RedactMatcher } from '../utils/tokenize.js';
16
17
  import type { ServersConfig, RateLimitConfig } from '../config/schema.js';
17
18
  export type ServerStatus = 'connecting' | 'connected' | 'disconnected' | 'error';
18
19
  export interface ServerConnection {
@@ -138,6 +139,28 @@ export declare class MCPHub extends EventEmitter {
138
139
  * Call a tool on a specific server
139
140
  */
140
141
  callTool(serverName: string, toolName: string, params: Record<string, unknown>): Promise<unknown>;
142
+ /**
143
+ * Call a tool on a specific server, applying PII tokenization to the result.
144
+ *
145
+ * Used by the bridge `/call` handler when the tool's `ToolDefinition.redact.response`
146
+ * annotation is present. Returns both the redacted result and the per-call
147
+ * reverse map so the sandbox can resolve tokens via `mcp.detokenize()`.
148
+ *
149
+ * The reverse map is scoped to a single `execute_code` invocation — it is
150
+ * embedded in the sandbox preamble by `generateSandboxCode` and never
151
+ * persisted to disk or shared across calls.
152
+ *
153
+ * @param serverName Backend server name
154
+ * @param toolName Tool to invoke
155
+ * @param params Tool arguments
156
+ * @param matchers Built-in matcher names from `ToolDefinition.redact.response`
157
+ * @returns `{ result, reverseMap }` — result has PII replaced with tokens;
158
+ * reverseMap maps `[TOKEN_N]` → original value.
159
+ */
160
+ callToolTokenized(serverName: string, toolName: string, params: Record<string, unknown>, matchers: ReadonlyArray<RedactMatcher>): Promise<{
161
+ result: unknown;
162
+ reverseMap: Record<string, string>;
163
+ }>;
141
164
  /**
142
165
  * Check if a server is connected
143
166
  */
@@ -1 +1 @@
1
- {"version":3,"file":"mcp-hub.d.ts","sourceRoot":"","sources":["../../src/hub/mcp-hub.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"mcp-hub.d.ts","sourceRoot":"","sources":["../../src/hub/mcp-hub.ts"],"names":[],"mappings":"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"}
@@ -12,7 +12,9 @@ import { Client } from '@modelcontextprotocol/sdk/client/index.js';
12
12
  import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
13
13
  import { EventEmitter } from 'node:events';
14
14
  import { logger, RateLimiter, minimalChildEnv } from '../utils/index.js';
15
+ import { tokenize } from '../utils/tokenize.js';
15
16
  import { loadClaudeConfig, findClaudeConfig, loadConductorConfig, findConductorConfig, } from '../config/loader.js';
17
+ import { MCPToolError, extractErrorCode } from '../reliability/errors.js';
16
18
  const DEFAULT_HUB_CONFIG = {
17
19
  claudeConfigPath: '',
18
20
  conductorConfigPath: '',
@@ -508,11 +510,42 @@ export class MCPHub extends EventEmitter {
508
510
  return result;
509
511
  }
510
512
  catch (error) {
513
+ // Never double-wrap an MCPToolError — propagate as-is.
514
+ if (error instanceof MCPToolError)
515
+ throw error;
511
516
  const errorMessage = error instanceof Error ? error.message : String(error);
512
517
  logger.error(`Tool call failed: ${serverName}.${toolName}`, { error: errorMessage, params });
513
- throw new Error(`Tool call failed: ${serverName}.${toolName} - ${errorMessage}`);
518
+ const code = extractErrorCode(error);
519
+ throw new MCPToolError(code, serverName, toolName, error);
514
520
  }
515
521
  }
522
+ /**
523
+ * Call a tool on a specific server, applying PII tokenization to the result.
524
+ *
525
+ * Used by the bridge `/call` handler when the tool's `ToolDefinition.redact.response`
526
+ * annotation is present. Returns both the redacted result and the per-call
527
+ * reverse map so the sandbox can resolve tokens via `mcp.detokenize()`.
528
+ *
529
+ * The reverse map is scoped to a single `execute_code` invocation — it is
530
+ * embedded in the sandbox preamble by `generateSandboxCode` and never
531
+ * persisted to disk or shared across calls.
532
+ *
533
+ * @param serverName Backend server name
534
+ * @param toolName Tool to invoke
535
+ * @param params Tool arguments
536
+ * @param matchers Built-in matcher names from `ToolDefinition.redact.response`
537
+ * @returns `{ result, reverseMap }` — result has PII replaced with tokens;
538
+ * reverseMap maps `[TOKEN_N]` → original value.
539
+ */
540
+ async callToolTokenized(serverName, toolName, params, matchers) {
541
+ const raw = await this.callTool(serverName, toolName, params);
542
+ if (matchers.length === 0) {
543
+ return { result: raw, reverseMap: {} };
544
+ }
545
+ const { redacted, reverseMap } = tokenize(raw, matchers);
546
+ logger.debug(`callToolTokenized: ${serverName}.${toolName} — ${Object.keys(reverseMap).length} token(s) minted`);
547
+ return { result: redacted, reverseMap };
548
+ }
516
549
  /**
517
550
  * Check if a server is connected
518
551
  */