@agimon-ai/mcp-proxy 0.4.4 → 0.4.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.cjs CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- const require_src = require('./src-G1hs2GLZ.cjs');
2
+ const require_src = require('./src-B2m53VQ1.cjs');
3
3
  let node_crypto = require("node:crypto");
4
4
  let node_fs_promises = require("node:fs/promises");
5
5
  let node_path = require("node:path");
@@ -8,8 +8,8 @@ let node_fs = require("node:fs");
8
8
  let liquidjs = require("liquidjs");
9
9
  let node_child_process = require("node:child_process");
10
10
  let commander = require("commander");
11
- let __agimon_ai_foundation_process_registry = require("@agimon-ai/foundation-process-registry");
12
11
  let __agimon_ai_foundation_port_registry = require("@agimon-ai/foundation-port-registry");
12
+ let __agimon_ai_foundation_process_registry = require("@agimon-ai/foundation-process-registry");
13
13
  let node_url = require("node:url");
14
14
 
15
15
  //#region src/utils/output.ts
@@ -113,6 +113,169 @@ const initCommand = new commander.Command("init").description("Initialize MCP co
113
113
  }
114
114
  });
115
115
 
116
+ //#endregion
117
+ //#region src/commands/prestart-http.ts
118
+ /**
119
+ * Prestart HTTP Command
120
+ *
121
+ * Starts an mcp-proxy HTTP runtime in the background, waits until it is healthy,
122
+ * and then exits so the runtime can be reused by other commands.
123
+ */
124
+ const WORKSPACE_MARKERS = [
125
+ "pnpm-workspace.yaml",
126
+ "nx.json",
127
+ ".git"
128
+ ];
129
+ const DEFAULT_HOST$1 = "localhost";
130
+ const DEFAULT_TIMEOUT_MS = 12e4;
131
+ const POLL_INTERVAL_MS = 250;
132
+ function resolveWorkspaceRoot(startPath = process.env.PROJECT_PATH || process.cwd()) {
133
+ let current = node_path.default.resolve(startPath);
134
+ while (true) {
135
+ for (const marker of WORKSPACE_MARKERS) if ((0, node_fs.existsSync)(node_path.default.join(current, marker))) return current;
136
+ const parent = node_path.default.dirname(current);
137
+ if (parent === current) return process.cwd();
138
+ current = parent;
139
+ }
140
+ }
141
+ function resolveSiblingRegistryPath(registryPath, fileName) {
142
+ if (!registryPath) return;
143
+ const resolved = node_path.default.resolve(registryPath);
144
+ if (node_path.default.extname(resolved) === ".json") return node_path.default.join(node_path.default.dirname(resolved), fileName);
145
+ return node_path.default.join(resolved, fileName);
146
+ }
147
+ function buildCliCandidates() {
148
+ const __filename$1 = (0, node_url.fileURLToPath)(require("url").pathToFileURL(__filename).href);
149
+ const __dirname$1 = node_path.default.dirname(__filename$1);
150
+ const distCandidates = [
151
+ node_path.default.resolve(__dirname$1, "cli.mjs"),
152
+ node_path.default.resolve(__dirname$1, "..", "dist", "cli.mjs"),
153
+ node_path.default.resolve(__dirname$1, "..", "..", "dist", "cli.mjs")
154
+ ];
155
+ const srcCandidates = [node_path.default.resolve(__dirname$1, "..", "cli.ts"), node_path.default.resolve(__dirname$1, "..", "..", "src", "cli.ts")];
156
+ for (const candidate of distCandidates) if ((0, node_fs.existsSync)(candidate)) return {
157
+ command: process.execPath,
158
+ args: [candidate]
159
+ };
160
+ for (const candidate of srcCandidates) if ((0, node_fs.existsSync)(candidate)) return {
161
+ command: process.execPath,
162
+ args: [
163
+ "--import",
164
+ "tsx",
165
+ candidate
166
+ ]
167
+ };
168
+ throw new Error("Unable to locate mcp-proxy CLI entrypoint");
169
+ }
170
+ function parseTimeoutMs(value) {
171
+ if (!value) return DEFAULT_TIMEOUT_MS;
172
+ const parsed = Number.parseInt(value, 10);
173
+ if (!Number.isInteger(parsed) || parsed <= 0) throw new Error(`Invalid timeout value: ${value}`);
174
+ return parsed;
175
+ }
176
+ async function waitForFile(filePath, timeoutMs) {
177
+ const deadline = Date.now() + timeoutMs;
178
+ while (Date.now() < deadline) {
179
+ try {
180
+ await (0, node_fs_promises.access)(filePath);
181
+ return;
182
+ } catch {}
183
+ await new Promise((resolve$2) => setTimeout(resolve$2, POLL_INTERVAL_MS));
184
+ }
185
+ throw new Error(`Timed out waiting for runtime state file: ${filePath}`);
186
+ }
187
+ async function waitForHealthyRuntime(serverId, timeoutMs) {
188
+ const runtimeStateService = new require_src.RuntimeStateService();
189
+ const deadline = Date.now() + timeoutMs;
190
+ while (Date.now() < deadline) {
191
+ const record = await runtimeStateService.read(serverId);
192
+ if (record) {
193
+ const healthUrl = `http://${record.host}:${record.port}/health`;
194
+ try {
195
+ const response = await fetch(healthUrl);
196
+ if (response.ok) {
197
+ const payload = await response.json().catch(() => null);
198
+ if (!payload || payload.transport === "http") return {
199
+ host: record.host,
200
+ port: record.port
201
+ };
202
+ }
203
+ } catch {}
204
+ }
205
+ await new Promise((resolve$2) => setTimeout(resolve$2, POLL_INTERVAL_MS));
206
+ }
207
+ throw new Error(`Timed out waiting for HTTP runtime '${serverId}' to become healthy`);
208
+ }
209
+ function spawnBackgroundRuntime(args, env, cwd) {
210
+ const { command, args: baseArgs } = buildCliCandidates();
211
+ const child = (0, node_child_process.spawn)(command, [...baseArgs, ...args], {
212
+ detached: true,
213
+ cwd,
214
+ stdio: "ignore",
215
+ env
216
+ });
217
+ child.unref();
218
+ return child;
219
+ }
220
+ async function prestartHttpRuntime(options) {
221
+ const serverId = options.id || require_src.generateServerId();
222
+ const timeoutMs = parseTimeoutMs(options.timeoutMs);
223
+ const registryPath = options.registryPath || options.registryDir;
224
+ const workspaceRoot = resolveWorkspaceRoot();
225
+ const childEnv = {
226
+ ...process.env,
227
+ ...registryPath ? {
228
+ PORT_REGISTRY_PATH: registryPath,
229
+ PROCESS_REGISTRY_PATH: resolveSiblingRegistryPath(registryPath, "processes.json")
230
+ } : {}
231
+ };
232
+ const child = spawnBackgroundRuntime([
233
+ "mcp-serve",
234
+ "--type",
235
+ "http",
236
+ "--id",
237
+ serverId,
238
+ "--host",
239
+ options.host || DEFAULT_HOST$1,
240
+ ...options.port !== void 0 ? ["--port", String(options.port)] : [],
241
+ ...options.config ? ["--config", options.config] : [],
242
+ ...options.cache === false ? ["--no-cache"] : [],
243
+ ...options.definitionsCache ? ["--definitions-cache", options.definitionsCache] : [],
244
+ ...options.clearDefinitionsCache ? ["--clear-definitions-cache"] : [],
245
+ "--proxy-mode",
246
+ options.proxyMode
247
+ ], childEnv, workspaceRoot);
248
+ const childExit = new Promise((_, reject) => {
249
+ child.once("exit", (code, signal) => {
250
+ reject(/* @__PURE__ */ new Error(`Background runtime exited before becoming healthy (code=${code ?? "null"}, signal=${signal ?? "null"})`));
251
+ });
252
+ });
253
+ const runtimeFile = node_path.default.join(require_src.RuntimeStateService.getDefaultRuntimeDir(), `${serverId}.runtime.json`);
254
+ try {
255
+ await Promise.race([waitForFile(runtimeFile, timeoutMs), childExit]);
256
+ const { host, port } = await Promise.race([waitForHealthyRuntime(serverId, timeoutMs), childExit]);
257
+ return {
258
+ host,
259
+ port,
260
+ serverId,
261
+ workspaceRoot
262
+ };
263
+ } catch (error) {
264
+ throw new Error(`Failed to prestart HTTP runtime '${serverId}': ${error instanceof Error ? error.message : String(error)}`);
265
+ }
266
+ }
267
+ const prestartHttpCommand = new commander.Command("prestart-http").description("Start an mcp-proxy HTTP runtime in the background and wait until it is healthy").option("--id <id>", "Server identifier to assign to the runtime").option("--host <host>", "Host to bind to", DEFAULT_HOST$1).option("-p, --port <port>", "Preferred HTTP port for the runtime", (value) => Number.parseInt(value, 10)).option("-c, --config <path>", "Path to MCP server configuration file").option("--no-cache", "Disable configuration caching, always reload from config file").option("--definitions-cache <path>", "Path to prefetched tool/prompt/skill definitions cache file").option("--clear-definitions-cache", "Delete definitions cache before startup", false).option("--proxy-mode <mode>", "How mcp-proxy exposes downstream tools: meta, flat, or search", "meta").option("--registry-path <path>", "Custom registry path or directory for service discovery").option("--registry-dir <path>", "Custom registry directory for service discovery").option("--timeout-ms <ms>", "How long to wait for the runtime to become healthy", String(DEFAULT_TIMEOUT_MS)).action(async (options) => {
268
+ try {
269
+ const { host, port, serverId, workspaceRoot } = await prestartHttpRuntime(options);
270
+ process.stdout.write(`mcp-proxy HTTP runtime ready at http://${host}:${port} (${serverId})\n`);
271
+ process.stdout.write(`runtimeId=${serverId}\n`);
272
+ process.stdout.write(`runtimeUrl=http://${host}:${port}\n`);
273
+ process.stdout.write(`workspaceRoot=${workspaceRoot}\n`);
274
+ } catch (error) {
275
+ throw new Error(`Failed to prestart HTTP runtime '${options.id || "generated-server-id"}': ${error instanceof Error ? error.message : String(error)}`);
276
+ }
277
+ });
278
+
116
279
  //#endregion
