negotium 0.1.12 → 0.1.13

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 (137) hide show
  1. package/dist/canonical-mcp-bridge.js +2 -3
  2. package/dist/canonical-mcp-bridge.js.map +1 -1
  3. package/dist/chunk-a9y2rwxf.js.map +1 -1
  4. package/dist/{chunk-j0kjscb7.js → chunk-s2gez3wg.js} +20 -2
  5. package/dist/{chunk-j0kjscb7.js.map → chunk-s2gez3wg.js.map} +2 -2
  6. package/dist/cron.js +49 -25
  7. package/dist/cron.js.map +15 -14
  8. package/dist/hosted-agent.js +3012 -17
  9. package/dist/hosted-agent.js.map +30 -3
  10. package/dist/main.js +31323 -36
  11. package/dist/main.js.map +198 -3
  12. package/dist/prompts.js +11 -8
  13. package/dist/prompts.js.map +4 -4
  14. package/dist/runtime/src/application/submit-user-message.ts +3 -0
  15. package/dist/runtime/src/index.ts +7 -0
  16. package/dist/runtime/src/mcp/runtime-spec.ts +3 -0
  17. package/dist/runtime/src/mcp/session-comm/server.ts +1 -1
  18. package/dist/runtime/src/platform/mcp-config.ts +13 -0
  19. package/dist/runtime/src/prompts/builders.ts +20 -6
  20. package/dist/runtime/src/query/active-rooms.ts +2 -0
  21. package/dist/runtime/src/runtime/delivery-ack.ts +112 -0
  22. package/dist/runtime/src/runtime/errors.ts +12 -12
  23. package/dist/runtime/src/runtime/turn-runner.ts +12 -0
  24. package/dist/runtime/src/storage/runtime-turn-requests.ts +1 -0
  25. package/dist/runtime/src/types/api.ts +2 -0
  26. package/dist/runtime/src/types.ts +2 -0
  27. package/dist/runtime/src/version.ts +1 -1
  28. package/dist/runtime-helpers.js.map +1 -1
  29. package/dist/storage.js.map +1 -1
  30. package/dist/types/packages/core/src/mcp/runtime-spec.d.ts +2 -0
  31. package/dist/types/packages/core/src/platform/mcp-config.d.ts +4 -0
  32. package/dist/types/packages/core/src/prompts/builders.d.ts +2 -0
  33. package/dist/types/packages/core/src/types/api.d.ts +2 -0
  34. package/dist/types/packages/core/src/types.d.ts +2 -0
  35. package/dist/types/packages/core/src/version.d.ts +1 -1
  36. package/dist/vault.js.map +1 -1
  37. package/package.json +1 -1
  38. package/dist/chunk-00q25k7p.js +0 -2387
  39. package/dist/chunk-00q25k7p.js.map +0 -26
  40. package/dist/chunk-03kv7mvj.js +0 -2152
  41. package/dist/chunk-03kv7mvj.js.map +0 -15
  42. package/dist/chunk-05pm51dh.js +0 -589
  43. package/dist/chunk-05pm51dh.js.map +0 -11
  44. package/dist/chunk-0eh16bk1.js +0 -2936
  45. package/dist/chunk-0eh16bk1.js.map +0 -20
  46. package/dist/chunk-0yfqyv88.js +0 -108
  47. package/dist/chunk-0yfqyv88.js.map +0 -10
  48. package/dist/chunk-1k7t7bnx.js +0 -97
  49. package/dist/chunk-1k7t7bnx.js.map +0 -10
  50. package/dist/chunk-3gkfr9vb.js +0 -431
  51. package/dist/chunk-3gkfr9vb.js.map +0 -11
  52. package/dist/chunk-5akd1gbj.js +0 -281
  53. package/dist/chunk-5akd1gbj.js.map +0 -10
  54. package/dist/chunk-7k19pcg8.js +0 -577
  55. package/dist/chunk-7k19pcg8.js.map +0 -14
  56. package/dist/chunk-81tbfbmt.js +0 -10
  57. package/dist/chunk-81tbfbmt.js.map +0 -10
  58. package/dist/chunk-82qhv1mq.js +0 -512
  59. package/dist/chunk-82qhv1mq.js.map +0 -13
  60. package/dist/chunk-8dy7e2a9.js +0 -103
  61. package/dist/chunk-8dy7e2a9.js.map +0 -10
  62. package/dist/chunk-8f2sqmrj.js +0 -1712
  63. package/dist/chunk-8f2sqmrj.js.map +0 -18
  64. package/dist/chunk-9hjeemfz.js +0 -372
  65. package/dist/chunk-9hjeemfz.js.map +0 -10
  66. package/dist/chunk-a4aa2ccv.js +0 -141
  67. package/dist/chunk-a4aa2ccv.js.map +0 -10
  68. package/dist/chunk-bsg215q3.js +0 -3536
  69. package/dist/chunk-bsg215q3.js.map +0 -20
  70. package/dist/chunk-bxnfsc7w.js +0 -58
  71. package/dist/chunk-bxnfsc7w.js.map +0 -10
  72. package/dist/chunk-c500xe9z.js +0 -33
  73. package/dist/chunk-c500xe9z.js.map +0 -10
  74. package/dist/chunk-cngrf7ab.js +0 -149
  75. package/dist/chunk-cngrf7ab.js.map +0 -10
  76. package/dist/chunk-d7tprf06.js +0 -53
  77. package/dist/chunk-d7tprf06.js.map +0 -10
  78. package/dist/chunk-ddnrmkrv.js +0 -61
  79. package/dist/chunk-ddnrmkrv.js.map +0 -10
  80. package/dist/chunk-edb07sjp.js +0 -567
  81. package/dist/chunk-edb07sjp.js.map +0 -14
  82. package/dist/chunk-ejcm6zx8.js +0 -591
  83. package/dist/chunk-ejcm6zx8.js.map +0 -11
  84. package/dist/chunk-ezs352qs.js +0 -237
  85. package/dist/chunk-ezs352qs.js.map +0 -11
  86. package/dist/chunk-f5xgprg1.js +0 -59
  87. package/dist/chunk-f5xgprg1.js.map +0 -10
  88. package/dist/chunk-fbtb3v0s.js +0 -1339
  89. package/dist/chunk-fbtb3v0s.js.map +0 -16
  90. package/dist/chunk-ffefbd3r.js +0 -17
  91. package/dist/chunk-ffefbd3r.js.map +0 -10
  92. package/dist/chunk-gaknyw04.js +0 -708
  93. package/dist/chunk-gaknyw04.js.map +0 -14
  94. package/dist/chunk-gxmny6tq.js +0 -1637
  95. package/dist/chunk-gxmny6tq.js.map +0 -15
  96. package/dist/chunk-h7bj5zwb.js +0 -3069
  97. package/dist/chunk-h7bj5zwb.js.map +0 -32
  98. package/dist/chunk-hp4g7wx7.js +0 -46
  99. package/dist/chunk-hp4g7wx7.js.map +0 -10
  100. package/dist/chunk-hwy3n31w.js +0 -21
  101. package/dist/chunk-hwy3n31w.js.map +0 -9
  102. package/dist/chunk-jzz91jwh.js +0 -70
  103. package/dist/chunk-jzz91jwh.js.map +0 -10
  104. package/dist/chunk-k778y16p.js +0 -153
  105. package/dist/chunk-k778y16p.js.map +0 -11
  106. package/dist/chunk-mh5mexsf.js +0 -147
  107. package/dist/chunk-mh5mexsf.js.map +0 -11
  108. package/dist/chunk-mnwaext5.js +0 -318
  109. package/dist/chunk-mnwaext5.js.map +0 -10
  110. package/dist/chunk-nkgs40ch.js +0 -23
  111. package/dist/chunk-nkgs40ch.js.map +0 -10
  112. package/dist/chunk-pz57vwsm.js +0 -250
  113. package/dist/chunk-pz57vwsm.js.map +0 -11
  114. package/dist/chunk-r6kdp7gn.js +0 -252
  115. package/dist/chunk-r6kdp7gn.js.map +0 -10
  116. package/dist/chunk-rkvrkcgx.js +0 -117
  117. package/dist/chunk-rkvrkcgx.js.map +0 -10
  118. package/dist/chunk-s74rdyqe.js +0 -117
  119. package/dist/chunk-s74rdyqe.js.map +0 -12
  120. package/dist/chunk-sa874j5c.js +0 -9
  121. package/dist/chunk-sa874j5c.js.map +0 -10
  122. package/dist/chunk-sffqdkvq.js +0 -122
  123. package/dist/chunk-sffqdkvq.js.map +0 -10
  124. package/dist/chunk-tcs9qjps.js +0 -64
  125. package/dist/chunk-tcs9qjps.js.map +0 -10
  126. package/dist/chunk-wx83ep8e.js +0 -131
  127. package/dist/chunk-wx83ep8e.js.map +0 -10
  128. package/dist/chunk-xag86ng3.js +0 -122
  129. package/dist/chunk-xag86ng3.js.map +0 -10
  130. package/dist/chunk-xfdkep71.js +0 -371
  131. package/dist/chunk-xfdkep71.js.map +0 -11
  132. package/dist/chunk-xhm1rfpt.js +0 -2907
  133. package/dist/chunk-xhm1rfpt.js.map +0 -24
  134. package/dist/chunk-xpcps28p.js +0 -2138
  135. package/dist/chunk-xpcps28p.js.map +0 -29
  136. package/dist/chunk-yb4rbev9.js +0 -214
  137. package/dist/chunk-yb4rbev9.js.map +0 -10