117
280
  //#region src/commands/mcp-serve.ts
118
281
  /**
@@ -141,7 +304,7 @@ const CONFIG_FILE_NAMES = [
141
304
  "mcp-config.json"
142
305
  ];
143
306
  const MCP_ENDPOINT_PATH = "/mcp";
144
- const DEFAULT_HOST$1 = "localhost";
307
+ const DEFAULT_HOST = "localhost";
145
308
  const TRANSPORT_TYPE_STDIO = "stdio";
146
309
  const TRANSPORT_TYPE_HTTP = "http";
147
310
  const TRANSPORT_TYPE_SSE = "sse";
@@ -149,10 +312,14 @@ const TRANSPORT_TYPE_STDIO_HTTP = "stdio-http";
149
312
  const RUNTIME_TRANSPORT = TRANSPORT_TYPE_HTTP;
150
313
  const PORT_REGISTRY_SERVICE_HTTP = "mcp-proxy-http";
151
314
  const PORT_REGISTRY_SERVICE_TYPE = "service";
152
- const PORT_REGISTRY_REPOSITORY_PATH = process.cwd();
315
+ function getWorkspaceRoot() {
316
+ return resolveWorkspaceRoot();
317
+ }
153
318
  const PROCESS_REGISTRY_SERVICE_HTTP = "mcp-proxy-http";
154
319
  const PROCESS_REGISTRY_SERVICE_TYPE = "service";
155
- const PROCESS_REGISTRY_REPOSITORY_PATH = process.cwd();
320
+ function getRegistryRepositoryPath() {
321
+ return getWorkspaceRoot();
322
+ }
156
323
  function toErrorMessage$9(error) {
157
324
  return error instanceof Error ? error.message : String(error);
158
325
  }
@@ -213,7 +380,23 @@ function createTransportConfig(options, mode) {
213
380
  return {
214
381
  mode,
215
382
  port: options.port ?? (Number.isFinite(envPort) ? envPort : void 0),
216
- host: options.host || process.env.MCP_HOST || DEFAULT_HOST$1
383
+ host: options.host || process.env.MCP_HOST || DEFAULT_HOST
384
+ };
385
+ }
386
+ function createStdioSafeLogger() {
387
+ const logToStderr = (message, data) => {
388
+ if (data === void 0) {
389
+ console.error(message);
390
+ return;
391
+ }
392
+ console.error(message, data);
393
+ };
394
+ return {
395
+ trace: logToStderr,
396
+ debug: logToStderr,
397
+ info: logToStderr,
398
+ warn: logToStderr,
399
+ error: logToStderr
217
400
  };
218
401
  }
219
402
  function createServerOptions(options, resolvedConfigPath, serverId) {
@@ -234,7 +417,7 @@ function formatStartError(type, host, port, error) {
234
417
  function createRuntimeRecord(serverId, config, port, shutdownToken, configPath) {
235
418
  return {
236
419
  serverId,
237
- host: config.host ?? DEFAULT_HOST$1,
420
+ host: config.host ?? DEFAULT_HOST,
238
421
  port,
239
422
  transport: RUNTIME_TRANSPORT,
240
423
  shutdownToken,
@@ -258,7 +441,7 @@ async function createPortRegistryLease(serviceName, host, preferredPort, serverI
258
441
  } : __agimon_ai_foundation_port_registry.DEFAULT_PORT_RANGE) {
259
442
  const portRegistry = createPortRegistryService();
260
443
  const result = await portRegistry.reservePort({
261
- repositoryPath: PORT_REGISTRY_REPOSITORY_PATH,
444
+ repositoryPath: getRegistryRepositoryPath(),
262
445
  serviceName,
263
446
  serviceType: PORT_REGISTRY_SERVICE_TYPE,
264
447
  environment: getRegistryEnvironment(),
@@ -284,7 +467,7 @@ async function createPortRegistryLease(serviceName, host, preferredPort, serverI
284
467
  if (released) return;
285
468
  released = true;
286
469
  const releaseResult = await portRegistry.releasePort({
287
- repositoryPath: PORT_REGISTRY_REPOSITORY_PATH,
470
+ repositoryPath: getRegistryRepositoryPath(),
288
471
  serviceName,
289
472
  serviceType: PORT_REGISTRY_SERVICE_TYPE,
290
473
  pid: process.pid,
@@ -298,7 +481,7 @@ async function createPortRegistryLease(serviceName, host, preferredPort, serverI
298
481
  async function createProcessRegistryLease(serviceName, host, port, serverId, transport, configPath) {
299
482
  const processRegistry = createProcessRegistryService();
300
483
  const result = await processRegistry.registerProcess({
301
- repositoryPath: PROCESS_REGISTRY_REPOSITORY_PATH,
484
+ repositoryPath: getRegistryRepositoryPath(),
302
485
  serviceName,
303
486
  serviceType: PROCESS_REGISTRY_SERVICE_TYPE,
304
487
  environment: getRegistryEnvironment(),
@@ -319,7 +502,7 @@ async function createProcessRegistryLease(serviceName, host, port, serverId, tra
319
502
  if (released) return;
320
503
  released = true;
321
504
  const releaseResult = await processRegistry.releaseProcess({
322
- repositoryPath: PROCESS_REGISTRY_REPOSITORY_PATH,
505
+ repositoryPath: getRegistryRepositoryPath(),
323
506
  serviceName,
324
507
  serviceType: PROCESS_REGISTRY_SERVICE_TYPE,
325
508
  pid: process.pid,
@@ -420,13 +603,13 @@ async function createAndStartHttpRuntime(serverOptions, config, resolvedConfigPa
420
603
  min: requestedPort,
421
604
  max: requestedPort
422
605
  } : __agimon_ai_foundation_port_registry.DEFAULT_PORT_RANGE;
423
- const portLease = await createPortRegistryLease(PORT_REGISTRY_SERVICE_HTTP, config.host ?? DEFAULT_HOST$1, requestedPort, runtimeServerId, TRANSPORT_TYPE_HTTP, resolvedConfigPath, portRange);
606
+ const portLease = await createPortRegistryLease(PORT_REGISTRY_SERVICE_HTTP, config.host ?? DEFAULT_HOST, requestedPort, runtimeServerId, TRANSPORT_TYPE_HTTP, resolvedConfigPath, portRange);
424
607
  const runtimePort = portLease.port;
425
608
  const runtimeConfig = {
426
609
  ...config,
427
610
  port: runtimePort
428
611
  };
429
- const processLease = await createProcessRegistryLease(PROCESS_REGISTRY_SERVICE_HTTP, runtimeConfig.host ?? DEFAULT_HOST$1, runtimePort, runtimeServerId, TRANSPORT_TYPE_HTTP, resolvedConfigPath);
612
+ const processLease = await createProcessRegistryLease(PROCESS_REGISTRY_SERVICE_HTTP, runtimeConfig.host ?? DEFAULT_HOST, runtimePort, runtimeServerId, TRANSPORT_TYPE_HTTP, resolvedConfigPath);
430
613
  let releasePort = async () => {
431
614
  await releasePortLease(portLease ?? null);
432
615
  releasePort = async () => void 0;
@@ -482,7 +665,7 @@ async function createAndStartHttpRuntime(serverOptions, config, resolvedConfigPa
482
665
  }
483
666
  async function startStdioTransport(serverOptions) {
484
667
  try {
485
- await startServer(new require_src.StdioTransportHandler(await require_src.createServer(serverOptions)));
668
+ await startServer(new require_src.StdioTransportHandler(await require_src.createServer(serverOptions), createStdioSafeLogger()));
486
669
  } catch (error) {
487
670
  throw new Error(`Failed to start stdio transport: ${toErrorMessage$9(error)}`);
488
671
  }
@@ -494,20 +677,29 @@ async function startSseTransport(serverOptions, config) {
494
677
  throw new Error(`Failed to start SSE transport: ${toErrorMessage$9(error)}`);
495
678
  }
496
679
  }
497
- async function resolveStdioHttpEndpoint(config) {
498
- if (config.port !== void 0) return new URL(`http://${config.host ?? DEFAULT_HOST$1}:${config.port}${MCP_ENDPOINT_PATH}`);
680
+ async function resolveStdioHttpEndpoint(config, options, resolvedConfigPath) {
681
+ const repositoryPath = getRegistryRepositoryPath();
682
+ if (config.port !== void 0) return new URL(`http://${config.host ?? DEFAULT_HOST}:${config.port}${MCP_ENDPOINT_PATH}`);
499
683
  const result = await createPortRegistryService().getPort({
500
- repositoryPath: PORT_REGISTRY_REPOSITORY_PATH,
684
+ repositoryPath,
501
685
  serviceName: PORT_REGISTRY_SERVICE_HTTP,
502
686
  serviceType: PORT_REGISTRY_SERVICE_TYPE,
503
687
  environment: getRegistryEnvironment()
504
688
  });
505
- if (!result.success || !result.record) throw new Error(result.error || "No prestarted HTTP backend found for stdio-http transport");
506
- return new URL(`http://${config.host ?? result.record.host}:${result.record.port}${MCP_ENDPOINT_PATH}`);
689
+ if (result.success && result.record) return new URL(`http://${config.host ?? result.record.host}:${result.record.port}${MCP_ENDPOINT_PATH}`);
690
+ const runtime = await prestartHttpRuntime({
691
+ host: config.host ?? DEFAULT_HOST,
692
+ config: options.config || resolvedConfigPath,
693
+ cache: options.cache,
694
+ definitionsCache: options.definitionsCache,
695
+ clearDefinitionsCache: options.clearDefinitionsCache,
696
+ proxyMode: options.proxyMode
697
+ });
698
+ return new URL(`http://${runtime.host}:${runtime.port}${MCP_ENDPOINT_PATH}`);
507
699
  }
508
- async function startStdioHttpTransport(config) {
700
+ async function startStdioHttpTransport(config, options, resolvedConfigPath) {
509
701
  try {
510
- await startServer(new require_src.StdioHttpTransportHandler({ endpoint: await resolveStdioHttpEndpoint(config) }));
702
+ await startServer(new require_src.StdioHttpTransportHandler({ endpoint: await resolveStdioHttpEndpoint(config, options, resolvedConfigPath) }, createStdioSafeLogger()));
511
703
  } catch (error) {
512
704
  throw new Error(`Failed to start stdio-http transport: ${toErrorMessage$9(error)}`);
513
705
  }
@@ -526,7 +718,7 @@ async function startTransport(transportType, options, resolvedConfigPath, server
526
718
  await startSseTransport(serverOptions, createTransportConfig(options, require_src.TRANSPORT_MODE.SSE));
527
719
  return;
528
720
  }
529
- await startStdioHttpTransport(createTransportConfig(options, require_src.TRANSPORT_MODE.HTTP));
721
+ await startStdioHttpTransport(createTransportConfig(options, require_src.TRANSPORT_MODE.HTTP), options, resolvedConfigPath);
530
722
  } catch (error) {
531
723
  throw new Error(`Failed to start transport '${transportType}': ${toErrorMessage$9(error)}`);
532
724
  }
@@ -534,7 +726,7 @@ async function startTransport(transportType, options, resolvedConfigPath, server
534
726
  /**
535
727
  * MCP Serve command
536
728
  */