@@ -1,567 +0,0 @@
1
- // ../../packages/mcp-host/src/spec.ts
2
- import { z } from "zod";
3
- var KEY_PATTERN = /^[a-zA-Z0-9._-]+$/;
4
- var portSchema = z.number().int().min(1).max(65535);
5
- var mcpServerSpecSchema = z.object({
6
- key: z.string().min(1).regex(KEY_PATTERN, "key may only contain [a-zA-Z0-9._-]").refine((k) => k !== "." && k !== "..", "key may not be a dot path"),
7
- transport: z.enum(["stdio", "http"]),
8
- command: z.string().min(1),
9
- args: z.array(z.string()).optional(),
10
- env: z.record(z.string(), z.string()).optional(),
11
- cwd: z.string().optional(),
12
- portRange: z.object({ base: portSchema, max: portSchema }).optional(),
13
- scope: z.enum(["node", "instance"]),
14
- readyTimeoutMs: z.number().int().positive().optional(),
15
- idleEvictMs: z.number().int().positive().optional(),
16
- healthIntervalMs: z.number().int().positive().optional()
17
- }).superRefine((spec, ctx) => {
18
- if (spec.transport === "http") {
19
- if (!spec.portRange) {
20
- ctx.addIssue({
21
- code: "custom",
22
- path: ["portRange"],
23
- message: 'portRange is required when transport is "http"'
24
- });
25
- } else if (spec.portRange.max < spec.portRange.base) {
26
- ctx.addIssue({
27
- code: "custom",
28
- path: ["portRange"],
29
- message: "portRange.max must be >= portRange.base"
30
- });
31
- }
32
- }
33
- });
34
-
35
- // ../../packages/mcp-host/src/manifest.ts
36
- import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "fs";
37
- import { dirname } from "path";
38
- import { z as z2 } from "zod";
39
-
40
- // ../../packages/mcp-host/src/paths.ts
41
- import { homedir } from "os";
42
- import { resolve } from "path";
43
- function stateDir() {
44
- const env = process.env.NEGOTIUM_STATE_DIR?.trim();
45
- return env ? resolve(env) : resolve(homedir(), ".negotium");
46
- }
47
- function defaultPortsDir() {
48
- return resolve(stateDir(), "run", "mcp-ports");
49
- }
50
- function defaultManifestFile() {
51
- return resolve(stateDir(), "data", "mcp-manifest.json");
52
- }
53
-
54
- // ../../packages/mcp-host/src/manifest.ts
55
- var manifestEntrySchema = z2.object({
56
- spec: mcpServerSpecSchema,
57
- enabled: z2.boolean()
58
- });
59
- var manifestFileSchema = z2.object({
60
- version: z2.literal(1),
61
- servers: z2.array(manifestEntrySchema)
62
- });
63
-
64
- class McpManifest {
65
- file;
66
- entries = new Map;
67
- constructor(opts) {
68
- this.file = opts?.file ?? defaultManifestFile();
69
- this.load();
70
- }
71
- list() {
72
- return [...this.entries.values()].map((e) => structuredClone(e.spec));
73
- }
74
- get(key) {
75
- const entry = this.entries.get(key);
76
- return entry ? structuredClone(entry.spec) : undefined;
77
- }
78
- add(spec) {
79
- const parsed = mcpServerSpecSchema.parse(spec);
80
- if (this.entries.has(parsed.key)) {
81
- throw new Error(`MCP spec already exists in manifest: "${parsed.key}"`);
82
- }
83
- this.entries.set(parsed.key, { spec: parsed, enabled: true });
84
- this.save();
85
- }
86
- remove(key) {
87
- const removed = this.entries.delete(key);
88
- if (removed)
89
- this.save();
90
- return removed;
91
- }
92
- setEnabled(key, enabled) {
93
- const entry = this.entries.get(key);
94
- if (!entry)
95
- throw new Error(`Unknown MCP spec: "${key}"`);
96
- if (entry.enabled === enabled)
97
- return;
98
- entry.enabled = enabled;
99
- this.save();
100
- }
101
- isEnabled(key) {
102
- return this.entries.get(key)?.enabled ?? false;
103
- }
104
- load() {
105
- if (!existsSync(this.file))
106
- return;
107
- let parsed;
108
- try {
109
- parsed = manifestFileSchema.parse(JSON.parse(readFileSync(this.file, "utf8")));
110
- } catch (e) {
111
- throw new Error(`Invalid MCP manifest at ${this.file}: ${e instanceof Error ? e.message : String(e)}`);
112
- }
113
- for (const entry of parsed.servers) {
114
- if (this.entries.has(entry.spec.key)) {
115
- throw new Error(`Duplicate MCP spec key in manifest ${this.file}: "${entry.spec.key}"`);
116
- }
117
- this.entries.set(entry.spec.key, entry);
118
- }
119
- }
120
- save() {
121
- mkdirSync(dirname(this.file), { recursive: true });
122
- const payload = {
123
- version: 1,
124
- servers: [...this.entries.values()]
125
- };
126
- const tmp = `${this.file}.tmp-${process.pid}`;
127
- writeFileSync(tmp, `${JSON.stringify(payload, null, 2)}
128
- `);
129
- renameSync(tmp, this.file);
130
- }
131
- }
132
-
133
- // ../../packages/mcp-host/src/manager.ts
134
- import { execFileSync, spawn } from "child_process";
135
- import {
136
- mkdirSync as mkdirSync2,
137
- readdirSync,
138
- readFileSync as readFileSync2,
139
- renameSync as renameSync2,
140
- unlinkSync,
141
- writeFileSync as writeFileSync2
142
- } from "fs";
143
- import { connect, createServer } from "net";
144
- import { join } from "path";
145
-
146
- // ../../packages/mcp-host/src/sanitize.ts
147
- function sanitizePathComponent(name) {
148
- const safe = name.replace(/[^a-zA-Z0-9._-]/g, "_") || "_";
149
- if (safe === "." || safe === "..")
150
- return "_";
151
- return safe;
152
- }
153
-
154
- // ../../packages/mcp-host/src/manager.ts
155
- var DEFAULT_READY_TIMEOUT_MS = 20000;
156
- var READY_POLL_MS = 150;
157
- var KILL_GRACE_MS = 5000;
158
- var DEFAULT_SWEEP_INTERVAL_MS = 30000;
159
- var PROBE_TIMEOUT_MS = 1000;
160
- var defaultLog = (level, msg, ctx) => {
161
- const fn = level === "info" ? console.log : level === "warn" ? console.warn : console.error;
162
- if (ctx)
163
- fn(`[mcp-host] ${msg}`, ctx);
164
- else
165
- fn(`[mcp-host] ${msg}`);
166
- };
167
- var delay = (ms) => new Promise((resolve2) => setTimeout(resolve2, ms));
168
- function regKey(key, instanceKey) {
169
- return `${key}\x00${instanceKey}`;
170
- }
171
- function portFileName(key, instanceKey) {
172
- return `${sanitizePathComponent(key)}--${sanitizePathComponent(instanceKey)}`;
173
- }
174
- function resolveInstanceKey(spec, instanceKey) {
175
- if (spec.scope === "node")
176
- return "node";
177
- if (!instanceKey) {
178
- throw new Error(`MCP spec "${spec.key}" is instance-scoped; an instanceKey is required`);
179
- }
180
- return instanceKey;
181
- }
182
- function canBind(port) {
183
- return new Promise((resolve2) => {
184
- const srv = createServer();
185
- srv.once("error", () => resolve2(false));
186
- srv.listen({ port, host: "127.0.0.1", exclusive: true }, () => {
187
- srv.close(() => resolve2(true));
188
- });
189
- });
190
- }
191
- function tcpProbe(port, timeoutMs = PROBE_TIMEOUT_MS) {
192
- return new Promise((resolve2) => {
193
- const sock = connect({ port, host: "127.0.0.1" });
194
- let settled = false;
195
- const done = (ok) => {
196
- if (settled)
197
- return;
198
- settled = true;
199
- sock.destroy();
200
- resolve2(ok);
201
- };
202
- sock.once("connect", () => done(true));
203
- sock.once("error", () => done(false));
204
- sock.setTimeout(timeoutMs, () => done(false));
205
- });
206
- }
207
- function killProcessTree(pid) {
208
- let children = [];
209
- try {
210
- children = execFileSync("pgrep", ["-P", String(pid)], { stdio: "pipe" }).toString().trim().split(`
211
- `).map((s) => Number.parseInt(s, 10)).filter((n) => !Number.isNaN(n));
212
- } catch {
213
- return;
214
- }
215
- for (const child of children)
216
- killProcessTree(child);
217
- for (const signal of ["SIGTERM", "SIGKILL"]) {
218
- for (const child of children) {
219
- try {
220
- process.kill(child, signal);
221
- } catch {}
222
- }
223
- }
224
- }
225
- function waitExit(proc, timeoutMs) {
226
- if (proc.exitCode !== null || proc.signalCode !== null)
227
- return Promise.resolve(true);
228
- return new Promise((resolve2) => {
229
- const timer = setTimeout(() => resolve2(false), timeoutMs);
230
- timer.unref?.();
231
- const settle = () => {
232
- clearTimeout(timer);
233
- resolve2(true);
234
- };
235
- proc.once("exit", settle);
236
- proc.once("error", settle);
237
- });
238
- }
239
-
240
- class McpHost {
241
- manifest;
242
- portsDir;
243
- log;
244
- sweepIntervalMs;
245
- registry = new Map;
246
- usedPorts = new Set;
247
- spawning = new Map;
248
- sweepInFlight = false;
249
- closed = false;
250
- constructor(opts) {
251
- this.manifest = opts?.manifest ?? new McpManifest;
252
- this.portsDir = opts?.portsDir ?? defaultPortsDir();
253
- this.log = opts?.log ?? defaultLog;
254
- this.sweepIntervalMs = opts?.sweepIntervalMs ?? DEFAULT_SWEEP_INTERVAL_MS;
255
- }
256
- async ensure(key, instanceKey) {
257
- if (this.closed)
258
- throw new Error("MCP host is stopped");
259
- const spec = this.requireEnabledSpec(key);
260
- const ik = resolveInstanceKey(spec, instanceKey);
261
- if (spec.transport === "stdio") {
262
- const now = new Date().toISOString();
263
- return { key: spec.key, instanceKey: ik, startedAt: now, lastUsedAt: now };
264
- }
265
- const rkey = regKey(spec.key, ik);
266
- const inProgress = this.spawning.get(rkey);
267
- if (inProgress)
268
- return inProgress;
269
- const promise = (async () => {
270
- const existing = this.registry.get(rkey);
271
- if (existing && !existing.process.killed && existing.process.exitCode === null) {
272
- if (await tcpProbe(existing.port)) {
273
- existing.lastUsedAt = Date.now();
274
- return this.toInstance(existing);
275
- }
276
- this.log("warn", "MCP instance unresponsive, restarting", {
277
- key: spec.key,
278
- instanceKey: ik,
279
- port: existing.port
280
- });
281
- await this.stopRunning(rkey, existing);
282
- } else if (existing) {
283
- this.cleanupRunning(rkey, existing);
284
- }
285
- return this.spawnHttp(spec, ik, rkey);
286
- })().finally(() => this.spawning.delete(rkey));
287
- this.spawning.set(rkey, promise);
288
- return promise;
289
- }
290
- touch(key, instanceKey) {
291
- const inst = this.registry.get(regKey(key, instanceKey ?? "node"));
292
- if (inst)
293
- inst.lastUsedAt = Date.now();
294
- }
295
- async stop(key, instanceKey) {
296
- const rkey = regKey(key, instanceKey ?? "node");
297
- const inst = this.registry.get(rkey);
298
- if (!inst)
299
- return false;
300
- await this.stopRunning(rkey, inst);
301
- return true;
302
- }
303
- async stopAll() {
304
- this.closed = true;
305
- await Promise.allSettled([...this.spawning.values()]);
306
- await Promise.all([...this.registry.entries()].map(([rkey, inst]) => this.stopRunning(rkey, inst)));
307
- }
308
- listRunning() {
309
- return [...this.registry.values()].map((inst) => this.toInstance(inst));
310
- }
311
- async buildAgentSpec(key, flavor, instanceKey) {
312
- const spec = this.requireEnabledSpec(key);
313
- if (spec.transport === "stdio") {
314
- return {
315
- command: spec.command,
316
- ...spec.args ? { args: [...spec.args] } : {},
317
- ...spec.env ? { env: { ...spec.env } } : {}
318
- };
319
- }
320
- const inst = await this.ensure(key, instanceKey);
321
- if (!inst.url)
322
- throw new Error(`MCP instance "${key}" has no URL (unexpected for http)`);
323
- return flavor === "sse" ? { type: "sse", url: `${inst.url}/sse` } : { url: `${inst.url}/mcp` };
324
- }
325
- startSweeper() {
326
- const timer = setInterval(() => {
327
- this.sweep();
328
- }, this.sweepIntervalMs);
329
- timer.unref?.();
330
- return () => clearInterval(timer);
331
- }
332
- requireEnabledSpec(key) {
333
- const spec = this.manifest.get(key);
334
- if (!spec)
335
- throw new Error(`Unknown MCP spec: "${key}"`);
336
- if (!this.manifest.isEnabled(key))
337
- throw new Error(`MCP spec "${key}" is disabled`);
338
- return spec;
339
- }
340
- toInstance(inst) {
341
- return {
342
- key: inst.key,
343
- instanceKey: inst.instanceKey,
344
- pid: inst.process.pid ?? -1,
345
- port: inst.port,
346
- url: `http://127.0.0.1:${inst.port}`,
347
- startedAt: new Date(inst.startedAt).toISOString(),
348
- lastUsedAt: new Date(inst.lastUsedAt).toISOString()
349
- };
350
- }
351
- async spawnHttp(spec, instanceKey, rkey) {
352
- const fileName = portFileName(spec.key, instanceKey);
353
- const port = await this.allocatePort(spec, fileName);
354
- if (this.closed) {
355
- this.usedPorts.delete(port);
356
- throw new Error("MCP host stopped during server startup");
357
- }
358
- const args = (spec.args ?? []).map((a) => a.replaceAll("{port}", String(port)));
359
- let spawnFailed = false;
360
- let proc;
361
- try {
362
- proc = spawn(spec.command, args, {
363
- stdio: "ignore",
364
- detached: false,
365
- cwd: spec.cwd,
366
- env: { ...process.env, ...spec.env }
367
- });
368
- } catch (e) {
369
- this.usedPorts.delete(port);
370
- throw e;
371
- }
372
- proc.once("error", (err) => {
373
- spawnFailed = true;
374
- this.log("error", "MCP server process error", {
375
- key: spec.key,
376
- instanceKey,
377
- err: String(err)
378
- });
379
- const cur = this.registry.get(rkey);
380
- if (cur?.process === proc)
381
- this.cleanupRunning(rkey, cur);
382
- });
383
- proc.once("exit", (code) => {
384
- const cur = this.registry.get(rkey);
385
- if (cur?.process === proc) {
386
- this.log("warn", "MCP server exited unexpectedly", { key: spec.key, instanceKey, code });
387
- this.cleanupRunning(rkey, cur);
388
- }
389
- });
390
- const now = Date.now();
391
- const inst = {
392
- spec,
393
- key: spec.key,
394
- instanceKey,
395
- process: proc,
396
- port,
397
- fileName,
398
- startedAt: now,
399
- lastUsedAt: now,
400
- lastHealthAt: now
401
- };
402
- this.registry.set(rkey, inst);
403
- const timeoutMs = spec.readyTimeoutMs ?? DEFAULT_READY_TIMEOUT_MS;
404
- const deadline = Date.now() + timeoutMs;
405
- let ready = false;
406
- while (Date.now() < deadline) {
407
- if (this.closed || spawnFailed || proc.exitCode !== null)
408
- break;
409
- if (await tcpProbe(port, 500)) {
410
- ready = true;
411
- break;
412
- }
413
- await delay(READY_POLL_MS);
414
- }
415
- if (!ready) {
416
- const exitCode = proc.exitCode;
417
- await this.stopRunning(rkey, inst);
418
- throw new Error(`MCP server "${spec.key}" (${instanceKey}) not ready on port ${port} within ${timeoutMs}ms` + (exitCode === null ? "" : ` (exitCode=${exitCode})`));
419
- }
420
- this.writePortFile(fileName, port);
421
- this.log("info", "MCP server started", { key: spec.key, instanceKey, port, pid: proc.pid });
422
- return this.toInstance(inst);
423
- }
424
- async allocatePort(spec, fileName) {
425
- const range = spec.portRange;
426
- if (!range)
427
- throw new Error(`MCP spec "${spec.key}" is http but has no portRange`);
428
- mkdirSync2(this.portsDir, { recursive: true });
429
- const claims = this.readPortClaims();
430
- const own = claims.get(fileName);
431
- if (own !== undefined && own >= range.base && own <= range.max && !this.usedPorts.has(own)) {
432
- this.usedPorts.add(own);
433
- if (await canBind(own))
434
- return own;
435
- this.usedPorts.delete(own);
436
- }
437
- const foreign = new Set;
438
- for (const [file, port] of claims) {
439
- if (file !== fileName)
440
- foreign.add(port);
441
- }
442
- for (let port = range.base;port <= range.max; port++) {
443
- if (this.usedPorts.has(port) || foreign.has(port))
444
- continue;
445
- this.usedPorts.add(port);
446
- if (await canBind(port))
447
- return port;
448
- this.usedPorts.delete(port);
449
- }
450
- throw new Error(`No free port for MCP spec "${spec.key}" in range ${range.base}-${range.max} ` + `(${this.registry.size} instances running)`);
451
- }
452
- readPortClaims() {
453
- const claims = new Map;
454
- let files;
455
- try {
456
- files = readdirSync(this.portsDir);
457
- } catch {
458
- return claims;
459
- }
460
- for (const file of files) {
461
- if (file.includes(".tmp-"))
462
- continue;
463
- try {
464
- const port = Number.parseInt(readFileSync2(join(this.portsDir, file), "utf8").trim(), 10);
465
- if (!Number.isNaN(port))
466
- claims.set(file, port);
467
- } catch {}
468
- }
469
- return claims;
470
- }
471
- writePortFile(fileName, port) {
472
- try {
473
- mkdirSync2(this.portsDir, { recursive: true });
474
- const file = join(this.portsDir, fileName);
475
- const tmp = `${file}.tmp-${process.pid}`;
476
- writeFileSync2(tmp, String(port));
477
- renameSync2(tmp, file);
478
- } catch (e) {
479
- this.log("warn", "Failed to write MCP port file", { fileName, port, err: String(e) });
480
- }
481
- }
482
- deletePortFile(fileName) {
483
- try {
484
- unlinkSync(join(this.portsDir, fileName));
485
- } catch {}
486
- }
487
- cleanupRunning(rkey, inst) {
488
- if (this.registry.get(rkey) === inst)
489
- this.registry.delete(rkey);
490
- this.usedPorts.delete(inst.port);
491
- this.deletePortFile(inst.fileName);
492
- }
493
- async stopRunning(rkey, inst) {
494
- const proc = inst.process;
495
- proc.removeAllListeners("error");
496
- proc.removeAllListeners("exit");
497
- this.cleanupRunning(rkey, inst);
498
- if (proc.exitCode !== null || proc.signalCode !== null)
499
- return;
500
- try {
501
- proc.kill("SIGTERM");
502
- } catch {}
503
- const exited = await waitExit(proc, KILL_GRACE_MS);
504
- if (!exited) {
505
- if (proc.pid)
506
- killProcessTree(proc.pid);
507
- try {
508
- proc.kill("SIGKILL");
509
- } catch {}
510
- await waitExit(proc, 1000);
511
- }
512
- this.log("info", "MCP server stopped", {
513
- key: inst.key,
514
- instanceKey: inst.instanceKey,
515
- port: inst.port,
516
- forced: !exited
517
- });
518
- }
519
- async sweep() {
520
- if (this.closed || this.sweepInFlight)
521
- return;
522
- this.sweepInFlight = true;
523
- try {
524
- const now = Date.now();
525
- for (const [rkey, inst] of [...this.registry]) {
526
- if (this.registry.get(rkey) !== inst)
527
- continue;
528
- const { idleEvictMs, healthIntervalMs } = inst.spec;
529
- if (idleEvictMs && now - inst.lastUsedAt >= idleEvictMs) {
530
- this.log("info", "Evicting idle MCP instance", {
531
- key: inst.key,
532
- instanceKey: inst.instanceKey,
533
- idleMs: now - inst.lastUsedAt
534
- });
535
- await this.stopRunning(rkey, inst);
536
- continue;
537
- }
538
- if (healthIntervalMs && now - inst.lastHealthAt >= healthIntervalMs) {
539
- inst.lastHealthAt = now;
540
- const healthy = await tcpProbe(inst.port);
541
- if (healthy || this.registry.get(rkey) !== inst)
542
- continue;
543
- this.log("warn", "MCP health probe failed, restarting", {
544
- key: inst.key,
545
- instanceKey: inst.instanceKey,
546
- port: inst.port
547
- });
548
- await this.stopRunning(rkey, inst);
549
- try {
550
- await this.ensure(inst.key, inst.instanceKey);
551
- } catch (e) {
552
- this.log("error", "Failed to restart unhealthy MCP instance", {
553
- key: inst.key,
554
- instanceKey: inst.instanceKey,
555
- err: String(e)
556
- });
557
- }
558
- }
559
- }
560
- } finally {
561
- this.sweepInFlight = false;
562
- }
563
- }
564
- }
565
- export { mcpServerSpecSchema, McpManifest, McpHost };
566
-
567
- //# debugId=A8A346BF3845A8CF64756E2164756E21
@@ -1,14 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../../../packages/mcp-host/src/spec.ts", "../../../packages/mcp-host/src/manifest.ts", "../../../packages/mcp-host/src/paths.ts", "../../../packages/mcp-host/src/manager.ts", "../../../packages/mcp-host/src/sanitize.ts"],
4
- "sourcesContent": [
5
- "import { z } from \"zod\";\n\n/**\n * MCP server transport kind.\n * - `stdio`: agents spawn the server per turn; the host does no process\n * management (specs are just validated catalog entries).\n * - `http`: the host launches a long-lived process that binds a port\n * exposing `/sse` and/or `/mcp`.\n */\nexport type McpTransport = \"stdio\" | \"http\";\n\n/** Catalog entry describing how to launch (or hand off) one MCP server. */\nexport interface McpServerSpec {\n /** Unique catalog name, e.g. \"playwright\". Also used in port file names. */\n key: string;\n transport: McpTransport;\n /** Executable to run. */\n command: string;\n /** Arguments; any literal \"{port}\" is substituted with the allocated port at launch. */\n args?: string[];\n /** Extra environment merged onto process.env for the child. */\n env?: Record<string, string>;\n cwd?: string;\n /** Ports scanned for a free slot. Required when transport === \"http\". */\n portRange?: { base: number; max: number };\n /** node = one shared process for the whole node; instance = one per instanceKey. */\n scope: \"node\" | \"instance\";\n /** How long to wait for the port to accept TCP after spawn. Default 20_000. */\n readyTimeoutMs?: number;\n /** Stop the instance after this long unused (http only). */\n idleEvictMs?: number;\n /** Periodic TCP probe interval; the instance is restarted on failure (http only). */\n healthIntervalMs?: number;\n}\n\n/** Snapshot of one live (or pseudo, for stdio) MCP instance. */\nexport interface McpInstance {\n key: string;\n /** \"node\" for node scope; caller-provided for instance scope. */\n instanceKey: string;\n /** Child process id. Absent for stdio pseudo-instances (nothing is spawned). */\n pid?: number;\n /** http only. */\n port?: number;\n /** http only: `http://127.0.0.1:{port}` (no path; endpoints live at /sse and /mcp). */\n url?: string;\n startedAt: string;\n lastUsedAt: string;\n}\n\n/** Keys must be safe as-is in file names and registry keys. */\nconst KEY_PATTERN = /^[a-zA-Z0-9._-]+$/;\n\nconst portSchema = z.number().int().min(1).max(65535);\n\n/**\n * Runtime validator for {@link McpServerSpec}. Enforces the cross-field\n * invariant that http specs carry a well-formed portRange. Unknown keys are\n * stripped, so manifests survive forward-compatible extra fields.\n */\nexport const mcpServerSpecSchema: z.ZodType<McpServerSpec> = z\n .object({\n key: z\n .string()\n .min(1)\n .regex(KEY_PATTERN, \"key may only contain [a-zA-Z0-9._-]\")\n .refine((k) => k !== \".\" && k !== \"..\", \"key may not be a dot path\"),\n transport: z.enum([\"stdio\", \"http\"]),\n command: z.string().min(1),\n args: z.array(z.string()).optional(),\n env: z.record(z.string(), z.string()).optional(),\n cwd: z.string().optional(),\n portRange: z.object({ base: portSchema, max: portSchema }).optional(),\n scope: z.enum([\"node\", \"instance\"]),\n readyTimeoutMs: z.number().int().positive().optional(),\n idleEvictMs: z.number().int().positive().optional(),\n healthIntervalMs: z.number().int().positive().optional(),\n })\n .superRefine((spec, ctx) => {\n if (spec.transport === \"http\") {\n if (!spec.portRange) {\n ctx.addIssue({\n code: \"custom\",\n path: [\"portRange\"],\n message: 'portRange is required when transport is \"http\"',\n });\n } else if (spec.portRange.max < spec.portRange.base) {\n ctx.addIssue({\n code: \"custom\",\n path: [\"portRange\"],\n message: \"portRange.max must be >= portRange.base\",\n });\n }\n }\n });\n",
6
- "import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from \"node:fs\";\nimport { dirname } from \"node:path\";\nimport { z } from \"zod\";\nimport { defaultManifestFile } from \"#paths\";\nimport { type McpServerSpec, mcpServerSpecSchema } from \"#spec\";\n\nconst manifestEntrySchema = z.object({\n spec: mcpServerSpecSchema,\n enabled: z.boolean(),\n});\n\nconst manifestFileSchema = z.object({\n version: z.literal(1),\n servers: z.array(manifestEntrySchema),\n});\n\ninterface ManifestEntry {\n spec: McpServerSpec;\n enabled: boolean;\n}\n\n/**\n * Persistent per-node registry of the MCP servers assigned to this node.\n *\n * Invariants:\n * - Keys are unique; `add` throws on duplicates rather than silently replacing.\n * - Disabled specs stay listed (visible in `list`) but consumers (McpHost)\n * must refuse to launch them.\n * - Every mutation is persisted synchronously with an atomic tmp+rename write,\n * so a fresh `McpManifest` on the same file always observes the last change.\n * - A corrupt or schema-invalid file throws at construction instead of being\n * silently clobbered on the next save.\n */\nexport class McpManifest {\n private readonly file: string;\n private readonly entries = new Map<string, ManifestEntry>();\n\n constructor(opts?: { file?: string }) {\n this.file = opts?.file ?? defaultManifestFile();\n this.load();\n }\n\n /** All specs, enabled or not, in insertion order. Returns defensive copies. */\n list(): McpServerSpec[] {\n return [...this.entries.values()].map((e) => structuredClone(e.spec));\n }\n\n get(key: string): McpServerSpec | undefined {\n const entry = this.entries.get(key);\n return entry ? structuredClone(entry.spec) : undefined;\n }\n\n /** Register a spec (validated). Throws on duplicate key. New specs start enabled. */\n add(spec: McpServerSpec): void {\n const parsed = mcpServerSpecSchema.parse(spec);\n if (this.entries.has(parsed.key)) {\n throw new Error(`MCP spec already exists in manifest: \"${parsed.key}\"`);\n }\n this.entries.set(parsed.key, { spec: parsed, enabled: true });\n this.save();\n }\n\n remove(key: string): boolean {\n const removed = this.entries.delete(key);\n if (removed) this.save();\n return removed;\n }\n\n /** Toggle launchability without losing the catalog entry. Throws on unknown key. */\n setEnabled(key: string, enabled: boolean): void {\n const entry = this.entries.get(key);\n if (!entry) throw new Error(`Unknown MCP spec: \"${key}\"`);\n if (entry.enabled === enabled) return;\n entry.enabled = enabled;\n this.save();\n }\n\n /** False for unknown keys as well as explicitly disabled ones. */\n isEnabled(key: string): boolean {\n return this.entries.get(key)?.enabled ?? false;\n }\n\n private load(): void {\n if (!existsSync(this.file)) return;\n let parsed: z.infer<typeof manifestFileSchema>;\n try {\n parsed = manifestFileSchema.parse(JSON.parse(readFileSync(this.file, \"utf8\")));\n } catch (e) {\n throw new Error(\n `Invalid MCP manifest at ${this.file}: ${e instanceof Error ? e.message : String(e)}`,\n );\n }\n for (const entry of parsed.servers) {\n if (this.entries.has(entry.spec.key)) {\n throw new Error(`Duplicate MCP spec key in manifest ${this.file}: \"${entry.spec.key}\"`);\n }\n this.entries.set(entry.spec.key, entry);\n }\n }\n\n private save(): void {\n mkdirSync(dirname(this.file), { recursive: true });\n const payload = {\n version: 1 as const,\n servers: [...this.entries.values()],\n };\n // Atomic write: a crash mid-write must never leave a truncated manifest.\n const tmp = `${this.file}.tmp-${process.pid}`;\n writeFileSync(tmp, `${JSON.stringify(payload, null, 2)}\\n`);\n renameSync(tmp, this.file);\n }\n}\n",
7
- "import { homedir } from \"node:os\";\nimport { resolve } from \"node:path\";\n\n/**\n * Node state directory. Each machine is one negotium node; all node state\n * lives in one dotdir. `NEGOTIUM_STATE_DIR` overrides (tests,\n * multi-node-on-one-box). Resolved lazily at call time — not module load —\n * so tests can point the env var at a temp dir before constructing objects.\n */\nexport function stateDir(): string {\n const env = process.env.NEGOTIUM_STATE_DIR?.trim();\n return env ? resolve(env) : resolve(homedir(), \".negotium\");\n}\n\n/** Default directory holding one port file per running http MCP instance. */\nexport function defaultPortsDir(): string {\n return resolve(stateDir(), \"run\", \"mcp-ports\");\n}\n\n/** Default location of the persistent per-node MCP manifest. */\nexport function defaultManifestFile(): string {\n return resolve(stateDir(), \"data\", \"mcp-manifest.json\");\n}\n",
8
- "import { type ChildProcess, execFileSync, spawn } from \"node:child_process\";\nimport {\n mkdirSync,\n readdirSync,\n readFileSync,\n renameSync,\n unlinkSync,\n writeFileSync,\n} from \"node:fs\";\nimport { connect, createServer } from \"node:net\";\nimport { join } from \"node:path\";\nimport { McpManifest } from \"#manifest\";\nimport { defaultPortsDir } from \"#paths\";\nimport { sanitizePathComponent } from \"#sanitize\";\nimport type { McpInstance, McpServerSpec } from \"#spec\";\n\nconst DEFAULT_READY_TIMEOUT_MS = 20_000;\nconst READY_POLL_MS = 150;\n/** SIGTERM → grace → SIGKILL. */\nconst KILL_GRACE_MS = 5_000;\nconst DEFAULT_SWEEP_INTERVAL_MS = 30_000;\nconst PROBE_TIMEOUT_MS = 1_000;\n\nexport type McpHostLogLevel = \"info\" | \"warn\" | \"error\";\nexport type McpHostLog = (level: McpHostLogLevel, msg: string, ctx?: object) => void;\n\nexport interface McpHostOptions {\n manifest?: McpManifest;\n /** Directory for port files. Default `~/.negotium/run/mcp-ports` (NEGOTIUM_STATE_DIR aware). */\n portsDir?: string;\n /** Injectable logger; defaults to console. Pass a no-op to silence. */\n log?: McpHostLog;\n /** Idle-eviction / health sweep cadence for `startSweeper`. Default 30s. */\n sweepIntervalMs?: number;\n}\n\n/** In-memory record of one live http child. */\ninterface RunningInstance {\n spec: McpServerSpec;\n key: string;\n instanceKey: string;\n process: ChildProcess;\n port: number;\n /** Port file name under portsDir (`${key}--${sanitizedInstanceKey}`). */\n fileName: string;\n startedAt: number;\n lastUsedAt: number;\n lastHealthAt: number;\n}\n\nconst defaultLog: McpHostLog = (level, msg, ctx) => {\n const fn = level === \"info\" ? console.log : level === \"warn\" ? console.warn : console.error;\n if (ctx) fn(`[mcp-host] ${msg}`, ctx);\n else fn(`[mcp-host] ${msg}`);\n};\n\nconst delay = (ms: number) => new Promise<void>((resolve) => setTimeout(resolve, ms));\n\n/** Registry key. `\u0000` cannot appear in keys/instanceKeys ambiguity-free. */\nfunction regKey(key: string, instanceKey: string): string {\n return `${key}\u0000${instanceKey}`;\n}\n\nfunction portFileName(key: string, instanceKey: string): string {\n return `${sanitizePathComponent(key)}--${sanitizePathComponent(instanceKey)}`;\n}\n\n/**\n * Resolve the effective instanceKey. Node-scoped specs always collapse to\n * \"node\" (one shared process per node); instance-scoped specs require an\n * explicit caller-provided key.\n */\nfunction resolveInstanceKey(spec: McpServerSpec, instanceKey?: string): string {\n if (spec.scope === \"node\") return \"node\";\n if (!instanceKey) {\n throw new Error(`MCP spec \"${spec.key}\" is instance-scoped; an instanceKey is required`);\n }\n return instanceKey;\n}\n\n/** Can we bind the port? True means it is free right now. */\nfunction canBind(port: number): Promise<boolean> {\n return new Promise((resolve) => {\n const srv = createServer();\n srv.once(\"error\", () => resolve(false));\n srv.listen({ port, host: \"127.0.0.1\", exclusive: true }, () => {\n srv.close(() => resolve(true));\n });\n });\n}\n\n/** Does something accept TCP on the port? Used for ready-wait and health. */\nfunction tcpProbe(port: number, timeoutMs = PROBE_TIMEOUT_MS): Promise<boolean> {\n return new Promise((resolve) => {\n const sock = connect({ port, host: \"127.0.0.1\" });\n let settled = false;\n const done = (ok: boolean) => {\n if (settled) return;\n settled = true;\n sock.destroy();\n resolve(ok);\n };\n sock.once(\"connect\", () => done(true));\n sock.once(\"error\", () => done(false));\n sock.setTimeout(timeoutMs, () => done(false));\n });\n}\n\n/**\n * Kill every descendant of `pid`, depth-first, so grandchildren are still\n * discoverable via `pgrep -P` before their parents die (same pattern as the\n * playwright manager's Chrome-tree reaping).\n */\nfunction killProcessTree(pid: number): void {\n let children: number[] = [];\n try {\n children = execFileSync(\"pgrep\", [\"-P\", String(pid)], { stdio: \"pipe\" })\n .toString()\n .trim()\n .split(\"\\n\")\n .map((s) => Number.parseInt(s, 10))\n .filter((n) => !Number.isNaN(n));\n } catch {\n return; // no children\n }\n for (const child of children) killProcessTree(child);\n for (const signal of [\"SIGTERM\", \"SIGKILL\"] as const) {\n for (const child of children) {\n try {\n process.kill(child, signal);\n } catch {\n // already gone\n }\n }\n }\n}\n\n/** Wait for the child to exit, up to timeoutMs. True if it exited. */\nfunction waitExit(proc: ChildProcess, timeoutMs: number): Promise<boolean> {\n if (proc.exitCode !== null || proc.signalCode !== null) return Promise.resolve(true);\n return new Promise((resolve) => {\n const timer = setTimeout(() => resolve(false), timeoutMs);\n timer.unref?.();\n const settle = () => {\n clearTimeout(timer);\n resolve(true);\n };\n proc.once(\"exit\", settle);\n proc.once(\"error\", settle);\n });\n}\n\n/**\n * MCP server process manager for one negotium node.\n *\n * Lifecycle mechanics (lifted from the battle-tested playwright/background-bash\n * managers in core):\n * - Port allocation scans the spec's range, skipping ports claimed by other\n * instances' port files and verifying actual availability with a bind test.\n * In-memory reservations happen before any await so concurrent allocations\n * never race on a single-threaded event loop.\n * - The whole health-check → kill → spawn sequence for a key runs inside a\n * `spawning` promise registered synchronously, so concurrent `ensure` calls\n * for the same instance join one attempt instead of double-spawning.\n * - Port files are written atomically after ready and removed on stop/crash.\n * - Stops follow SIGTERM → 5s grace → SIGKILL (including the child's subtree).\n * - stdio specs involve no process management: agents spawn them per turn, so\n * `ensure` just validates and returns a pseudo-instance.\n */\nexport class McpHost {\n private readonly manifest: McpManifest;\n private readonly portsDir: string;\n private readonly log: McpHostLog;\n private readonly sweepIntervalMs: number;\n\n private readonly registry = new Map<string, RunningInstance>();\n private readonly usedPorts = new Set<number>();\n private readonly spawning = new Map<string, Promise<McpInstance>>();\n private sweepInFlight = false;\n private closed = false;\n\n constructor(opts?: McpHostOptions) {\n this.manifest = opts?.manifest ?? new McpManifest();\n this.portsDir = opts?.portsDir ?? defaultPortsDir();\n this.log = opts?.log ?? defaultLog;\n this.sweepIntervalMs = opts?.sweepIntervalMs ?? DEFAULT_SWEEP_INTERVAL_MS;\n }\n\n /**\n * Ensure a running instance for `key` (+`instanceKey` for instance scope).\n * Reuses a live, TCP-healthy instance; otherwise allocates a free port,\n * spawns, waits for ready, and writes the port file. For stdio specs this\n * only validates and returns a pseudo-instance (no pid/port).\n */\n async ensure(key: string, instanceKey?: string): Promise<McpInstance> {\n if (this.closed) throw new Error(\"MCP host is stopped\");\n const spec = this.requireEnabledSpec(key);\n const ik = resolveInstanceKey(spec, instanceKey);\n\n if (spec.transport === \"stdio\") {\n const now = new Date().toISOString();\n return { key: spec.key, instanceKey: ik, startedAt: now, lastUsedAt: now };\n }\n\n const rkey = regKey(spec.key, ik);\n const inProgress = this.spawning.get(rkey);\n if (inProgress) return inProgress;\n\n // No await between the guard above and spawning.set below — airtight on a\n // single-threaded event loop (see ensurePlaywright/ensureBgBash in core).\n const promise = (async (): Promise<McpInstance> => {\n const existing = this.registry.get(rkey);\n if (existing && !existing.process.killed && existing.process.exitCode === null) {\n if (await tcpProbe(existing.port)) {\n existing.lastUsedAt = Date.now();\n return this.toInstance(existing);\n }\n this.log(\"warn\", \"MCP instance unresponsive, restarting\", {\n key: spec.key,\n instanceKey: ik,\n port: existing.port,\n });\n await this.stopRunning(rkey, existing);\n } else if (existing) {\n // Process already dead but the exit handler hasn't cleaned up yet.\n this.cleanupRunning(rkey, existing);\n }\n return this.spawnHttp(spec, ik, rkey);\n })().finally(() => this.spawning.delete(rkey));\n this.spawning.set(rkey, promise);\n return promise;\n }\n\n /** Touch lastUsedAt so idle eviction doesn't reap an active instance. */\n touch(key: string, instanceKey?: string): void {\n const inst = this.registry.get(regKey(key, instanceKey ?? \"node\"));\n if (inst) inst.lastUsedAt = Date.now();\n }\n\n /** Stop one instance (SIGTERM → 5s → SIGKILL). True if something was running. */\n async stop(key: string, instanceKey?: string): Promise<boolean> {\n const rkey = regKey(key, instanceKey ?? \"node\");\n const inst = this.registry.get(rkey);\n if (!inst) return false;\n await this.stopRunning(rkey, inst);\n return true;\n }\n\n /** Stop every running instance. Call on node shutdown. */\n async stopAll(): Promise<void> {\n this.closed = true;\n await Promise.allSettled([...this.spawning.values()]);\n await Promise.all(\n [...this.registry.entries()].map(([rkey, inst]) => this.stopRunning(rkey, inst)),\n );\n }\n\n listRunning(): McpInstance[] {\n return [...this.registry.values()].map((inst) => this.toInstance(inst));\n }\n\n /**\n * Build the agent-facing transport spec.\n * - stdio → `{command, args?, env?}` (flavor is irrelevant; nothing is spawned).\n * - http → ensures a live instance, then `{type:\"sse\", url:…/sse}` for the\n * SSE transport or `{url:…/mcp}` for streamable-http.\n */\n async buildAgentSpec(\n key: string,\n flavor: \"sse\" | \"http\",\n instanceKey?: string,\n ): Promise<Record<string, unknown>> {\n const spec = this.requireEnabledSpec(key);\n if (spec.transport === \"stdio\") {\n return {\n command: spec.command,\n ...(spec.args ? { args: [...spec.args] } : {}),\n ...(spec.env ? { env: { ...spec.env } } : {}),\n };\n }\n const inst = await this.ensure(key, instanceKey);\n if (!inst.url) throw new Error(`MCP instance \"${key}\" has no URL (unexpected for http)`);\n return flavor === \"sse\" ? { type: \"sse\", url: `${inst.url}/sse` } : { url: `${inst.url}/mcp` };\n }\n\n /**\n * Start the combined idle-eviction / health sweep timer. Idle instances\n * (spec.idleEvictMs) are stopped; unhealthy ones (spec.healthIntervalMs TCP\n * probe fails) are restarted. Returns a stop function.\n */\n startSweeper(): () => void {\n const timer = setInterval(() => {\n void this.sweep();\n }, this.sweepIntervalMs);\n timer.unref?.();\n return () => clearInterval(timer);\n }\n\n // --- internals ---\n\n private requireEnabledSpec(key: string): McpServerSpec {\n const spec = this.manifest.get(key);\n if (!spec) throw new Error(`Unknown MCP spec: \"${key}\"`);\n if (!this.manifest.isEnabled(key)) throw new Error(`MCP spec \"${key}\" is disabled`);\n return spec;\n }\n\n private toInstance(inst: RunningInstance): McpInstance {\n return {\n key: inst.key,\n instanceKey: inst.instanceKey,\n pid: inst.process.pid ?? -1,\n port: inst.port,\n url: `http://127.0.0.1:${inst.port}`,\n startedAt: new Date(inst.startedAt).toISOString(),\n lastUsedAt: new Date(inst.lastUsedAt).toISOString(),\n };\n }\n\n private async spawnHttp(\n spec: McpServerSpec,\n instanceKey: string,\n rkey: string,\n ): Promise<McpInstance> {\n const fileName = portFileName(spec.key, instanceKey);\n const port = await this.allocatePort(spec, fileName);\n if (this.closed) {\n this.usedPorts.delete(port);\n throw new Error(\"MCP host stopped during server startup\");\n }\n\n const args = (spec.args ?? []).map((a) => a.replaceAll(\"{port}\", String(port)));\n let spawnFailed = false;\n let proc: ChildProcess;\n try {\n proc = spawn(spec.command, args, {\n stdio: \"ignore\",\n detached: false,\n cwd: spec.cwd,\n env: { ...process.env, ...spec.env },\n });\n } catch (e) {\n this.usedPorts.delete(port);\n throw e;\n }\n\n // Crash handlers: only clean up if the registry still points at this exact\n // process — intentional stops remove these listeners first, so a firing\n // handler always means the child died on its own.\n proc.once(\"error\", (err) => {\n spawnFailed = true;\n this.log(\"error\", \"MCP server process error\", {\n key: spec.key,\n instanceKey,\n err: String(err),\n });\n const cur = this.registry.get(rkey);\n if (cur?.process === proc) this.cleanupRunning(rkey, cur);\n });\n proc.once(\"exit\", (code) => {\n const cur = this.registry.get(rkey);\n if (cur?.process === proc) {\n this.log(\"warn\", \"MCP server exited unexpectedly\", { key: spec.key, instanceKey, code });\n this.cleanupRunning(rkey, cur);\n }\n });\n\n const now = Date.now();\n const inst: RunningInstance = {\n spec,\n key: spec.key,\n instanceKey,\n process: proc,\n port,\n fileName,\n startedAt: now,\n lastUsedAt: now,\n lastHealthAt: now,\n };\n this.registry.set(rkey, inst);\n\n const timeoutMs = spec.readyTimeoutMs ?? DEFAULT_READY_TIMEOUT_MS;\n const deadline = Date.now() + timeoutMs;\n let ready = false;\n while (Date.now() < deadline) {\n if (this.closed || spawnFailed || proc.exitCode !== null) break;\n if (await tcpProbe(port, 500)) {\n ready = true;\n break;\n }\n await delay(READY_POLL_MS);\n }\n\n if (!ready) {\n const exitCode = proc.exitCode;\n await this.stopRunning(rkey, inst);\n throw new Error(\n `MCP server \"${spec.key}\" (${instanceKey}) not ready on port ${port} within ${timeoutMs}ms` +\n (exitCode === null ? \"\" : ` (exitCode=${exitCode})`),\n );\n }\n\n this.writePortFile(fileName, port);\n this.log(\"info\", \"MCP server started\", { key: spec.key, instanceKey, port, pid: proc.pid });\n return this.toInstance(inst);\n }\n\n /**\n * Allocate a free port in the spec's range: reuse this instance's own prior\n * port file when its port is actually bindable (stable across restarts and\n * self-heals stale files after a host crash), skip ports claimed by other\n * instances' files, reserve in-memory before the async bind check, and\n * verify availability by binding.\n */\n private async allocatePort(spec: McpServerSpec, fileName: string): Promise<number> {\n const range = spec.portRange;\n if (!range) throw new Error(`MCP spec \"${spec.key}\" is http but has no portRange`);\n mkdirSync(this.portsDir, { recursive: true });\n\n const claims = this.readPortClaims();\n\n const own = claims.get(fileName);\n if (own !== undefined && own >= range.base && own <= range.max && !this.usedPorts.has(own)) {\n this.usedPorts.add(own); // reserve before await\n if (await canBind(own)) return own;\n // Occupied — possibly an orphan from a previous run we cannot safely\n // identify generically. Leave it alone and allocate elsewhere.\n this.usedPorts.delete(own);\n }\n\n const foreign = new Set<number>();\n for (const [file, port] of claims) {\n if (file !== fileName) foreign.add(port);\n }\n\n for (let port = range.base; port <= range.max; port++) {\n if (this.usedPorts.has(port) || foreign.has(port)) continue;\n this.usedPorts.add(port); // reserve before await\n if (await canBind(port)) return port;\n this.usedPorts.delete(port);\n }\n throw new Error(\n `No free port for MCP spec \"${spec.key}\" in range ${range.base}-${range.max} ` +\n `(${this.registry.size} instances running)`,\n );\n }\n\n /** Read all port files (fileName → port), ignoring tmp files and garbage. */\n private readPortClaims(): Map<string, number> {\n const claims = new Map<string, number>();\n let files: string[];\n try {\n files = readdirSync(this.portsDir);\n } catch {\n return claims;\n }\n for (const file of files) {\n if (file.includes(\".tmp-\")) continue;\n try {\n const port = Number.parseInt(readFileSync(join(this.portsDir, file), \"utf8\").trim(), 10);\n if (!Number.isNaN(port)) claims.set(file, port);\n } catch {\n // unreadable/racing file — treat as no claim\n }\n }\n return claims;\n }\n\n private writePortFile(fileName: string, port: number): void {\n try {\n mkdirSync(this.portsDir, { recursive: true });\n const file = join(this.portsDir, fileName);\n const tmp = `${file}.tmp-${process.pid}`;\n writeFileSync(tmp, String(port));\n renameSync(tmp, file);\n } catch (e) {\n this.log(\"warn\", \"Failed to write MCP port file\", { fileName, port, err: String(e) });\n }\n }\n\n private deletePortFile(fileName: string): void {\n try {\n unlinkSync(join(this.portsDir, fileName));\n } catch {\n // already gone\n }\n }\n\n /** Synchronous bookkeeping cleanup: registry entry, port reservation, port file. */\n private cleanupRunning(rkey: string, inst: RunningInstance): void {\n if (this.registry.get(rkey) === inst) this.registry.delete(rkey);\n this.usedPorts.delete(inst.port);\n this.deletePortFile(inst.fileName);\n }\n\n /**\n * Intentional stop: detach crash handlers (so this isn't misread as a\n * crash), clean bookkeeping synchronously, then SIGTERM → 5s → SIGKILL the\n * child and its subtree.\n */\n private async stopRunning(rkey: string, inst: RunningInstance): Promise<void> {\n const proc = inst.process;\n proc.removeAllListeners(\"error\");\n proc.removeAllListeners(\"exit\");\n this.cleanupRunning(rkey, inst);\n\n if (proc.exitCode !== null || proc.signalCode !== null) return;\n try {\n proc.kill(\"SIGTERM\");\n } catch {\n // already dead\n }\n const exited = await waitExit(proc, KILL_GRACE_MS);\n if (!exited) {\n if (proc.pid) killProcessTree(proc.pid);\n try {\n proc.kill(\"SIGKILL\");\n } catch {\n // already dead\n }\n await waitExit(proc, 1_000);\n }\n this.log(\"info\", \"MCP server stopped\", {\n key: inst.key,\n instanceKey: inst.instanceKey,\n port: inst.port,\n forced: !exited,\n });\n }\n\n /** One sweep pass: evict idle instances, probe health, restart on failure. */\n private async sweep(): Promise<void> {\n if (this.closed || this.sweepInFlight) return;\n this.sweepInFlight = true;\n try {\n const now = Date.now();\n for (const [rkey, inst] of [...this.registry]) {\n if (this.registry.get(rkey) !== inst) continue; // stopped mid-sweep\n const { idleEvictMs, healthIntervalMs } = inst.spec;\n\n if (idleEvictMs && now - inst.lastUsedAt >= idleEvictMs) {\n this.log(\"info\", \"Evicting idle MCP instance\", {\n key: inst.key,\n instanceKey: inst.instanceKey,\n idleMs: now - inst.lastUsedAt,\n });\n await this.stopRunning(rkey, inst);\n continue;\n }\n\n if (healthIntervalMs && now - inst.lastHealthAt >= healthIntervalMs) {\n inst.lastHealthAt = now;\n const healthy = await tcpProbe(inst.port);\n if (healthy || this.registry.get(rkey) !== inst) continue;\n this.log(\"warn\", \"MCP health probe failed, restarting\", {\n key: inst.key,\n instanceKey: inst.instanceKey,\n port: inst.port,\n });\n await this.stopRunning(rkey, inst);\n try {\n await this.ensure(inst.key, inst.instanceKey);\n } catch (e) {\n this.log(\"error\", \"Failed to restart unhealthy MCP instance\", {\n key: inst.key,\n instanceKey: inst.instanceKey,\n err: String(e),\n });\n }\n }\n }\n } finally {\n this.sweepInFlight = false;\n }\n }\n}\n",
9
- "/**\n * Sanitize an arbitrary string for use as a single path component (port file\n * names). Mirrors core's `sanitizeFileName` (reimplemented locally so this\n * package stays standalone): everything outside [a-zA-Z0-9._-] becomes \"_\",\n * empty input yields \"_\", and pure-dot results (\".\", \"..\") are replaced with\n * \"_\" to block path traversal when used as a standalone component.\n */\nexport function sanitizePathComponent(name: string): string {\n const safe = name.replace(/[^a-zA-Z0-9._-]/g, \"_\") || \"_\";\n if (safe === \".\" || safe === \"..\") return \"_\";\n return safe;\n}\n"
10
- ],
11
- "mappings": "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",
12
- "debugId": "A8A346BF3845A8CF64756E2164756E21",
13
- "names": []
14
- }