537
- const mcpServeCommand = new commander.Command("mcp-serve").description("Start MCP server with specified transport").option("-t, --type <type>", `Transport type: ${TRANSPORT_TYPE_STDIO}, ${TRANSPORT_TYPE_HTTP}, ${TRANSPORT_TYPE_SSE}, or ${TRANSPORT_TYPE_STDIO_HTTP}`, TRANSPORT_TYPE_STDIO).option("-p, --port <port>", "Port to listen on (http/sse) or backend port for stdio-http", (val) => parseInt(val, 10)).option("--host <host>", "Host to bind to (http/sse) or backend host for stdio-http", DEFAULT_HOST$1).option("-c, --config <path>", "Path to MCP server configuration file").option("--no-cache", "Disable configuration caching, always reload from config file").option("--definitions-cache <path>", "Path to prefetched tool/prompt/skill definitions cache file").option("--clear-definitions-cache", "Delete definitions cache before startup", false).option("--proxy-mode <mode>", "How mcp-proxy exposes downstream tools: meta, flat, or search", "meta").option("--id <id>", "Unique server identifier (overrides config file id, auto-generated if not provided)").action(async (options) => {
729
+ const mcpServeCommand = new commander.Command("mcp-serve").description("Start MCP server with specified transport").option("-t, --type <type>", `Transport type: ${TRANSPORT_TYPE_STDIO}, ${TRANSPORT_TYPE_HTTP}, ${TRANSPORT_TYPE_SSE}, or ${TRANSPORT_TYPE_STDIO_HTTP}`, TRANSPORT_TYPE_STDIO).option("-p, --port <port>", "Port to listen on (http/sse) or backend port for stdio-http", (val) => Number.parseInt(val, 10)).option("--host <host>", "Host to bind to (http/sse) or backend host for stdio-http", DEFAULT_HOST).option("-c, --config <path>", "Path to MCP server configuration file").option("--no-cache", "Disable configuration caching, always reload from config file").option("--definitions-cache <path>", "Path to prefetched tool/prompt/skill definitions cache file").option("--clear-definitions-cache", "Delete definitions cache before startup", false).option("--proxy-mode <mode>", "How mcp-proxy exposes downstream tools: meta, flat, or search", "meta").option("--id <id>", "Unique server identifier (overrides config file id, auto-generated if not provided)").action(async (options) => {
538
730
  try {
539
731
  const transportType = validateTransportType(options.type.toLowerCase());
540
732
  validateProxyMode(options.proxyMode);
@@ -550,156 +742,6 @@ const mcpServeCommand = new commander.Command("mcp-serve").description("Start MC
550
742
  }
551
743
  });
552
744
 
553
- //#endregion
554
- //#region src/commands/prestart-http.ts
555
- /**
556
- * Prestart HTTP Command
557
- *
558
- * Starts an mcp-proxy HTTP runtime in the background, waits until it is healthy,
559
- * and then exits so the runtime can be reused by other commands.
560
- */
561
- const WORKSPACE_MARKERS = [
562
- "pnpm-workspace.yaml",
563
- "nx.json",
564
- ".git"
565
- ];
566
- const DEFAULT_HOST = "localhost";
567
- const DEFAULT_TIMEOUT_MS = 12e4;
568
- const POLL_INTERVAL_MS = 250;
569
- function resolveWorkspaceRoot(startPath = process.cwd()) {
570
- let current = node_path.default.resolve(startPath);
571
- while (true) {
572
- for (const marker of WORKSPACE_MARKERS) if ((0, node_fs.existsSync)(node_path.default.join(current, marker))) return current;
573
- const parent = node_path.default.dirname(current);
574
- if (parent === current) return process.cwd();
575
- current = parent;
576
- }
577
- }
578
- function resolveSiblingRegistryPath(registryPath, fileName) {
579
- if (!registryPath) return;
580
- const resolved = node_path.default.resolve(registryPath);
581
- if (node_path.default.extname(resolved) === ".json") return node_path.default.join(node_path.default.dirname(resolved), fileName);
582
- return node_path.default.join(resolved, fileName);
583
- }
584
- function buildCliCandidates() {
585
- const __filename$1 = (0, node_url.fileURLToPath)(require("url").pathToFileURL(__filename).href);
586
- const __dirname$1 = node_path.default.dirname(__filename$1);
587
- const distCandidates = [
588
- node_path.default.resolve(__dirname$1, "cli.mjs"),
589
- node_path.default.resolve(__dirname$1, "..", "dist", "cli.mjs"),
590
- node_path.default.resolve(__dirname$1, "..", "..", "dist", "cli.mjs")
591
- ];
592
- const srcCandidates = [node_path.default.resolve(__dirname$1, "..", "cli.ts"), node_path.default.resolve(__dirname$1, "..", "..", "src", "cli.ts")];
593
- for (const candidate of distCandidates) if ((0, node_fs.existsSync)(candidate)) return {
594
- command: process.execPath,
595
- args: [candidate]
596
- };
597
- for (const candidate of srcCandidates) if ((0, node_fs.existsSync)(candidate)) return {
598
- command: process.execPath,
599
- args: [
600
- "--import",
601
- "tsx",
602
- candidate
603
- ]
604
- };
605
- throw new Error("Unable to locate mcp-proxy CLI entrypoint");
606
- }
607
- function parseTimeoutMs(value) {
608
- if (!value) return DEFAULT_TIMEOUT_MS;
609
- const parsed = Number.parseInt(value, 10);
610
- if (!Number.isInteger(parsed) || parsed <= 0) throw new Error(`Invalid timeout value: ${value}`);
611
- return parsed;
612
- }
613
- async function waitForFile(filePath, timeoutMs) {
614
- const deadline = Date.now() + timeoutMs;
615
- while (Date.now() < deadline) {
616
- try {
617
- await (0, node_fs_promises.access)(filePath);
618
- return;
619
- } catch {}
620
- await new Promise((resolve$2) => setTimeout(resolve$2, POLL_INTERVAL_MS));
621
- }
622
- throw new Error(`Timed out waiting for runtime state file: ${filePath}`);
623
- }
624
- async function waitForHealthyRuntime(serverId, timeoutMs) {
625
- const runtimeStateService = new require_src.RuntimeStateService();
626
- const deadline = Date.now() + timeoutMs;
627
- while (Date.now() < deadline) {
628
- const record = await runtimeStateService.read(serverId);
629
- if (record) {
630
- const healthUrl = `http://${record.host}:${record.port}/health`;
631
- try {
632
- const response = await fetch(healthUrl);
633
- if (response.ok) {
634
- const payload = await response.json().catch(() => null);
635
- if (!payload || payload.transport === "http") return {
636
- host: record.host,
637
- port: record.port
638
- };
639
- }
640
- } catch {}
641
- }
642
- await new Promise((resolve$2) => setTimeout(resolve$2, POLL_INTERVAL_MS));
643
- }
644
- throw new Error(`Timed out waiting for HTTP runtime '${serverId}' to become healthy`);
645
- }
646
- function spawnBackgroundRuntime(args, env) {
647
- const { command, args: baseArgs } = buildCliCandidates();
648
- const child = (0, node_child_process.spawn)(command, [...baseArgs, ...args], {
649
- detached: true,
650
- stdio: "ignore",
651
- env
652
- });
653
- child.unref();
654
- return child;
655
- }
656
- const prestartHttpCommand = new commander.Command("prestart-http").description("Start an mcp-proxy HTTP runtime in the background and wait until it is healthy").option("--id <id>", "Server identifier to assign to the runtime").option("--host <host>", "Host to bind to", DEFAULT_HOST).option("-p, --port <port>", "Preferred HTTP port for the runtime", (value) => Number.parseInt(value, 10)).option("-c, --config <path>", "Path to MCP server configuration file").option("--no-cache", "Disable configuration caching, always reload from config file").option("--definitions-cache <path>", "Path to prefetched tool/prompt/skill definitions cache file").option("--clear-definitions-cache", "Delete definitions cache before startup", false).option("--proxy-mode <mode>", "How mcp-proxy exposes downstream tools: meta, flat, or search", "meta").option("--registry-path <path>", "Custom registry path or directory for service discovery").option("--registry-dir <path>", "Custom registry directory for service discovery").option("--timeout-ms <ms>", "How long to wait for the runtime to become healthy", String(DEFAULT_TIMEOUT_MS)).action(async (options) => {
657
- const serverId = options.id || require_src.generateServerId();
658
- const timeoutMs = parseTimeoutMs(options.timeoutMs);
659
- const registryPath = options.registryPath || options.registryDir;
660
- new require_src.RuntimeStateService();
661
- const workspaceRoot = resolveWorkspaceRoot(process.cwd());
662
- const childEnv = {
663
- ...process.env,
664
- ...registryPath ? {
665
- PORT_REGISTRY_PATH: registryPath,
666
- PROCESS_REGISTRY_PATH: resolveSiblingRegistryPath(registryPath, "processes.json")
667
- } : {}
668
- };
669
- const child = spawnBackgroundRuntime([
670
- "mcp-serve",
671
- "--type",
672
- "http",
673
- "--id",
674
- serverId,
675
- "--host",
676
- options.host || DEFAULT_HOST,
677
- ...options.port !== void 0 ? ["--port", String(options.port)] : [],
678
- ...options.config ? ["--config", options.config] : [],
679
- ...options.cache === false ? ["--no-cache"] : [],
680
- ...options.definitionsCache ? ["--definitions-cache", options.definitionsCache] : [],
681
- ...options.clearDefinitionsCache ? ["--clear-definitions-cache"] : [],
682
- "--proxy-mode",
683
- options.proxyMode
684
- ], childEnv);
685
- const childExit = new Promise((_, reject) => {
686
- child.once("exit", (code, signal) => {
687
- reject(/* @__PURE__ */ new Error(`Background runtime exited before becoming healthy (code=${code ?? "null"}, signal=${signal ?? "null"})`));
688
- });
689
- });
690
- const runtimeFile = node_path.default.join(require_src.RuntimeStateService.getDefaultRuntimeDir(), `${serverId}.runtime.json`);
691
- try {
692
- await Promise.race([waitForFile(runtimeFile, timeoutMs), childExit]);
693
- const { host, port } = await Promise.race([waitForHealthyRuntime(serverId, timeoutMs), childExit]);
694
- process.stdout.write(`mcp-proxy HTTP runtime ready at http://${host}:${port} (${serverId})\n`);
695
- process.stdout.write(`runtimeId=${serverId}\n`);
696
- process.stdout.write(`runtimeUrl=http://${host}:${port}\n`);
697
- process.stdout.write(`workspaceRoot=${workspaceRoot}\n`);
698
- } catch (error) {
699
- throw new Error(`Failed to prestart HTTP runtime '${serverId}': ${error instanceof Error ? error.message : String(error)}`);
700
- }
701
- });
702
-
703
745
  //#endregion
704
746
  //#region src/commands/bootstrap.ts
705
747
  function toErrorMessage$8(error) {
package/dist/cli.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { D as findConfigFile, E as generateServerId, T as DefinitionsCacheService, d as version, f as StdioHttpTransportHandler, h as HttpTransportHandler, m as SseTransportHandler, n as createServer, o as createProxyIoCContainer, p as StdioTransportHandler, r as createSessionServer, t as TRANSPORT_MODE, u as initializeSharedServices, v as RuntimeStateService } from "./src-0OJqEpGA.mjs";
2
+ import { D as findConfigFile, E as generateServerId, T as DefinitionsCacheService, d as version, f as StdioHttpTransportHandler, h as HttpTransportHandler, m as SseTransportHandler, n as createServer, o as createProxyIoCContainer, p as StdioTransportHandler, r as createSessionServer, t as TRANSPORT_MODE, u as initializeSharedServices, v as RuntimeStateService } from "./src-DCIv5S_2.mjs";
3
3
  import { randomUUID } from "node:crypto";
4
4
  import { access, writeFile } from "node:fs/promises";
5
5
  import path, { join, resolve } from "node:path";
@@ -7,8 +7,8 @@ import { constants, existsSync } from "node:fs";
7
7
  import { Liquid } from "liquidjs";
8
8
  import { spawn } from "node:child_process";
9
9
  import { Command } from "commander";
10
- import { ProcessRegistryService } from "@agimon-ai/foundation-process-registry";
11
10
  import { DEFAULT_PORT_RANGE, PortRegistryService } from "@agimon-ai/foundation-port-registry";
11
+ import { ProcessRegistryService } from "@agimon-ai/foundation-process-registry";
12
12
  import { fileURLToPath } from "node:url";
13
13
 
14
14
  //#region src/utils/output.ts
@@ -112,6 +112,169 @@ const initCommand = new Command("init").description("Initialize MCP configuratio
112
112
  }
113
113
  });
114
114
 
115
+ //#endregion
116
+ //#region src/commands/prestart-http.ts
117
+ /**
118
+ * Prestart HTTP Command
119
+ *
120
+ * Starts an mcp-proxy HTTP runtime in the background, waits until it is healthy,
121
+ * and then exits so the runtime can be reused by other commands.
122
+ */
123
+ const WORKSPACE_MARKERS = [
124
+ "pnpm-workspace.yaml",
125
+ "nx.json",
126
+ ".git"
127
+ ];
128
+ const DEFAULT_HOST$1 = "localhost";
129
+ const DEFAULT_TIMEOUT_MS = 12e4;
130
+ const POLL_INTERVAL_MS = 250;
131
+ function resolveWorkspaceRoot(startPath = process.env.PROJECT_PATH || process.cwd()) {
132
+ let current = path.resolve(startPath);
133
+ while (true) {
134
+ for (const marker of WORKSPACE_MARKERS) if (existsSync(path.join(current, marker))) return current;
135
+ const parent = path.dirname(current);
136
+ if (parent === current) return process.cwd();
137
+ current = parent;
138
+ }
139
+ }
140
+ function resolveSiblingRegistryPath(registryPath, fileName) {
141
+ if (!registryPath) return;
142
+ const resolved = path.resolve(registryPath);
143
+ if (path.extname(resolved) === ".json") return path.join(path.dirname(resolved), fileName);
144
+ return path.join(resolved, fileName);
145
+ }
146
+ function buildCliCandidates() {
147
+ const __filename = fileURLToPath(import.meta.url);
148
+ const __dirname = path.dirname(__filename);
149
+ const distCandidates = [
150
+ path.resolve(__dirname, "cli.mjs"),
151
+ path.resolve(__dirname, "..", "dist", "cli.mjs"),
152
+ path.resolve(__dirname, "..", "..", "dist", "cli.mjs")
153
+ ];
154
+ const srcCandidates = [path.resolve(__dirname, "..", "cli.ts"), path.resolve(__dirname, "..", "..", "src", "cli.ts")];
155
+ for (const candidate of distCandidates) if (existsSync(candidate)) return {
156
+ command: process.execPath,
157
+ args: [candidate]
158
+ };
159
+ for (const candidate of srcCandidates) if (existsSync(candidate)) return {
160
+ command: process.execPath,
161
+ args: [
162
+ "--import",
163
+ "tsx",
164
+ candidate
165
+ ]
166
+ };
167
+ throw new Error("Unable to locate mcp-proxy CLI entrypoint");
168
+ }
169
+ function parseTimeoutMs(value) {
170
+ if (!value) return DEFAULT_TIMEOUT_MS;
171
+ const parsed = Number.parseInt(value, 10);
172
+ if (!Number.isInteger(parsed) || parsed <= 0) throw new Error(`Invalid timeout value: ${value}`);
173
+ return parsed;
174
+ }
175
+ async function waitForFile(filePath, timeoutMs) {
176
+ const deadline = Date.now() + timeoutMs;
177
+ while (Date.now() < deadline) {
178
+ try {
179
+ await access(filePath);
180
+ return;
181
+ } catch {}
182
+ await new Promise((resolve$1) => setTimeout(resolve$1, POLL_INTERVAL_MS));
183
+ }
184
+ throw new Error(`Timed out waiting for runtime state file: ${filePath}`);
185
+ }
186
+ async function waitForHealthyRuntime(serverId, timeoutMs) {
187
+ const runtimeStateService = new RuntimeStateService();
188
+ const deadline = Date.now() + timeoutMs;
189
+ while (Date.now() < deadline) {
190
+ const record = await runtimeStateService.read(serverId);
191
+ if (record) {
192
+ const healthUrl = `http://${record.host}:${record.port}/health`;
193
+ try {
194
+ const response = await fetch(healthUrl);
195
+ if (response.ok) {
196
+ const payload = await response.json().catch(() => null);
197
+ if (!payload || payload.transport === "http") return {
198
+ host: record.host,
199
+ port: record.port
200
+ };
201
+ }
202
+ } catch {}
203
+ }
204
+ await new Promise((resolve$1) => setTimeout(resolve$1, POLL_INTERVAL_MS));
205
+ }
206
+ throw new Error(`Timed out waiting for HTTP runtime '${serverId}' to become healthy`);
207
+ }
208
+ function spawnBackgroundRuntime(args, env, cwd) {
209
+ const { command, args: baseArgs } = buildCliCandidates();
210
+ const child = spawn(command, [...baseArgs, ...args], {
211
+ detached: true,
212
+ cwd,
213
+ stdio: "ignore",
214
+ env
215
+ });
216
+ child.unref();
217
+ return child;
218
+ }
219
+ async function prestartHttpRuntime(options) {
220
+ const serverId = options.id || generateServerId();
221
+ const timeoutMs = parseTimeoutMs(options.timeoutMs);
222
+ const registryPath = options.registryPath || options.registryDir;
223
+ const workspaceRoot = resolveWorkspaceRoot();
224
+ const childEnv = {
225
+ ...process.env,
226
+ ...registryPath ? {
227
+ PORT_REGISTRY_PATH: registryPath,
228
+ PROCESS_REGISTRY_PATH: resolveSiblingRegistryPath(registryPath, "processes.json")
229
+ } : {}
230
+ };
231
+ const child = spawnBackgroundRuntime([
232
+ "mcp-serve",
233
+ "--type",
234
+ "http",
235
+ "--id",
236
+ serverId,
237
+ "--host",
238
+ options.host || DEFAULT_HOST$1,
239
+ ...options.port !== void 0 ? ["--port", String(options.port)] : [],
240
+ ...options.config ? ["--config", options.config] : [],
241
+ ...options.cache === false ? ["--no-cache"] : [],
242
+ ...options.definitionsCache ? ["--definitions-cache", options.definitionsCache] : [],
243
+ ...options.clearDefinitionsCache ? ["--clear-definitions-cache"] : [],
244
+ "--proxy-mode",
245
+ options.proxyMode
246
+ ], childEnv, workspaceRoot);
247
+ const childExit = new Promise((_, reject) => {
248
+ child.once("exit", (code, signal) => {
249
+ reject(/* @__PURE__ */ new Error(`Background runtime exited before becoming healthy (code=${code ?? "null"}, signal=${signal ?? "null"})`));
250
+ });
251
+ });
252
+ const runtimeFile = path.join(RuntimeStateService.getDefaultRuntimeDir(), `${serverId}.runtime.json`);
253
+ try {
254
+ await Promise.race([waitForFile(runtimeFile, timeoutMs), childExit]);
255
+ const { host, port } = await Promise.race([waitForHealthyRuntime(serverId, timeoutMs), childExit]);
256
+ return {
257
+ host,
258
+ port,
259
+ serverId,
260
+ workspaceRoot
261
+ };
262
+ } catch (error) {
263
+ throw new Error(`Failed to prestart HTTP runtime '${serverId}': ${error instanceof Error ? error.message : String(error)}`);
264
+ }
265
+ }
266
+ const prestartHttpCommand = new Command("prestart-http").description("Start an mcp-proxy HTTP runtime in the background and wait until it is healthy").option("--id <id>", "Server identifier to assign to the runtime").option("--host <host>", "Host to bind to", DEFAULT_HOST$1).option("-p, --port <port>", "Preferred HTTP port for the runtime", (value) => Number.parseInt(value, 10)).option("-c, --config <path>", "Path to MCP server configuration file").option("--no-cache", "Disable configuration caching, always reload from config file").option("--definitions-cache <path>", "Path to prefetched tool/prompt/skill definitions cache file").option("--clear-definitions-cache", "Delete definitions cache before startup", false).option("--proxy-mode <mode>", "How mcp-proxy exposes downstream tools: meta, flat, or search", "meta").option("--registry-path <path>", "Custom registry path or directory for service discovery").option("--registry-dir <path>", "Custom registry directory for service discovery").option("--timeout-ms <ms>", "How long to wait for the runtime to become healthy", String(DEFAULT_TIMEOUT_MS)).action(async (options) => {
267
+ try {
268
+ const { host, port, serverId, workspaceRoot } = await prestartHttpRuntime(options);
269
+ process.stdout.write(`mcp-proxy HTTP runtime ready at http://${host}:${port} (${serverId})\n`);
270
+ process.stdout.write(`runtimeId=${serverId}\n`);
271
+ process.stdout.write(`runtimeUrl=http://${host}:${port}\n`);
272
+ process.stdout.write(`workspaceRoot=${workspaceRoot}\n`);
273
+ } catch (error) {
274
+ throw new Error(`Failed to prestart HTTP runtime '${options.id || "generated-server-id"}': ${error instanceof Error ? error.message : String(error)}`);
275
+ }
276
+ });
277
+
115
278
  //#endregion
116
279
  //#region src/commands/mcp-serve.ts
117
280
  /**
@@ -140,7 +303,7 @@ const CONFIG_FILE_NAMES = [
140
303
  "mcp-config.json"
141
304
  ];
142
305
  const MCP_ENDPOINT_PATH = "/mcp";
143
- const DEFAULT_HOST$1 = "localhost";
306
+ const DEFAULT_HOST = "localhost";
144
307
  const TRANSPORT_TYPE_STDIO = "stdio";
145
308
  const TRANSPORT_TYPE_HTTP = "http";
146
309
  const TRANSPORT_TYPE_SSE = "sse";
@@ -148,10 +311,14 @@ const TRANSPORT_TYPE_STDIO_HTTP = "stdio-http";
148
311
  const RUNTIME_TRANSPORT = TRANSPORT_TYPE_HTTP;
149
312
  const PORT_REGISTRY_SERVICE_HTTP = "mcp-proxy-http";
150
313
  const PORT_REGISTRY_SERVICE_TYPE = "service";
151
- const PORT_REGISTRY_REPOSITORY_PATH = process.cwd();
314
+ function getWorkspaceRoot() {
315
+ return resolveWorkspaceRoot();
316
+ }
152
317
  const PROCESS_REGISTRY_SERVICE_HTTP = "mcp-proxy-http";
153
318
  const PROCESS_REGISTRY_SERVICE_TYPE = "service";
154
- const PROCESS_REGISTRY_REPOSITORY_PATH = process.cwd();
319
+ function getRegistryRepositoryPath() {
320
+ return getWorkspaceRoot();
321
+ }
155
322
  function toErrorMessage$9(error) {
156
323
  return error instanceof Error ? error.message : String(error);
157
324
  }
@@ -212,7 +379,23 @@ function createTransportConfig(options, mode) {
212
379
  return {
213
380
  mode,
214
381
  port: options.port ?? (Number.isFinite(envPort) ? envPort : void 0),
215
- host: options.host || process.env.MCP_HOST || DEFAULT_HOST$1
382
+ host: options.host || process.env.MCP_HOST || DEFAULT_HOST
383
+ };
384
+ }
385
+ function createStdioSafeLogger() {
386
+ const logToStderr = (message, data) => {
387
+ if (data === void 0) {
388
+ console.error(message);
389
+ return;
390
+ }
391
+ console.error(message, data);
392
+ };
393
+ return {
394
+ trace: logToStderr,
395
+ debug: logToStderr,
396
+ info: logToStderr,
397
+ warn: logToStderr,
398
+ error: logToStderr
216
399
  };
217
400
  }
218
401
  function createServerOptions(options, resolvedConfigPath, serverId) {
@@ -233,7 +416,7 @@ function formatStartError(type, host, port, error) {
233
416
  function createRuntimeRecord(serverId, config, port, shutdownToken, configPath) {
234
417
  return {
235
418
  serverId,
236
- host: config.host ?? DEFAULT_HOST$1,
419
+ host: config.host ?? DEFAULT_HOST,
237
420
  port,
238
421
  transport: RUNTIME_TRANSPORT,
239
422
  shutdownToken,
@@ -257,7 +440,7 @@ async function createPortRegistryLease(serviceName, host, preferredPort, serverI
257
440
  } : DEFAULT_PORT_RANGE) {
258
441
  const portRegistry = createPortRegistryService();
259
442
  const result = await portRegistry.reservePort({
260
- repositoryPath: PORT_REGISTRY_REPOSITORY_PATH,
443
+ repositoryPath: getRegistryRepositoryPath(),
261
444
  serviceName,
262
445
  serviceType: PORT_REGISTRY_SERVICE_TYPE,
263
446
  environment: getRegistryEnvironment(),
@@ -283,7 +466,7 @@ async function createPortRegistryLease(serviceName, host, preferredPort, serverI
283
466
  if (released) return;
284
467
  released = true;
285
468
  const releaseResult = await portRegistry.releasePort({
286
- repositoryPath: PORT_REGISTRY_REPOSITORY_PATH,
469
+ repositoryPath: getRegistryRepositoryPath(),
287
470
  serviceName,
288
471
  serviceType: PORT_REGISTRY_SERVICE_TYPE,
289
472
  pid: process.pid,
@@ -297,7 +480,7 @@ async function createPortRegistryLease(serviceName, host, preferredPort, serverI
297
480
  async function createProcessRegistryLease(serviceName, host, port, serverId, transport, configPath) {
298
481
  const processRegistry = createProcessRegistryService();
299
482
  const result = await processRegistry.registerProcess({
300
- repositoryPath: PROCESS_REGISTRY_REPOSITORY_PATH,
483
+ repositoryPath: getRegistryRepositoryPath(),
301
484
  serviceName,
302
485
  serviceType: PROCESS_REGISTRY_SERVICE_TYPE,
303
486
  environment: getRegistryEnvironment(),
@@ -318,7 +501,7 @@ async function createProcessRegistryLease(serviceName, host, port, serverId, tra
318
501
  if (released) return;
319
502
  released = true;
320
503
  const releaseResult = await processRegistry.releaseProcess({
321
- repositoryPath: PROCESS_REGISTRY_REPOSITORY_PATH,
504
+ repositoryPath: getRegistryRepositoryPath(),
322
505
  serviceName,
323
506
  serviceType: PROCESS_REGISTRY_SERVICE_TYPE,
324
507
  pid: process.pid,
@@ -419,13 +602,13 @@ async function createAndStartHttpRuntime(serverOptions, config, resolvedConfigPa
419
602
  min: requestedPort,
420
603
  max: requestedPort
421
604
  } : DEFAULT_PORT_RANGE;
422
- const portLease = await createPortRegistryLease(PORT_REGISTRY_SERVICE_HTTP, config.host ?? DEFAULT_HOST$1, requestedPort, runtimeServerId, TRANSPORT_TYPE_HTTP, resolvedConfigPath, portRange);
605
+ const portLease = await createPortRegistryLease(PORT_REGISTRY_SERVICE_HTTP, config.host ?? DEFAULT_HOST, requestedPort, runtimeServerId, TRANSPORT_TYPE_HTTP, resolvedConfigPath, portRange);
423
606
  const runtimePort = portLease.port;
424
607
  const runtimeConfig = {
425
608
  ...config,
426
609
  port: runtimePort
427
610
  };
428
- const processLease = await createProcessRegistryLease(PROCESS_REGISTRY_SERVICE_HTTP, runtimeConfig.host ?? DEFAULT_HOST$1, runtimePort, runtimeServerId, TRANSPORT_TYPE_HTTP, resolvedConfigPath);
611
+ const processLease = await createProcessRegistryLease(PROCESS_REGISTRY_SERVICE_HTTP, runtimeConfig.host ?? DEFAULT_HOST, runtimePort, runtimeServerId, TRANSPORT_TYPE_HTTP, resolvedConfigPath);
429
612
  let releasePort = async () => {
430
613
  await releasePortLease(portLease ?? null);
431
614
  releasePort = async () => void 0;
@@ -481,7 +664,7 @@ async function createAndStartHttpRuntime(serverOptions, config, resolvedConfigPa
481
664
  }
482
665
  async function startStdioTransport(serverOptions) {
483
666
  try {
484
- await startServer(new StdioTransportHandler(await createServer(serverOptions)));
667
+ await startServer(new StdioTransportHandler(await createServer(serverOptions), createStdioSafeLogger()));
485
668
  } catch (error) {
486
669
  throw new Error(`Failed to start stdio transport: ${toErrorMessage$9(error)}`);
487
670
  }
@@ -493,20 +676,29 @@ async function startSseTransport(serverOptions, config) {
493
676
  throw new Error(`Failed to start SSE transport: ${toErrorMessage$9(error)}`);
494
677
  }
495
678
  }
496
- async function resolveStdioHttpEndpoint(config) {
497
- if (config.port !== void 0) return new URL(`http://${config.host ?? DEFAULT_HOST$1}:${config.port}${MCP_ENDPOINT_PATH}`);
679
+ async function resolveStdioHttpEndpoint(config, options, resolvedConfigPath) {
680
+ const repositoryPath = getRegistryRepositoryPath();
681
+ if (config.port !== void 0) return new URL(`http://${config.host ?? DEFAULT_HOST}:${config.port}${MCP_ENDPOINT_PATH}`);
498
682
  const result = await createPortRegistryService().getPort({
499
- repositoryPath: PORT_REGISTRY_REPOSITORY_PATH,
683
+ repositoryPath,
500
684
  serviceName: PORT_REGISTRY_SERVICE_HTTP,
501
685
  serviceType: PORT_REGISTRY_SERVICE_TYPE,
502
686
  environment: getRegistryEnvironment()
503
687
  });
504
- if (!result.success || !result.record) throw new Error(result.error || "No prestarted HTTP backend found for stdio-http transport");
505
- return new URL(`http://${config.host ?? result.record.host}:${result.record.port}${MCP_ENDPOINT_PATH}`);
688
+ if (result.success && result.record) return new URL(`http://${config.host ?? result.record.host}:${result.record.port}${MCP_ENDPOINT_PATH}`);
689
+ const runtime = await prestartHttpRuntime({
690
+ host: config.host ?? DEFAULT_HOST,
691
+ config: options.config || resolvedConfigPath,
692
+ cache: options.cache,
693
+ definitionsCache: options.definitionsCache,
694
+ clearDefinitionsCache: options.clearDefinitionsCache,
695
+ proxyMode: options.proxyMode
696
+ });
697
+ return new URL(`http://${runtime.host}:${runtime.port}${MCP_ENDPOINT_PATH}`);
506
698
  }
507
- async function startStdioHttpTransport(config) {
699
+ async function startStdioHttpTransport(config, options, resolvedConfigPath) {
508
700
  try {
509
- await startServer(new StdioHttpTransportHandler({ endpoint: await resolveStdioHttpEndpoint(config) }));
701
+ await startServer(new StdioHttpTransportHandler({ endpoint: await resolveStdioHttpEndpoint(config, options, resolvedConfigPath) }, createStdioSafeLogger()));
510
702
  } catch (error) {
511
703
  throw new Error(`Failed to start stdio-http transport: ${toErrorMessage$9(error)}`);
512
704
  }
@@ -525,7 +717,7 @@ async function startTransport(transportType, options, resolvedConfigPath, server
525
717
  await startSseTransport(serverOptions, createTransportConfig(options, TRANSPORT_MODE.SSE));
526
718
  return;
527
719
  }
528
- await startStdioHttpTransport(createTransportConfig(options, TRANSPORT_MODE.HTTP));
720
+ await startStdioHttpTransport(createTransportConfig(options, TRANSPORT_MODE.HTTP), options, resolvedConfigPath);
529
721
  } catch (error) {
530
722
  throw new Error(`Failed to start transport '${transportType}': ${toErrorMessage$9(error)}`);
531
723
  }
@@ -533,7 +725,7 @@ async function startTransport(transportType, options, resolvedConfigPath, server
533
725
  /**
534
726
  * MCP Serve command
535
727
  */
536
- const mcpServeCommand = new Command("mcp-serve").description("Start MCP server with specified transport").option("-t, --type <type>", `Transport type: ${TRANSPORT_TYPE_STDIO}, ${TRANSPORT_TYPE_HTTP}, ${TRANSPORT_TYPE_SSE}, or ${TRANSPORT_TYPE_STDIO_HTTP}`, TRANSPORT_TYPE_STDIO).option("-p, --port <port>", "Port to listen on (http/sse) or backend port for stdio-http", (val) => parseInt(val, 10)).option("--host <host>", "Host to bind to (http/sse) or backend host for stdio-http", DEFAULT_HOST$1).option("-c, --config <path>", "Path to MCP server configuration file").option("--no-cache", "Disable configuration caching, always reload from config file").option("--definitions-cache <path>", "Path to prefetched tool/prompt/skill definitions cache file").option("--clear-definitions-cache", "Delete definitions cache before startup", false).option("--proxy-mode <mode>", "How mcp-proxy exposes downstream tools: meta, flat, or search", "meta").option("--id <id>", "Unique server identifier (overrides config file id, auto-generated if not provided)").action(async (options) => {
728
+ const mcpServeCommand = new Command("mcp-serve").description("Start MCP server with specified transport").option("-t, --type <type>", `Transport type: ${TRANSPORT_TYPE_STDIO}, ${TRANSPORT_TYPE_HTTP}, ${TRANSPORT_TYPE_SSE}, or ${TRANSPORT_TYPE_STDIO_HTTP}`, TRANSPORT_TYPE_STDIO).option("-p, --port <port>", "Port to listen on (http/sse) or backend port for stdio-http", (val) => Number.parseInt(val, 10)).option("--host <host>", "Host to bind to (http/sse) or backend host for stdio-http", DEFAULT_HOST).option("-c, --config <path>", "Path to MCP server configuration file").option("--no-cache", "Disable configuration caching, always reload from config file").option("--definitions-cache <path>", "Path to prefetched tool/prompt/skill definitions cache file").option("--clear-definitions-cache", "Delete definitions cache before startup", false).option("--proxy-mode <mode>", "How mcp-proxy exposes downstream tools: meta, flat, or search", "meta").option("--id <id>", "Unique server identifier (overrides config file id, auto-generated if not provided)").action(async (options) => {
537
729
  try {
538
730
  const transportType = validateTransportType(options.type.toLowerCase());
539
731
  validateProxyMode(options.proxyMode);
@@ -549,156 +741,6 @@ const mcpServeCommand = new Command("mcp-serve").description("Start MCP server w
549
741
  }
550
742
  });
551
743
 
552
- //#endregion
553
- //#region src/commands/prestart-http.ts
554
- /**
555
- * Prestart HTTP Command
556
- *
557
- * Starts an mcp-proxy HTTP runtime in the background, waits until it is healthy,
558
- * and then exits so the runtime can be reused by other commands.
559
- */
560
- const WORKSPACE_MARKERS = [
561
- "pnpm-workspace.yaml",
562
- "nx.json",
563
- ".git"
564
- ];
565
- const DEFAULT_HOST = "localhost";
566
- const DEFAULT_TIMEOUT_MS = 12e4;
567
- const POLL_INTERVAL_MS = 250;
568
- function resolveWorkspaceRoot(startPath = process.cwd()) {
569
- let current = path.resolve(startPath);
570
- while (true) {
571
- for (const marker of WORKSPACE_MARKERS) if (existsSync(path.join(current, marker))) return current;
572
- const parent = path.dirname(current);
573
- if (parent === current) return process.cwd();
574
- current = parent;
575
- }
576
- }
577
- function resolveSiblingRegistryPath(registryPath, fileName) {
578
- if (!registryPath) return;
579
- const resolved = path.resolve(registryPath);
580
- if (path.extname(resolved) === ".json") return path.join(path.dirname(resolved), fileName);
581
- return path.join(resolved, fileName);
582
- }
583
- function buildCliCandidates() {
584
- const __filename = fileURLToPath(import.meta.url);
585
- const __dirname = path.dirname(__filename);
586
- const distCandidates = [
587
- path.resolve(__dirname, "cli.mjs"),
588
- path.resolve(__dirname, "..", "dist", "cli.mjs"),
589
- path.resolve(__dirname, "..", "..", "dist", "cli.mjs")
590
- ];
591
- const srcCandidates = [path.resolve(__dirname, "..", "cli.ts"), path.resolve(__dirname, "..", "..", "src", "cli.ts")];
592
- for (const candidate of distCandidates) if (existsSync(candidate)) return {
593
- command: process.execPath,
594
- args: [candidate]
595
- };
596
- for (const candidate of srcCandidates) if (existsSync(candidate)) return {
597
- command: process.execPath,
598
- args: [
599
- "--import",
600
- "tsx",
601
- candidate
602
- ]
603
- };
604
- throw new Error("Unable to locate mcp-proxy CLI entrypoint");
605
- }
606
- function parseTimeoutMs(value) {
607
- if (!value) return DEFAULT_TIMEOUT_MS;
608
- const parsed = Number.parseInt(value, 10);
609
- if (!Number.isInteger(parsed) || parsed <= 0) throw new Error(`Invalid timeout value: ${value}`);
610
- return parsed;
611
- }
612
- async function waitForFile(filePath, timeoutMs) {
613
- const deadline = Date.now() + timeoutMs;
614
- while (Date.now() < deadline) {
615
- try {
616
- await access(filePath);
617
- return;
618
- } catch {}
619
- await new Promise((resolve$1) => setTimeout(resolve$1, POLL_INTERVAL_MS));
620
- }
621
- throw new Error(`Timed out waiting for runtime state file: ${filePath}`);
622
- }
623
- async function waitForHealthyRuntime(serverId, timeoutMs) {
624
- const runtimeStateService = new RuntimeStateService();
625
- const deadline = Date.now() + timeoutMs;
626
- while (Date.now() < deadline) {
627
- const record = await runtimeStateService.read(serverId);
628
- if (record) {
629
- const healthUrl = `http://${record.host}:${record.port}/health`;
630
- try {
631
- const response = await fetch(healthUrl);
632
- if (response.ok) {
633
- const payload = await response.json().catch(() => null);
634
- if (!payload || payload.transport === "http") return {
635
- host: record.host,
636
- port: record.port
637
- };
638
- }
639
- } catch {}
640
- }
641
- await new Promise((resolve$1) => setTimeout(resolve$1, POLL_INTERVAL_MS));
642
- }
643
- throw new Error(`Timed out waiting for HTTP runtime '${serverId}' to become healthy`);
644
- }
645
- function spawnBackgroundRuntime(args, env) {
646
- const { command, args: baseArgs } = buildCliCandidates();
647
- const child = spawn(command, [...baseArgs, ...args], {
648
- detached: true,
649
- stdio: "ignore",
650
- env
651
- });
652
- child.unref();
653
- return child;
654
- }
655
- const prestartHttpCommand = new Command("prestart-http").description("Start an mcp-proxy HTTP runtime in the background and wait until it is healthy").option("--id <id>", "Server identifier to assign to the runtime").option("--host <host>", "Host to bind to", DEFAULT_HOST).option("-p, --port <port>", "Preferred HTTP port for the runtime", (value) => Number.parseInt(value, 10)).option("-c, --config <path>", "Path to MCP server configuration file").option("--no-cache", "Disable configuration caching, always reload from config file").option("--definitions-cache <path>", "Path to prefetched tool/prompt/skill definitions cache file").option("--clear-definitions-cache", "Delete definitions cache before startup", false).option("--proxy-mode <mode>", "How mcp-proxy exposes downstream tools: meta, flat, or search", "meta").option("--registry-path <path>", "Custom registry path or directory for service discovery").option("--registry-dir <path>", "Custom registry directory for service discovery").option("--timeout-ms <ms>", "How long to wait for the runtime to become healthy", String(DEFAULT_TIMEOUT_MS)).action(async (options) => {
656
- const serverId = options.id || generateServerId();
657
- const timeoutMs = parseTimeoutMs(options.timeoutMs);
658
- const registryPath = options.registryPath || options.registryDir;
659
- new RuntimeStateService();
660
- const workspaceRoot = resolveWorkspaceRoot(process.cwd());
661
- const childEnv = {
662
- ...process.env,
663
- ...registryPath ? {
664
- PORT_REGISTRY_PATH: registryPath,
665
- PROCESS_REGISTRY_PATH: resolveSiblingRegistryPath(registryPath, "processes.json")
666
- } : {}
667
- };
668
- const child = spawnBackgroundRuntime([
669
- "mcp-serve",
670
- "--type",
671
- "http",
672
- "--id",
673
- serverId,
674
- "--host",
675
- options.host || DEFAULT_HOST,
676
- ...options.port !== void 0 ? ["--port", String(options.port)] : [],
677
- ...options.config ? ["--config", options.config] : [],
678
- ...options.cache === false ? ["--no-cache"] : [],
679
- ...options.definitionsCache ? ["--definitions-cache", options.definitionsCache] : [],
680
- ...options.clearDefinitionsCache ? ["--clear-definitions-cache"] : [],
681
- "--proxy-mode",
682
- options.proxyMode
683
- ], childEnv);
684
- const childExit = new Promise((_, reject) => {
685
- child.once("exit", (code, signal) => {
686
- reject(/* @__PURE__ */ new Error(`Background runtime exited before becoming healthy (code=${code ?? "null"}, signal=${signal ?? "null"})`));
687
- });
688
- });
689
- const runtimeFile = path.join(RuntimeStateService.getDefaultRuntimeDir(), `${serverId}.runtime.json`);
690
- try {
691
- await Promise.race([waitForFile(runtimeFile, timeoutMs), childExit]);
692
- const { host, port } = await Promise.race([waitForHealthyRuntime(serverId, timeoutMs), childExit]);
693
- process.stdout.write(`mcp-proxy HTTP runtime ready at http://${host}:${port} (${serverId})\n`);
694
- process.stdout.write(`runtimeId=${serverId}\n`);
695
- process.stdout.write(`runtimeUrl=http://${host}:${port}\n`);
696
- process.stdout.write(`workspaceRoot=${workspaceRoot}\n`);
697
- } catch (error) {
698
- throw new Error(`Failed to prestart HTTP runtime '${serverId}': ${error instanceof Error ? error.message : String(error)}`);
699
- }
700
- });
701
-
702
744
  //#endregion
703
745
  //#region src/commands/bootstrap.ts
704
746
  function toErrorMessage$8(error) {
package/dist/index.cjs CHANGED
@@ -1,4 +1,4 @@
1
- const require_src = require('./src-G1hs2GLZ.cjs');
1
+ const require_src = require('./src-B2m53VQ1.cjs');
2
2
 
3
3
  exports.ConfigFetcherService = require_src.ConfigFetcherService;
4
4
  exports.DefinitionsCacheService = require_src.DefinitionsCacheService;
package/dist/index.mjs CHANGED
@@ -1,3 +1,3 @@
1
- import { C as SearchListToolsTool, D as findConfigFile, E as generateServerId, S as UseToolTool, T as DefinitionsCacheService, _ as StopServerService, a as createProxyContainer, b as createProxyLogger, c as createStdioHttpTransportHandler, f as StdioHttpTransportHandler, g as SkillService, h as HttpTransportHandler, i as createHttpTransportHandler, l as createStdioTransportHandler, m as SseTransportHandler, n as createServer, p as StdioTransportHandler, r as createSessionServer, s as createSseTransportHandler, t as TRANSPORT_MODE, u as initializeSharedServices, v as RuntimeStateService, w as DescribeToolsTool, x as ConfigFetcherService, y as McpClientManagerService } from "./src-0OJqEpGA.mjs";
1
+ import { C as SearchListToolsTool, D as findConfigFile, E as generateServerId, S as UseToolTool, T as DefinitionsCacheService, _ as StopServerService, a as createProxyContainer, b as createProxyLogger, c as createStdioHttpTransportHandler, f as StdioHttpTransportHandler, g as SkillService, h as HttpTransportHandler, i as createHttpTransportHandler, l as createStdioTransportHandler, m as SseTransportHandler, n as createServer, p as StdioTransportHandler, r as createSessionServer, s as createSseTransportHandler, t as TRANSPORT_MODE, u as initializeSharedServices, v as RuntimeStateService, w as DescribeToolsTool, x as ConfigFetcherService, y as McpClientManagerService } from "./src-DCIv5S_2.mjs";
2
2
 
3
3
  export { ConfigFetcherService, DefinitionsCacheService, DescribeToolsTool, HttpTransportHandler, McpClientManagerService, RuntimeStateService, SearchListToolsTool, SkillService, SseTransportHandler, StdioHttpTransportHandler, StdioTransportHandler, StopServerService, TRANSPORT_MODE, UseToolTool, createHttpTransportHandler, createProxyContainer, createProxyLogger, createServer, createSessionServer, createSseTransportHandler, createStdioHttpTransportHandler, createStdioTransportHandler, findConfigFile, generateServerId, initializeSharedServices };
@@ -4342,7 +4342,7 @@ var StdioHttpTransportHandler = class {
4342
4342
 
4343
4343
  //#endregion
4344
4344
  //#region package.json
4345
- var version = "0.4.2";
4345
+ var version = "0.5.0";
4346
4346
 
4347
4347
  //#endregion
4348
4348
  //#region src/container/index.ts
@@ -4313,7 +4313,7 @@ var StdioHttpTransportHandler = class {
4313
4313
 
4314
4314
  //#endregion
4315
4315
  //#region package.json
4316
- var version = "0.4.2";
4316
+ var version = "0.5.0";
4317
4317
 
4318
4318
  //#endregion
4319
4319
  //#region src/container/index.ts
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@agimon-ai/mcp-proxy",
3
3
  "description": "MCP proxy server package",
4
- "version": "0.4.4",
4
+ "version": "0.4.6",
5
5
  "license": "AGPL-3.0",
6
6
  "keywords": [
7
7
  "mcp",
@@ -27,9 +27,9 @@
27
27
  "js-yaml": "^4.1.0",
28
28
  "liquidjs": "^10.21.0",
29
29
  "zod": "^3.24.1",
30
- "@agimon-ai/foundation-process-registry": "0.2.3",
31
- "@agimon-ai/log-sink-mcp": "0.2.7",
32
- "@agimon-ai/foundation-port-registry": "0.2.7"
30
+ "@agimon-ai/foundation-process-registry": "0.2.4",
31
+ "@agimon-ai/foundation-port-registry": "0.2.8",
32
+ "@agimon-ai/log-sink-mcp": "0.2.8"
33
33
  },
34
34
  "devDependencies": {
35
35
  "@types/express": "^5.0.0",